Почему указатели трудны и что с этим делать
Указатели сложны не потому, что это объективно что-то сложное, а потому, что авторы языка Си — козлы, которые позаботились об экономии числа нажатий при печатании, а о том, удобство чтения кода куда важнее, чем удобство его написания, они не подумали. Чего только стоят все эти название функций типа strcspn () и feof ().
Человек должен быть парсером, а это не то, что человеку хорошо удаётся. В случае с указателями, кроме того, что используются плохо читаемые символы, ещё и нет их однозначного «перевода» на человеческий. Например, звёздочка рядом с именем переменной в выражении означает обратное тому, что она означает в описании переменной. Си:
int n[10]; // здесь n хранит адрес, но нет ни звёздочки, ни амперсанда
n[5] = 77; // незаметно поиграли в указатели
*(n + 5) = 77; // здесь звёдочка означает «значение по адресу» (уже заметно)
char *s; // здесь звёздочка уже означает «переменная хранит не значение, а адрес»
unsigned int m = 2131;
*((char *) &m + 1) = ’A’; // теперь, если у меня правильно взорвался мозг, m == 2113
Если бы вместо звёздочки и амперсанда использовались конструкции addressof () и valueat (), для объявления типов был бы модификатор address, а для кастинга использовался бы оператор as указатели бы понимало в 10 раз больше человек. Назовём такой язык Ди (хоть такой уже и есть).
Квадратные скобки в выражениях в Ди пусть означают «значение по адресу с указанным сдвигом», тогда есть для любого x будет справедливо:
x == x[0] == valueat (addressof (x) + 0).
Наличие квадратных скобок в объявлении переменной пусть само по себе не превращает переменную в указатель, то есть, если мы захотим указатель, нам придётся дописать слово address. Тогда запишем первые три строчки нашего кода на Ди:
int address n[10];
valueat (n)[5] = 77; // пока получается некрасиво
valueat (n + 5) = 77; // а тут — нормально
Так обращение к элементам массива, как видно, получается слишком громоздким. Но кто нас заставляет вообще играть в указатели там, где это не нужно? Квадратные скобки в объявлении переменной у нас просто резервируют памяти на несколько таких переменных, но в указатель её не превращают. Так не будем этого делать и мы, выкинем слово address:
int n[10]; // так само n будет хранить значение нулевого инта (n == n[0], напомню)
n[5] = 77; // значение со сдвигом — как раз то, что нам нужно
valueat (addressof (n) + 5) = 77; // длинная запись того же самого
Теперь простая вещь выглядит просто, а игры с указателями выглядят как игры с указателями, но вдобавок не теряют понятности. Синонимичность последних двух строк тоже очевидна. Это прекрасно. А вот другие наши сишные строчки в переводе на Ди:
char address s;
unsigned int m = 2131;
valueat ((addressof (m) as char address) + 1) = ’A’
Теперь, если мы что-нибудь перепутаем, это сразу будет видно:
valueat ((m as char address) + 1) = ’A’ // пытаемся представить значение в качестве адреса
valueat (m) = ’A’ // пытаемся взять значение по адресу m, в то время как m не является адресом
Такой язык не требует ни больших вычислительных ресурсов, чем Си, ни каких-либо ещё достижений современности, зато читать его легче. Чтобы его придумать, нужно было просто отнестись к задаче чуть внимательнее, чем к ней отнёсся тот, кто нажимал Шифт+цифры в поисках ещё незадействованных символов.
Не исключено, что я где-нибудь наошибался, потому что я вхожу в число тех людей, у кого с указателями дружба складывается весьма посредственно. Тогда подскажите, пожалуйста.
Человек должен быть парсером, а это не то, что человеку хорошо удаётся. В случае с указателями, кроме того, что используются плохо читаемые символы, ещё и нет их однозначного «перевода» на человеческий. Например, звёздочка рядом с именем переменной в выражении означает обратное тому, что она означает в описании переменной. Си:
int n[10]; // здесь n хранит адрес, но нет ни звёздочки, ни амперсанда
n[5] = 77; // незаметно поиграли в указатели
*(n + 5) = 77; // здесь звёдочка означает «значение по адресу» (уже заметно)
char *s; // здесь звёздочка уже означает «переменная хранит не значение, а адрес»
unsigned int m = 2131;
*((char *) &m + 1) = ’A’; // теперь, если у меня правильно взорвался мозг, m == 2113
Если бы вместо звёздочки и амперсанда использовались конструкции addressof () и valueat (), для объявления типов был бы модификатор address, а для кастинга использовался бы оператор as указатели бы понимало в 10 раз больше человек. Назовём такой язык Ди (хоть такой уже и есть).
Квадратные скобки в выражениях в Ди пусть означают «значение по адресу с указанным сдвигом», тогда есть для любого x будет справедливо:
x == x[0] == valueat (addressof (x) + 0).
Наличие квадратных скобок в объявлении переменной пусть само по себе не превращает переменную в указатель, то есть, если мы захотим указатель, нам придётся дописать слово address. Тогда запишем первые три строчки нашего кода на Ди:
int address n[10];
valueat (n)[5] = 77; // пока получается некрасиво
valueat (n + 5) = 77; // а тут — нормально
Так обращение к элементам массива, как видно, получается слишком громоздким. Но кто нас заставляет вообще играть в указатели там, где это не нужно? Квадратные скобки в объявлении переменной у нас просто резервируют памяти на несколько таких переменных, но в указатель её не превращают. Так не будем этого делать и мы, выкинем слово address:
int n[10]; // так само n будет хранить значение нулевого инта (n == n[0], напомню)
n[5] = 77; // значение со сдвигом — как раз то, что нам нужно
valueat (addressof (n) + 5) = 77; // длинная запись того же самого
Теперь простая вещь выглядит просто, а игры с указателями выглядят как игры с указателями, но вдобавок не теряют понятности. Синонимичность последних двух строк тоже очевидна. Это прекрасно. А вот другие наши сишные строчки в переводе на Ди:
char address s;
unsigned int m = 2131;
valueat ((addressof (m) as char address) + 1) = ’A’
Теперь, если мы что-нибудь перепутаем, это сразу будет видно:
valueat ((m as char address) + 1) = ’A’ // пытаемся представить значение в качестве адреса
valueat (m) = ’A’ // пытаемся взять значение по адресу m, в то время как m не является адресом
Такой язык не требует ни больших вычислительных ресурсов, чем Си, ни каких-либо ещё достижений современности, зато читать его легче. Чтобы его придумать, нужно было просто отнестись к задаче чуть внимательнее, чем к ней отнёсся тот, кто нажимал Шифт+цифры в поисках ещё незадействованных символов.
Не исключено, что я где-нибудь наошибался, потому что я вхожу в число тех людей, у кого с указателями дружба складывается весьма посредственно. Тогда подскажите, пожалуйста.
void f(int n[10]);
это не тоже самое, что и
void f(int n*);
Так же массив нельзя вернуть из функции, а указатель можно. То есть
int[10] f(); — запись недопустимая, в отличие от
int* f();
Во-вторых, голых указателей вообще следует избегать, пользуясь auto_ptr с typedef-ами:
typedef std::auto_ptr<int> int_ptr;
int_ptr p = new int;
В-третьих, си-касты типа (char*) — так же оставлены только в целях совместимости, и пользоваться ими не следует (то есть вообще за них надо отрывать руки).
int *p = static_cast<int*>(q);
В-четвертых, следует избегать указателей там, где можно использовать ссылку (90% случаев). В том числе это здорово упрощает запись.
В-пятых, «m as char address» резко сокращает гибкость языка, так как такую конструкцию уже не запихнешь, например, в темплейт, а это автоматически снижает ценность языка процентов на 50.
Вообще 99% случаев критики C++ — это следствие того, что люди имеют лишь отдаленное о нем представление на уровне «С++ за 10 дней».
int *p = static_cast<int*>(q); // никто не знает, что написано в этой строчке и как это работает
В результате был бы популярен другой, более лаконичный, язык. И все бы жаловались уже на него.
вконец добивают слова, завершающие блок: if — fi, case — esac
Сергей, самый лаконичный язык — это Brainfuck :-)
> int[10] f(); — запись недопустимая, в отличие от
Если бы в C можно было вернуть массив из функции, согласно правилу «направо—налево», это бы выглядело так:
int f()[10];
А указатели не нужны. Рулят контейнеры и итераторы.
Насчет выучить язык за 10 дней — насколько я помню ты тут недавно рассуждал о профессинализме. То есть в дизайне быть профессионалом необходимо, и если кто-то где-то допустил малейшую ошибку, то его надо расстрелять. Почему ты считаешь, что язык программирования можно выучить за десять дней? Почему такая ассиметричность?
> Если бы вместо звёздочки и амперсанда использовались конструкции addressof () и valueat (), для объявления
> типов был бы модификатор address, а для кастинга использовался бы оператор as указатели бы понимало в
> 10 раз больше человек. Назовём такой язык Ди (хоть такой уже и есть).
Аминь!
Затем такая семантика массивов привела к проблемам — сложно реализовать структуры содержащие массивы, в которых всегда неявно инициализируется одно поле (адресом начала массива в той же структуре), к тому же так их размер получался на одно машинное слово больше, чем кажется. Поэтому массивы были введены в язык по нормальному и понятия массива и указателя были разъединены.
Длина идентификаторов в символьной таблице первых компиляторов C была то ли 7, то ли 8 символов, что накладывало ограничения на имена функций.
А авторы языка Си были не козлы, а нормальные программисты, которые понимали, что без обратной совместимости ни один реально использующийся язык обойтись не может: если нельзя перекомпилировать программу написанную месяц назад, любой нормальный человек поищет более толерантный к своим пользователям язык. Это не значит, что сейчас (или даже всего через несколько лет после создания языка) авторам нравятся его странности. Просто так сложилось и исправить это уже нельзя.
P.S. «n == n[0]» это бред, который помимо отсутствия любого вменяемого физического смысла может так же приводить к идиотским ошибкам. (Что если я забуду указать один из индексов в цикле?)
P.S. http://nsl.com/ — это не про Си но в тему. Языки программирования с очень сложной нотацией, которые тем не менее реально используются во многих областях. Многие программы занимающие на обычных языках целую страницу на них можно записать в строчку. И многим это нравится. Ссылка на тему: http://dr-klm.livejournal.com/42312.html
int n[10];
n[5] = 77;
@ (addr (n) + 5) = 77;
char addr s;
unsigned int m = 2131;
@ ((addr (m) as char addr) + 1) = ’A’
И кратко, и смысл остаётся.
Создатели ПХП, кстати, еще хуже себе представляли, что такое указатели (сравните работу оператора «=» для объектов в 4 и 5 версии), так что не комплексуйте, Илья! :)
*((char *) &m + 1) = ’A’;
Результат тут будет зависеть от того, на каком компьютере вы его запускаете. Little-endian, big-endian, каков размер int в байтах, все дела. На то он и низкоуровневый язык.
На интеле вроде получается m = 16723, я на калькуляторе посчитал.
А для прикладного программирования просто стоило бы использовать другой язык. Тот же паскаль выглядит в этом плане более привлекательно, хоть тоже и не лишен недостатков. Но, конечно, это не должен быть паскаль, а просто другой язык, с синтаксисом, более понятным человеку, чем компилятору.
Твоя критика — это то же самое что прочитать кригу «HTML for Dummies», а потом ругать HTML за то, что оформление не отделено от содержания, а CSS имеет лишь примитивнейшие свойства типа color и font-size. CSS есть за что критиковать, но мы с тобой понимаем, что недостатки CSS кроются совершенно в другом, и они не являются следствием тупорылости W3C — на то есть другие причины, которые ненасильственными мерами просто не поборешь. Ты же рассуждаешь о C++ именно на уровне «представление за 10 дней».
И заметь, что о современном веб-программирование со всеми AJAX-ами тоже за 10 дней нормального представления тоже не получишь. Это не значит, что JavaScript говно.
#define stringWithFormat strcspn
’A’ = 65 = 0x41;
Предположим, что int действительно два байта В зависимости от архитектуры исходное значение 2131 будет идти в памяти как байты 08 53 (у байта 08 адрес младше), либо как 53 08 (у байта 53 адрес младше). При этом адрес m всего числа совпадает с адресом того байта, который «левее» :). Соответственно, меняя байт m + 1 на 0x41, мы получим либо 0x0841 = 2113, либо 0x4153 = 16723. Я могу ошибаться, но мне помнится, что в интеле у байт, представляющих старшие разряды числа, адрес старше. То есть получим мы 16723.
Кстати, очень интересный вопрос, почему интеловцы решили записывать числа в память «задом наперед». И ведь они правы.
А С/C++ остаётся для тех задач, когда надо «очень быстро, на четвереньках и задом наперёд» (из старого анекдота). Для встраиваемых систем, низкоуровневых библиотек, слабого железа, и некторых идиотских мобилок, на которых нет ни Java, ни ObjectiveC))) Т. е. оказывается в той сравнительно узкой нише, где раньше был ассемблер.
З.Ы. а упомянутый «strcspn» — это уже не просто С, это POSIX (http://www.space.unibe.ch/comp_doc/c_manual/C/FUNCTIONS/funcref.htm). Уже успели даже стандарт принять (ISO/IEC 9945).
Арабы пишут справа налево, европейцы пишут слева направо. Числа при этом и те, и другие записывают одинаково. При заимствовании арабских цифр эта особенность не была учтена. Мы начинаем запись числа с его старшего разряда, арабы — с младшего. С первого взгляда кажется логичным расположить нумеровать байты памяти слева направо («нулевой» байт слева, «первый» правее, и т. д.) и расположить число 2131 = 0x0853 в памяти как 08 53. Так же наглядно и естественно, правда?
А теперь посмотрим на число в двоичной записи. Младший бит, указывающий множитель при слагаемом двойки в степени 0, располагается справа. Множитель двойки в степени 1 находится слева от него. И так далее. Выходит, биты нашего числа в выбраной нами записи на распечатке будут выглядеть перемешанными и идти в порядке:
7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8
Чтобы этого избежать, исправим ошибку, допущенную при заимствовании арабских цифр и начнем все записывать справа налево. Нулевой байт, как и нулевой бит любого числа будет находиться справа. Возрастающие адреса будут соответствовать возрастающим степеням двухсотпятидесятишестерки в двухсотпятидесятишестеричной записи числа байтами. Все становится на свои места, если только на распечатках памяти развернуть ось адресов в обратную сторону.
И да, если мы называем старшие адреса памяти «верхними», то, вероятно, при распечатке больших диапазонов памяти старшие адреса должны быть вверху, что мы и видим в интеловской документации. Вот блок памяти 64 КБ строками по 256 байт, заполненный двухбайтовыми числами-указателями на самих себя:
Старшие адреса
FFFF ... FF00
FEFF ... FE00
...
00FF ... 0000
Младшие адреса
А вот диаграммы, изображающие формат сетевых протоколов и файлов, устроены так, что их читают справа налево, сверху вниз. Это потому что по сети и из файла байты приходят по порядку, а не все сразу, и писать код заполнения шаблона заголовка пакета удобнее по картинке, в которой порядок байтов соответствует привычному способу чтения.
char* x, y;
Правильный ответ: один.
int n[10];
n[5] = 77;
@ (addr (n) + 5) = 77;
не совсем понял зачем заменять «*» на «@». Ну то есть совсем не понял. Если вам очень хочется, это может сделать препроцессор.
И мне кстати лаконичность нравится, {} в 100 раз лучше begin..end.
Есть куда более ужасные вещи, например необходимость объявлять функцию *до* ее использования, или дурацкая система инклюдов (+ необходимость писать каждый раз по 2 файла, заголовочный и с кодом, + отсутствие директивы вроде include_once). Ну и еще необходимость ручного управления памятью, куча 2-смысленностей, из-за которых программировать на этом языке можно (имхо) только любителям острых извращений.
Кстати, язык D, очень даже ничего, если бы не сборщик мусора, и огромные бинарники, который делает компилятор (ну и еще пару моментов, вроде плохо реализованных строк :), был бы вообще идеальным языком ))
char* x; char* y;
Согласён с Ильёй в том, что сейчас, в век широкоэкранных мониторов и code autocompletion, можно не экономить символы.
Во-первых, естесственный язык намного сложнее любого языка программирования (хотя бы запомнить где когда какой предлог использовать — уже большая проблема), так же естесственный язык часто допускает многие варианты записи одного и того же, что для языка программирования не допустимо.
Во-вторых обычный текст гораздо хуже читается, когда это программа, так как он не структурирован. С ходу найти в куске текста конкретное предложение, где говорится о чем-либо весьма сложно. Уродливый reinterpret_cast<type>(value) сразу бросается в глаза.
В-третьих, человеческие языки не обладают достаточной краткостью для обозначения строгих формальных понятий (вспомним уродливое «тогда и только тогда»). Как ты в двух словах написашь название операции, которая проверяет корректность приведения типа во время выполнения так, чтобы ее однозначно можно было отделить от приведения типа, который выполняет проверку во время компиляции и от приведения типа, который отменяет свойство const? Кажется, const_cast, reinterpret_cast, static_cast и dynamic_cast — лучшие варианты на это.
Можно на самом деле продолжать бесконечно.
Вообще я не понимаю почему такое внимание уделяется указателям и приведениям типов. Это редкие и опасные операции, которых следует избегать, о чем уродливость конструкции reinterpret_cast<int*>(p) нам еще раз напоминает. Если кто-то работает с указателями и приведениями типов — пускай витамины чтоли пьет, для развития мозга.
«Естесственный язык часто допускает многие варианты записи одного и того же, что для языка программирования не допустимо» — чушь какая-то, я могу сотней способов одно и то же написать на куче ЯП.
Ты передёргиваешь вообще, я не предлагаю приближать язык к человеческому, я предлагаю давать конструкциям вменяемые и осмысленные названия или использовать понятные символы. Если бы оператор ?: использовал вместо вопросительного знака обратный слеш, я бы тоже предложил заменить на вопросительный знак. Звёздочка могла бы работать, допустим, как оператор goto (типа, сноска), а как оператор «значение по адресу» она работает херово, а собака — хорошо, вот и всё.
Писать на Си без указателей невозможно, это не Паскаль тебе.
:)
— я надеюсь, это псевдокод, потому что в реальном коде такая конструкция имеет мало шансов быть оцененной однозначно, тут несколько операторов внутри одной точки следования.
Указатели сложны для многих людей, многие работают программистами, не понимая их, предпочитая Визуал Бейсик и прочее.
Человеку стремящемуся, все равно придется принять саму идею, заложенную в указателях. Если он не в состоянии понять идею, то слова valueat и addressof ему всё равно не помогут. А когда идея будет понята, он первый попросит заменить valueat на «*», а addressof на «&», потому что краткосрочный период обучения — это одно, а всю жизнь потом писать addressof — это другое.
Насчёт прикладного программирования трудно представить, чтобы человек не понимал значение указателей. По крайней мере, если он понимает, что такое полиморфизм и для чего он нужен.
А его лаконичность — это красиво. Такие короткие и емкие записи получаются, вроде while (*s++ = *t++); — сказка просто :-)
>... дурацкая система инклюдов ... отсутствие директивы вроде include_once ...
но есть ведь #pragma once ? Не стандарт, но это другое дело.
а вообще
#ifndef __MYFILE_H__
#define __MYFILE_H__
...
#endif //__MYFILE_H__
и вся проблема
Для понимания достаточно знать, что типы переменных в Си именуются не так, как в более новых языках, т. е. не слева-направо. Они называются так, как будут использоваться
int *x; // *x has type int
int n[10]; // n[i] has type int
И всё. Достаточно прочесть учебник. Это другой способ, а не кривой, хотя, конечно, каждый знает единственно верный способ.
> int n[10]; // здесь n хранит адрес, но нет ни звёздочки, ни амперсанда
n не хранит ничего (т. е. нигде не написано, что там лежит указатель), это массив, который неявно преобразуется в указатель на нулевой элемент (что прекрасно имплементируется и при Вашем способе хранения).
> x == x[0]
Зато это считается нормальным и однозначным.
По поводу козлов авторов есть хорошая поговорка «Был бы я такой умный вчера, как моя жена сейчас». Задним числом все умны.
Я давно для себя решил, что в нормальном объектном языке для обращения к методам и атрибутам используется точка. Если используется что-то другое, значит это плохой объектный язык.
Я знаю, про использвоание дефайнов для этого, их сейчас некоторые иды даже сами подставляют, но все равно это уродство. А между pragma once и include_once есть разница — одна применяется в включаемом файле, другая — в том, который включает.
> все равно это уродство
ну, если «уродство» — это свойство явления, вызывающее отвращение, то это субъективное высказывание, т. к. у вас вызывает, а у кого-то не вызывает :-)
> между pragma once и include_once есть разница
а как эта разница мешает вам обеспечить одноразовое включение .заголовочного файла?
было:
pCar->goForward(); //машина — вперёд!
стало:
valueat (pCar).goForward(); // что-то по адресу «машина»... а что там? оно умеет вперёд?..
Ну ладно, хоть Страуструпа отмазали :-) Кериниган и Ричи по-прежнему опасносте! :-)
Попробуем применить методику на трудночитаемом фрагменте чистого Си:
было:
void ** (*func) (int &, char **(*)(char *, char **));
стало:
void address address (address func) (int ref, char address address (address) (char address, char address address));
стало лучше?
Стало ли лучше я не знаю, потому, что я не понимаю что написано. Расскажите?
1. ссылка на int
2. указатель на функцию, которая тоже имеет два параметра:
1. указатель на char
2. указатель на указатель на char
эта функция возвращает указатель на указатель на char
а func возвращает указатель на указатель на void
typedef void ** (*func) (int &, char **(*)(char *, char **));
char ** callback (char *, char **)
{
...
}
func pFunc = (func) GetProcAddress (hSomeDll, "funcFromSomeLibrary");
int a = 5;
void **p;
p = pFunc (a, callback);
понятно, что это учебный пример, но у меня есть реальный проект, где я писал объектные обёртки для одной географической библиотеки. Если интересно, могу показать, как в «прикладном ПО» выглядит, только там Си++ и кода надо приводить страницы на полторы-две, иначе контекста не уловить.
Это всё жесть, конечно.
Жесть, но на первый взгляд, а вообще, в работе встречается. Для меня лично жесть — это наследование шаблонов.
typedef void ** (*RENDER_MAP_FUNC) (int, char **(*)(char *, char **));
char ** callback (char *pc, char **ppc)
{
/* pCurRenderFunc могла бы вызывать её
по ходу длительной операции, типа "n% complete..." */
...
}
RENDER_MAP_FUNC pCurRenderFunc =
(RENDER_MAP_FUNC) GetProcAddress (hSomeDll, "funcFromSomeLibrary");
int zoomLevel = 5;
void **pResult; /* массив массивов с результатами */
pResult = pCurRenderFunc (zoomLevel, callback);
На самом деле, на РПГ можно писать и в более удобном для программиста стиле (в «Ай-Би-Эм» тоже нашёлся свой Илья Бирман), но большинство профессиональных программистов на РПГ начинают с поддержки существующего кода, который оформлен в старом стиле. Переписать весь код по-новому ?— задача неподъёмная, поэтому проще научить програмистов понимать неудобную запись.
P. S. В предыдущем сообщении я набрал четыре звездочки подряд, очевидно тут полужирное начертание делается с помощью обрамления в двойные звездочки. Может сделать тег <code>?
Хотя идея с предложенной вами нотацией неплоха, мне кажется, что в указателях трудна не столько нотация, сколько сами указатели.
Трудность состоит в том, что в языке есть переменные (они же объекты), которые хранят некоторые значения, а есть указатели, которые, хоть и выглядят, как остальные переменные, но кардинально отличаются от них тем, что сами как будто бы ничего не хранят, а лишь неким образом ссылаются на другие переменные. Таким образом, в языке создается два типа разнородных сущностей, внешне выглядящих одинаково.
А основная сложность состоит как раз в обращении к значению по указателю. Чтобы понять подобную конструкцию, нужно произвести более сложные умственные действия, чем для того, чтобы понять просто присваивание значения переменной, например.
А стоит лишь немного усложнить использование — например, использовать указатель на указатель, и многие даже опытные программисты могут начать испытывать трудности с пониманием таких конструкций. Не сомневаюсь, что указатель на указатель на указатель вызовет трудности у всех без исключения.
Параллель с дифференцированием и интегрированием, кстати, неплохо это иллюстрирует. Думаю, не такая это тривиальная задача — представить, как связаны между собой некоторая функция и ее вторая производная, не говоря уже о третьей.
Я думаю, оптимальным решением было бы вообще не использовать указатели в таком виде, в каком они используются в C и C++.
Указатель в C и C++ — это лишь частный ограниченный случай ссылки, заставляющий размещать объекты только в оперативной памяти, занимая непрерывную ее область, и давать им строго определенную структуру. А ведь объект, в принципе, может размещаться и в памяти другого процесса или компьютера, на диске или в сети. К тому же, объекта вообще может не существовать физически — его значение может вычисляться в результате работы какого-то алгоритма, он может быть разбит на части в оперативной памяти, он может занимать несколько битов внутри различных байт, не находящихся рядом друг с другом, и так далее.
Интересно, что понятие ссылки является фундаментальным в программировании, и реализация ссылок в языке программирования весьма существенно влияет на стиль, характер и возможности языка. Например, некоторые авторы основной характеристикой языка C называют именно указатели.
> Указатель в C и C++ — это лишь частный ограниченный случай ссылки ...
Дмитрий, в C++ есть понятие ссылки. Как во-вашему, в чем её отличие от указателя?
И ваше понимание «объектов» очень заинтересовало меня. Это которые не в оперативной памяти, а на диске или в сети.
Если вы знаете C, вы сами скажете, в чем отличие ссылки от указателя.
Но я говорю не про эту ссылку. Кстати, дискуссия-то шла о C, а не C.
Я говорю об абстрактном понятии ссылки. Например, текст «#53 Павел Малинников» — это ссылка на ваше сообщение, она локальна для этой страницы, а это ссылка на страницу: «http://ilyabirman.ru/meanwhile/2009/05/26/1/comments/», ISBN книги — это ссылка на книгу, адрес объекта в оперативной памяти — это ссылка на объект, и так далее.
А понятие «объекта» в том смысле, в котором его определяют в теории объектно-ориентированного программирования, вряд ли изменится, если объект будет храниться на диске или в сети, а не в оперативной памяти.
Ведь диск — это тоже память, которая отличается от оперативной памяти энергонезависимостью, скоростью и, возможно, другой моделью адресации (ссылок).
Как вы знаете, еще есть такие типы памяти как ПЗУ и SSD.
Вся разница здесь только в том, как вы ссылаетесь на этот объект (ну и, возможно, как вы получаете доступ к его данным).
Допустим, «0x0F326E70» — это ссылка на объект в памяти текущего процесса (его адрес), «c:\file.bin, 0x32FE0» — это ссылка на объект хранящийся в файле (имя файла и смещение в байтах), «http://site.com/file.bin, 0x32FE0» — это ссылка на объект, хранящийся в сети (url файла и смещение в байтах).
Например, ISBN книги — это ссылка на книгу. Как из этого следует, что «оптимальным решением было бы вообще не использовать указатели в таком виде, в каком они используются в C и C++»?
Да, дискуссия о C. Зачем же вы тогда говорите об «абстрактном понятии ссылки»? Или вы просто нас разыгрываете?
Есть объект в оперативной памяти и файл, в котором хранятся данные, на основе которых объект в памяти создаётся. Я не могу поверить, что человек, не видящий этой разницы, может публично высказываться о целесообразности использования указателей и т. п.
У вас не возникало вопроса, почему при доступности терабайтных винчестеров люди всё-таки покупают оперативную память? Ставят себе жалкие 16 Гб оперативки, а винчестером 1 Тб не пользуются? Это ведь тоже память? Наверное, потому, что в случае оперативной памяти скорость доступа выше, да? А так бы пользовались за милую душу?
По вашей ссылке «http://site.com/file.bin, 0x32FE0» записано, например, «ED 03 BF 16 A4». Раз уж вы упомянули ООП, скажите, пожалуйста, какого типа это объект? Есть ли у него какие-нибудь методы? Как их вызвать?
самого устройства компьютера. Но они отражают её честно, давая программисту модель,
при помощи которой он будет понимать, что происходит у него в программе.
Вне зависимости от языка, на котором пишет.
А языки, которые пытаются скрыть устройство объектов (например Паскаль),
защищает программиста не от сложности, а от понимания, как работает память.
Из-за чего полно людей, рассуждающих об указателях, основываясь только на своих фантазиях.
Причём все как один, склоняются, что «не надо бы их использовать», а то вдруг «объект»
не в тех битах байта разместится, что тогда будем делать?
Вот они, какие, указатели-то. Опасные.