Что такое указатель C, если не адрес памяти?


206

В авторитетном источнике о C, после обсуждения &оператора дается следующая информация :

... Немного прискорбно, что терминология [адрес] остается, потому что она сбивает с толку тех, кто не знает, о чем идет речь, и вводит в заблуждение тех, кто это делает: думать о указателях так, как будто они были адресами, обычно приводит к горе ... ,

Другие материалы, которые я читал (я бы сказал, из столь же авторитетных источников), всегда безоговорочно называли указатели и &операторы как адреса памяти. Я хотел бы продолжать искать актуальность этого вопроса, но это довольно сложно, когда авторитетные источники KIND OF не согласны.

Теперь я немного растерялся - что же это за указатель, если не адрес памяти?

PS

Позже автор говорит: ... Я буду продолжать использовать термин «адрес», потому что изобрести другой [термин] было бы еще хуже.


118
Указатель - это переменная, которая содержит адрес. У него также есть собственный адрес. В этом принципиальная разница между указателем и массивом. Массив фактически является адресом (и, по сути , его адрес является самим собой ).
WhozCraig

7
Какой у вас "авторитетный источник" для цитаты?
Cornstalks

22
Источником авторитетности является языковой стандарт, а не книги, полу-производные от него и полу-потянувшиеся от автора. Я усвоил это трудным путем, делая почти каждую возможную ошибку, и постепенно строил мысленную модель C, несколько похожую на описанную стандартом, а затем, наконец, заменил упомянутую модель моделью стандарта.
Алексей Фрунзе

9
@thang Люди думают, что pointer = integer, потому что это часто так (x86 Linux и Windows «учат» нас этому), потому что люди любят обобщать, потому что люди плохо знают языковой стандарт и потому, что у них мало опыта в радикально разных платформ. Те же люди, вероятно, предполагают, что указатель на данные и указатель на функцию могут быть преобразованы друг в друга, а данные могут быть выполнены как код, а код - как данные. Хотя это может быть верно для архитектур фон Неймана (с 1 адресным пространством), но не обязательно верно для архитектур Гарварда (с кодом и пространством данных).
Алексей Фрунзе

6
Стандарты @exebook не для новичков (особенно полных). Они не должны давать мягких представлений и множества примеров. Они формально что-то определяют, поэтому это могут быть правильно реализованы профессионалами.
Алексей Фрунзе

Ответы:


148

Стандарт C не определяет, что такое указатель внутри и как он работает внутри. Это сделано намеренно, чтобы не ограничивать количество платформ, где C может быть реализован как скомпилированный или интерпретируемый язык.

Значение указателя может быть своего рода идентификатором или дескриптором или комбинацией нескольких идентификаторов (например, привет сегментам и смещениям x86) и необязательно реальным адресом памяти. Этот идентификатор может быть чем угодно, даже текстовой строкой фиксированного размера. Неадресные представления могут быть особенно полезны для интерпретатора языка C.


34
Там не так много, чтобы объяснить. Каждая переменная имеет свой адрес в памяти. Но вам не нужно хранить их адреса в указателях на них. Вместо этого вы можете нумеровать ваши переменные от 1 до чего угодно и сохранять это число в указателе. Это вполне законно для языкового стандарта, если реализация знает, как преобразовать эти числа в адреса и как сделать арифметику указателей с этими числами и всеми другими вещами, требуемыми стандартом.
Алексей Фрунзе

4
Я хотел бы добавить, что в x86 адрес памяти состоит из селектора сегмента и смещения, поэтому представление указателя в виде сегмента: смещение по-прежнему использует адрес памяти.
Тханг

6
@Lundin У меня нет проблем, игнорируя общий характер стандарта и неприменимость, когда я знаю свою платформу и мой компилятор. Однако оригинальный вопрос является общим, поэтому при ответе на него нельзя игнорировать стандарт.
Алексей Фрунзе

8
@ Лундин Вам не нужно быть революционером или ученым. Предположим, вы хотите эмулировать 32-разрядный компьютер на физическом 16-разрядном компьютере, и вы расширяете 64 КБ ОЗУ до 4 ГБ, используя дисковое хранилище, и внедряете 32-разрядные указатели в виде смещений в огромный файл. Эти указатели не являются реальными адресами памяти.
Алексей Фрунзе

6
Лучшим примером, который я когда-либо видел, была реализация C для Symbolics Lisp Machines (около 1990 года). Каждый объект C был реализован как массив Lisp, а указатели были реализованы в виде пары массива и индекса. Из-за проверки границ массива в Lisp вы никогда не сможете переполниться от одного объекта к другому.
Бармар

62

Я не уверен в вашем источнике, но тип языка, который вы описываете, исходит из стандарта C:

6.5.3.2 Операторы адреса и косвенности
[...]
3. Унарный оператор & выдает адрес своего операнда. [...]

Так что ... да, указатели указывают на адреса памяти. По крайней мере, так говорит стандарт С.

Проще говоря, указатель - это переменная, содержащая значение некоторого адреса . Адрес объекта (который может храниться в указателе) возвращается с унарным &оператором.

Я могу сохранить адрес «42 Wallaby Way, Sydney» в переменной (и эта переменная будет своего рода «указателем», но, поскольку это не адрес памяти, мы не будем называть его «указателем»). Ваш компьютер имеет адреса для своих блоков памяти. Указатели хранят значение адреса (то есть указатель хранит значение «42 Wallaby Way, Sydney», которое является адресом).

Редактировать: Я хочу расширить комментарий Алексея Фрунзе.

Что именно является указателем? Давайте посмотрим на стандарт C:

6.2.5 Типы
[...]
20. [...]
Тип указателя может быть получен из типа функции или типа объекта, называемого ссылочным типом . Тип указателя описывает объект, значение которого предоставляет ссылку на объект ссылочного типа. Тип указателя, полученный из ссылочного типа T, иногда называют «указателем на T». Конструкция типа указателя из ссылочного типа называется «деривацией типа указателя». Тип указателя является полным типом объекта.

По сути, указатели хранят значение, которое предоставляет ссылку на некоторый объект или функцию. Вид. Указатели предназначены для хранения значения, которое предоставляет ссылку на некоторый объект или функцию, но это не всегда так:

6.3.2.3 Указатели
[...]
5. Целое число может быть преобразовано в любой тип указателя. За исключением случаев, указанных ранее, результат определяется реализацией, может быть некорректно выровнен, может не указывать на объект ссылочного типа и может быть представлением прерываний.

Приведенная выше цитата говорит, что мы можем превратить целое число в указатель. Если мы сделаем это (то есть, если мы вставим целочисленное значение в указатель вместо конкретной ссылки на объект или функцию), то указатель «может не указывать на объект ссылочного типа» (т. Е. Он может не обеспечивать ссылка на объект или функцию). Это может дать нам что-то еще. И это единственное место, где вы можете вставить какой-то дескриптор или идентификатор в указатель (то есть указатель не указывает на объект; он хранит значение, которое представляет что-то, но это значение может не быть адресом).

Так что да, как говорит Алексей Фрунзе, возможно, указатель не хранит адрес объекта или функции. Возможно, вместо этого указатель хранит некоторый «дескриптор» или идентификатор, и вы можете сделать это, присвоив указателю какое-то произвольное целочисленное значение. То, что представляет этот дескриптор или идентификатор, зависит от системы / среды / контекста. Пока ваша система / реализация может понять значение, вы в хорошей форме (но это зависит от конкретного значения и конкретной системы / реализации).

Обычно указатель хранит адрес объекта или функции. Если он не хранит фактический адрес (для объекта или функции), результат определяется реализацией (это означает, что именно то, что происходит и то, что теперь представляет указатель, зависит от вашей системы и реализации, поэтому это может быть дескриптор или идентификатор для конкретной системы, но использование того же кода / значения в другой системе может привести к сбою вашей программы).

Это оказалось дольше, чем я думал, что это будет ...


3
В интерпретаторе C указатель может содержать неадресный идентификатор / handle / etc.
Алексей Фрунзе

4
@exebook Стандарт никоим образом не ограничен скомпилированным C.
Алексей Фрунзе

7
@ Лундин Браво! Давайте больше игнорировать стандарт! Как будто мы уже недостаточно проигнорировали это и не создали из-за этого глючное и плохо переносимое программное обеспечение. Кроме того, пожалуйста, не делайте так, чтобы исходный вопрос был общим и, как таковой, нуждается в общем ответе.
Алексей Фрунзе

3
Когда другие говорят, что указатель может быть дескриптором или чем-то другим, а не адресом, они не просто означают, что вы можете привести данные в указатель, преобразовав целое число в указатель. Они означают, что компилятор может использовать что-то кроме адресов памяти для реализации указателей. На процессоре Alpha с ABI DEC указатель функции не был адресом функции, но был адресом дескриптора функции, а дескриптор содержал адрес функции и некоторые данные о параметрах функции. Дело в том, что стандарт С очень гибок.
Эрик Постпишил

5
@Lundin: утверждение, что указатели реализованы в виде целочисленных адресов на 100% существующих компьютерных систем в реальном мире, является ложным. Компьютеры существуют с адресацией слов и смещением сегментов. Компиляторы все еще существуют с поддержкой ближних и дальних указателей. Существуют компьютеры PDP-11 с RSX-11 и Task Builder и его наложениями, в которых указатель должен идентифицировать информацию, необходимую для загрузки функции с диска. Указатель не может иметь адрес памяти объекта, если объект не находится в памяти!
Эрик Постпишил

39

Указатель против переменной

В этой картине,

pointer_p - это указатель, который расположен в 0x12345 и указывает на переменную variable_v в 0x34567.


16
Мало того, что это не относится к понятию адреса, в отличие от указателя, но оно полностью пропускает точку, что адрес не просто целое число.
Жиль "ТАК ... перестать быть злым"

19
-1, это просто объясняет, что такое указатель. Это был не question-- и вы оттесняя все сложности , что вопрос является о.
Алексис

34

Думать о указателе как об адресе - это приближение . Как и во всех приближениях, иногда это достаточно полезно, но также не совсем точно, что означает, что полагаться на него вызывает проблемы.

Указатель похож на адрес в том смысле, что он указывает, где найти объект. Одно непосредственное ограничение этой аналогии - то, что не все указатели фактически содержат адрес. NULLэто указатель, который не является адресом Содержимое переменной-указателя фактически может быть одного из трех видов:

  • адрес объекта, который может быть разыменовано (если pсодержит адрес , xто выражение *pимеет такое же значение , как x);
  • указатель NULL , из которых NULLв качестве примера;
  • недопустимое содержимое, которое не указывает на объект (если pоно не содержит допустимого значения, то *pможет сделать что-либо («неопределенное поведение»), с довольно частой возможностью сбоя программы).

Более того, было бы точнее сказать, что указатель (если он действителен и не равен нулю) содержит адрес: указатель указывает, где найти объект, но с ним связано больше информации.

В частности, указатель имеет тип. На большинстве платформ тип указателя не имеет влияния во время выполнения, но он влияет не только на тип во время компиляции. Если pуказатель на int( int *p;), то p + 1указывает на целое число, которое является sizeof(int)байтами после p(при условии, что p + 1это все еще допустимый указатель). Если qуказатель charуказывает на тот же адрес, что и p( char *q = p;), то q + 1это не тот же адрес, что и p + 1. Если вы рассматриваете указатель как адрес, не очень интуитивно понятно, что «следующий адрес» различен для разных указателей на одно и то же местоположение.

В некоторых средах возможно иметь несколько значений указателей с разными представлениями (разными битовыми комбинациями в памяти), которые указывают на одно и то же место в памяти. Вы можете рассматривать их как разные указатели, содержащие один и тот же адрес, или как разные адреса для одного и того же местоположения - в этом случае метафора не ясна. ==Оператор всегда говорит вам ли два операнда указывают на то же место, так что на этих условиях вы можете иметь , p == qхотя pи qимеют различные битовые шаблоны.

Существуют даже среды, в которых указатели переносят другую информацию за пределы адреса, такую ​​как информация о типе или разрешении. Вы можете легко пройти свою жизнь как программист, не сталкиваясь с этим.

Существуют среды, в которых разные виды указателей имеют разные представления. Вы можете думать об этом как о разных видах адресов, имеющих разные представления. Например, некоторые архитектуры имеют байтовые указатели и указатели слов или указатели объектов и указатели функций.

В целом, думать об указателях как об адресах не так уж и плохо, если учесть, что

  • это только допустимые ненулевые указатели, которые являются адресами;
  • Вы можете иметь несколько адресов для одного и того же местоположения;
  • вы не можете делать арифметику по адресам, и на них нет порядка;
  • указатель также несет информацию о типе.

Обратный путь намного более хлопотный. Не все, что выглядит как адрес, может быть указателем . Где-то глубоко внутри любой указатель представлен в виде битового шаблона, который можно прочитать как целое число, и вы можете сказать, что это целое число является адресом. Но если пойти по другому пути, не каждое целое число является указателем.

Сначала есть некоторые известные ограничения; например, целое число, обозначающее местоположение вне адресного пространства вашей программы, не может быть допустимым указателем. Неверно выровненный адрес не делает действительный указатель для типа данных, который требует выравнивания; например, на платформе, где intтребуется 4-байтовое выравнивание, 0x7654321 не может быть допустимым int*значением.

Однако это выходит далеко за рамки этого, потому что когда вы превращаете указатель в целое число, вы попадаете в мир неприятностей. Большая часть этой проблемы заключается в том, что оптимизирующие компиляторы гораздо лучше справляются с микрооптимизацией, чем большинство программистов ожидают, поэтому их ментальная модель работы программы глубоко неверна. То, что у вас есть указатели с одинаковым адресом, не означает, что они эквивалентны. Например, рассмотрим следующий фрагмент:

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

Вы можете ожидать, что на обычной машине, где sizeof(int)==4и sizeof(short)==2, это либо печатает 1 = 1?(little-endian), либо 65536 = 1?(big-endian). Но на моем 64-битном ПК с Linux с GCC 4.4:

$ c99 -O2 -Wall a.c && ./a.out 
a.c: In function main’:
a.c:6: warning: dereferencing pointer p does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?

GCC достаточно любезен, чтобы предупредить нас о том, что идет не так в этом простом примере - в более сложных примерах компилятор может не заметить. Так как pимеет другой тип &x, то изменение того, на что pуказывает, не может повлиять на то, на что &xуказывает (за исключением некоторых четко определенных исключений). Поэтому компилятор может сохранять значение xв регистре и не обновлять этот регистр как *pизменения. Программа разыменовывает два указателя на один и тот же адрес и получает два разных значения!

Мораль этого примера заключается в том, что думать о (ненулевом действительном) указателе как об адресе хорошо, если вы придерживаетесь точных правил языка Си. Обратная сторона медали в том, что правила языка Си являются сложными, и их трудно понять интуитивно, если вы не знаете, что происходит под капотом. И то, что происходит под капотом, заключается в том, что связь между указателями и адресами несколько ослабла, как для поддержки «экзотических» архитектур процессоров, так и для поддержки оптимизирующих компиляторов.

Поэтому думайте, что указатели являются адресами, как первый шаг в вашем понимании, но не следуйте этой интуиции слишком далеко.


5
+1. Другие ответы, кажется, пропускают, что указатель идет с информацией о типе. Это гораздо важнее, чем адрес / ID / любое обсуждение.
undur_gongor

+1 Отличный балл о типе информации. Я не уверен, что примеры компиляторов верны, хотя ... Кажется очень маловероятным, например, что *p = 3гарантированно будет успешным, когда p не был инициализирован.
LarsH

@LarsH Ты прав, спасибо, как я это написал? Я заменил его примером, который даже демонстрирует удивительное поведение на моем ПК.
Жиль "ТАК - перестань быть злым"

1
ну, это NULL ((void *) 0) ..?
Аникет Инге

1
@ gnasher729 Нулевой указатель является указателем. NULLнет, но для уровня детализации, требуемого здесь, это не имеет значения, отвлекает. Даже для повседневного программирования тот факт, что он NULLможет быть реализован как нечто, не NULLговорящее «указатель», встречается не часто (прежде всего, переходя к функции с переменным числом аргументов - но даже там, если вы не приводите ее) вы уже предполагаете, что все типы указателей имеют одинаковое представление).
Жиль "ТАК - перестань быть злым"

19

Указатель - это переменная, которая хранит адрес памяти, а не сам адрес. Однако вы можете разыменовать указатель - и получить доступ к ячейке памяти.

Например:

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

Вот и все. Это так просто.

введите описание изображения здесь

Программа для демонстрации того, что я говорю, и ее выход здесь:

http://ideone.com/rcSUsb

Программа:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}

5
Это может запутать еще больше. Алиса, ты видишь кота? Нет, я вижу только улыбку кота. То есть, говоря, что указатель - это адрес, или указатель - это переменная, которая содержит адрес, или говоря, что указатель - это имя концепции, которая относится к идее адреса, как далеко могут зайти авторы книг в запутанных новостях?
Exebook

@exebook для тех, кто закален в указателях, это довольно просто. Может быть, картина поможет?
Аникет Инге

5
Указатель не обязательно содержит адрес. В интерпретаторе C это может быть что-то еще, какой-то ID / дескриптор.
Алексей Фрунзе

«Метка» или имя переменной является компилятором / ассемблером и не существует на уровне машины, поэтому я не думаю, что оно должно появиться в памяти.
Бен

1
@Aniket Переменная-указатель может содержать значение указателя. Вам нужно сохранить результат fopenв переменную только в том случае, если вам нужно использовать его более одного раза (что, к fopenпримеру, почти всегда).
Жиль "ТАК ... перестать быть злым"

16

Трудно сказать, что именно имеют в виду авторы этих книг. Содержит ли указатель адрес или нет, зависит от того, как вы определяете адрес и как вы определяете указатель.

Судя по всем написанным ответам, некоторые люди предполагают, что (1) адрес должен быть целым числом и (2) указатель не обязательно должен быть виртуальным, чтобы его не было сказано в спецификации. С этими допущениями ясно, что указатели не обязательно содержат адреса.

Тем не менее, мы видим, что хотя (2), вероятно, верно, (1), вероятно, не обязательно должно быть верно. И что делать с тем фактом, что & называется адресом оператора согласно ответу @ CornStalks? Означает ли это, что авторы спецификации намереваются, чтобы указатель содержал адрес?

Можно ли сказать, что указатель содержит адрес, но адрес не обязательно должен быть целым числом? Может быть.

Я думаю, что все это - бредовой педантичный семантический разговор. Это абсолютно бесполезно практически говоря. Можете ли вы представить себе компилятор, который генерирует код таким образом, что значение указателя не является адресом? Если да, то? Это то, о чем я думал...

Я думаю, что автор книги (первая выдержка, утверждающая, что указатели не обязательно являются просто адресами), вероятно, ссылается на тот факт, что указатель сопровождается информацией о типе.

Например,

 int x;
 int* y = &x;
 char* z = &x;

и y, и z являются указателями, но y + 1 и z + 1 различны. если они являются адресами памяти, разве эти выражения не дадут вам одинаковое значение?

И здесь во лжи размышления о указателях, как если бы они были адресами, обычно приводят к печали . Ошибки были написаны, потому что люди думают об указателях, как будто они были адресами , и это обычно приводит к горе .

55555, вероятно, не указатель, хотя это может быть адрес, но (int *) 55555 является указателем. 55555 + 1 = 55556, но (int *) 55555 + 1 составляет 55559 (+/- разница в размере size (int)).


1
+1 для указания на указатель арифметика не совпадает с арифметикой на адресах.
kutschkem

В случае 16-битного 8086 адрес памяти описывается основанием сегмента + смещение, оба 16 бит. Существует много комбинаций сегмента base + offset, которые дают один и тот же адрес в памяти. Этот farуказатель не просто "целое число".
vonbrand

@ Vonbrand Я не понимаю, почему вы опубликовали этот комментарий. этот вопрос обсуждался как комментарии под другими ответами. практически любой другой ответ предполагает, что адрес = целое число, а все, что не является целым числом, не является адресом. Я просто указываю на это и отмечаю, что это может или не может быть правильно. Весь мой смысл в ответе заключается в том, что он не имеет отношения к делу. все это просто педантично, и главная проблема не решается в других ответах.
Thang

@tang, идея "указатель == адрес" неверна . То, что все и их любимая тетя продолжают говорить, не делает это правильно.
vonbrand

@ vonbrand, а почему ты сделал этот комментарий под моим постом? Я не сказал, что это правильно или неправильно. На самом деле, это правильно в определенных сценариях / предположениях, но не всегда. Позвольте мне снова резюмировать суть поста (во второй раз). Весь мой смысл в ответе заключается в том, что он не имеет отношения к делу. все это просто педантично, и главная проблема не решается в других ответах. было бы более целесообразно прокомментировать ответы, которые утверждают, что указатель == адрес или адрес == целое число. см. мои комментарии под постом Алексея относительно сегмента: смещение.
Thang

15

Ну, указатель - это абстракция, представляющая область памяти. Обратите внимание, что цитата не говорит, что думать о указателях, как будто они были адресами памяти, неверна, она просто говорит, что «обычно приводит к горе». Другими словами, это приводит к неверным ожиданиям.

Наиболее вероятным источником горя, безусловно, является арифметика указателей, которая на самом деле является одной из сильных сторон Си. Если указатель был адресом, можно ожидать, что арифметика указателя будет арифметикой адреса; но это не так. Например, добавление 10 к адресу должно дать вам адрес, который больше на 10 единиц адресации; но добавление 10 к указателю увеличивает его в 10 раз по сравнению с типом объекта, на который он указывает (и даже не фактическим размером, а округленным до границы выравнивания). При использовании int *обычной архитектуры с 32-разрядными целыми числами добавление к ней 10 увеличит ее на 40 единиц адресации (байтов). Опытные программисты на C знают об этом и живут этим, но ваш автор, очевидно, не фанат небрежных метафор.

Есть дополнительный вопрос о том, как содержимое указателя представляет ячейку памяти: как объяснили многие ответы, адрес не всегда является целым (или длинным). В некоторых архитектурах адрес - это «сегмент» плюс смещение. Указатель может даже содержать только смещение в текущем сегменте («ближний» указатель), которое само по себе не является уникальным адресом памяти. И содержимое указателя может иметь только косвенную связь с адресом памяти, как его понимает аппаратное обеспечение. Но автор цитируемой цитаты даже не упоминает репрезентацию, поэтому я думаю, что они имели в виду концептуальную эквивалентность, а не репрезентацию.


12

Вот как я объяснил это некоторым сбитым с толку людям в прошлом: указатель имеет два атрибута, которые влияют на его поведение. У него есть значение , которое является (в типичных средах) адресом памяти и типом , который сообщает вам тип и размер объекта, на который он указывает.

Например, учитывая:

union {
    int i;
    char c;
} u;

Вы можете иметь три разных указателя, указывающих на один и тот же объект:

void *v = &u;
int *i = &u.i;
char *c = &u.c;

Если вы сравните значения этих указателей, они все равны:

v==i && i==c

Однако, если вы увеличите каждый указатель, вы увидите, что тип, на который они указывают, становится релевантным.

i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c

Переменные iи cбудут иметь разные значения в этой точке, потому что i++заставляет iсодержать адрес следующего доступного целого числа и c++заставляет cуказывать на следующий адресуемый символ. Как правило, целые числа занимают больше памяти, чем символы, поэтому в iитоге получим большее значение, чем cпосле того, как они оба будут увеличены.


2
+1 Спасибо. С помощью указателей ценность и тип неразделимы, так как человек может отделить тело человека от его души.
Аки Суйхконен

i == cплохо сформирован (вы можете сравнивать указатели на разные типы только при неявном преобразовании из одного в другой). Кроме того, исправление этого с помощью приведения означает, что вы применили преобразование, и тогда остается дискуссионным, изменяет ли преобразование значение или нет. (Вы можете утверждать, что это не так, но тогда это просто утверждение того же, что вы пытались доказать с помощью этого примера).
ММ

8

Марк Бесси уже сказал это, но это нужно еще раз подчеркнуть, пока не поймут.

Указатель имеет такое же отношение к переменной, как литерал 3.

Указатель - это кортеж значения (адреса) и типа (с дополнительными свойствами, такими как только чтение). Тип (и дополнительные параметры, если таковые имеются) могут дополнительно определять или ограничивать контекст; например. __far ptr, __near ptr: каков контекст адреса: стек, куча, линейный адрес, смещение откуда-то, физическая память или что.

Это свойство типа, которое делает арифметику указателей немного отличной от целочисленной арифметики.

Счетчик примеров того, что указатель не является переменной, слишком велик, чтобы его игнорировать

  • fopen возвращает указатель FILE. (где переменная)

  • указатель стека или указатель кадра обычно являются неадресуемыми регистрами

    *(int *)0x1231330 = 13; - приведение произвольного целочисленного значения к типу pointer_of_integer и запись / чтение целого числа, даже не вводя переменную

Во время жизни C-программы будет много других экземпляров временных указателей, которые не имеют адресов - и, следовательно, они не являются переменными, а выражениями / значениями с типом, связанным со временем компиляции.


8

Вы правы и вменяемы. Обычно указатель - это просто адрес, поэтому вы можете привести его к целому числу и выполнить любую арифметику.

Но иногда указатели являются только частью адреса. На некоторых архитектурах указатель преобразуется в адрес с добавлением базы или используется другой регистр ЦП .

Но в наши дни на ПК и архитектуре ARM с плоской моделью памяти и языком C, скомпилированным с нуля, нормально думать, что указатель является целочисленным адресом в некотором месте в одномерной адресуемой ОЗУ.


ПК ... плоская модель памяти? Какие селекторы?
Тханг

Riight. И когда произойдет следующее изменение архитектуры, возможно, с отдельным кодом и пространством данных, или кто-то вернется к почтенной сегментной архитектуре (которая имеет огромное значение для безопасности, может даже добавить некоторый ключ к номеру сегмента + смещение, чтобы проверить разрешения), ваш милые «указатели - просто целые числа» рушатся.
vonbrand

7

Указатель, как и любая другая переменная в C, по сути является набором битов, которые могут быть представлены одним или несколькими объединенными unsigned charзначениями (как и в любом другом типе cariable, sizeof(some_variable)будет указывать количество unsigned charзначений). Что отличает указатель от других переменных, так это то, что компилятор C будет интерпретировать биты в указателе как идентифицирующие место, где может храниться переменная. В C, в отличие от некоторых других языков, можно запросить пространство для нескольких переменных, а затем преобразовать указатель на любое значение в этом наборе в указатель на любую другую переменную в этом наборе.

Многие компиляторы реализуют указатели, используя свои биты, хранящие фактические машинные адреса, но это не единственно возможная реализация. Реализация могла бы сохранить один массив - недоступный для кода пользователя - перечисляющий аппаратный адрес и выделенный размер всех объектов памяти (наборов переменных), которые использовала программа, и каждый указатель содержал бы индекс в массив вдоль со смещением от этого индекса. Такая конструкция позволила бы системе не только ограничивать работу кода только над памятью, которой она владела, но и гарантировать, что указатель на один элемент памяти не может быть случайно преобразован в указатель на другой элемент памяти (в системе, использующей аппаратное обеспечение). адреса, если fooи barявляются массивами из 10 элементов, которые хранятся последовательно в памяти, указатель на «одиннадцатый» элементfooвместо этого может указывать на первый элемент bar, но в системе, где каждый «указатель» является идентификатором объекта и смещением, система может перехватить ловушку, если код попытается проиндексировать указатель за fooпределами своего выделенного диапазона). Такая система также могла бы устранить проблемы фрагментации памяти, поскольку физические адреса, связанные с любыми указателями, можно перемещать.

Обратите внимание, что, хотя указатели несколько абстрактны, они недостаточно абстрактны, чтобы компилятор C, полностью соответствующий стандартам, мог реализовать сборщик мусора. Компилятор C указывает, что каждая переменная, включая указатели, представлена ​​в виде последовательности unsigned charзначений. Для любой переменной можно разложить ее на последовательность чисел, а затем преобразовать эту последовательность чисел обратно в переменную исходного типа. Следовательно, программа могла быcallocнекоторое хранилище (получая указатель на него), сохраняйте что-то там, разбивайте указатель на серию байтов, отображайте их на экране и затем стирайте все ссылки на них. Если программа затем принимает некоторые цифры с клавиатуры, восстанавливает их в указателе, а затем пытается прочитать данные из этого указателя, и если пользователь вводит те же числа, которые ранее отображала программа, программа должна будет выводить данные который был сохранен в callocпамяти. Поскольку невозможно представить, чтобы компьютер мог узнать, сделал ли пользователь копию отображенных чисел, невозможно предположить, если бы компьютер мог знать, будет ли когда-либо доступ к вышеупомянутой памяти в будущем.


При больших накладных расходах, возможно, вы могли бы обнаружить любое использование значения указателя, которое могло бы «утечь» его числовое значение, и закрепить распределение так, чтобы сборщик мусора не собирал или не перемещал его (если free, конечно, не вызывается явно). Будет ли полученная реализация настолько полезной, это другой вопрос, так как ее способность собирать может быть слишком ограниченной, но вы можете, по крайней мере, назвать ее сборщиком мусора :-) Назначение указателя и арифметика не «утекут» значение, но любой доступ к char*неизвестному происхождению должен быть проверен.
Стив Джессоп

@SteveJessop: я думаю, что такой дизайн будет хуже, чем бесполезный, так как для кода будет невозможно узнать, какие указатели необходимо освободить. Сборщики мусора, которые предполагают, что все, что выглядит как указатель, могут быть чрезмерно консервативными, но обычно вещи, которые похожи, но не являются указателями, имеют возможность изменения, что позволяет избежать «постоянных» утечек памяти. Любое действие, которое выглядит как разложение указателя на байты, навсегда замораживает указатель - гарантированный рецепт утечек памяти.
суперкат

Я думаю, что он все равно потерпит неудачу по соображениям производительности - если вы хотите, чтобы ваш код работал так медленно, потому что каждый доступ проверяется, не пишите его на C ;-) У меня больше надежд на изобретательность программистов на C, чем на вас, поскольку я думаю, что это неудобно, вероятно, не исключено, что избежать ненужного закрепления выделений. В любом случае, C ++ определяет «безопасные производные указатели» именно для решения этой проблемы, поэтому мы знаем, что делать, если мы когда-нибудь захотим повысить абстрактность указателей C до уровня, на котором они поддерживают достаточно эффективный сбор мусора.
Стив Джессоп

@SteveJessop: для того, чтобы система GC была полезной, она должна либо иметь возможность надежно высвобождать память, для которой freeеще не был вызван, либо предотвращать превращение любой ссылки на освобожденный объект в ссылку на живой объект [даже при использовании ресурсов, которые требуют явное управление временем жизни, GC все еще может выполнять последнюю функцию]; система GC, которая иногда ошибочно рассматривает объекты как имеющие живые ссылки на них, может быть полезной, если вероятность того, что N объектов будет ненужно закреплено одновременно, приближается к нулю, когда N становится большим . Если только кто-то не захочет
пометить

... для кода, который является допустимым C ++, но для которого компилятор не сможет доказать, что указатель никогда не может быть преобразован в неузнаваемую форму, я не вижу, как можно избежать риска, что программа, которая на самом деле никогда использует указатели, так как целые числа могут ошибочно рассматриваться как таковые.
суперкат

6

Указатель - это тип переменной, который изначально доступен в C / C ++ и содержит адрес памяти. Как и любая другая переменная, она имеет собственный адрес и занимает память (количество зависит от платформы).

Одна из проблем, которую вы увидите в результате путаницы, - попытка изменить референт внутри функции, просто передав указатель по значению. Это создаст копию указателя в области действия функции и любые изменения в том месте, где этот новый указатель «указывает», не изменит референта указателя в области действия, вызвавшей функцию. Чтобы изменить действительный указатель внутри функции, обычно нужно передать указатель на указатель.


1
Как правило, это ручка / ID. Обычно это простой адрес.
Алексей Фрунзе

Я изменил свой ответ, чтобы быть немного более подходящим для ПК к определению Handle в википедии. Мне нравится ссылаться на указатели как на конкретный экземпляр дескриптора, так как дескриптор может быть просто ссылкой на указатель.
Мэтью Сандерс

6

КРАТКОЕ РЕЗЮМЕ (которое я также поставлю вверху):

(0) Думать об указателях как адресах часто является хорошим инструментом обучения и часто является реальной реализацией указателей на обычные типы данных.

(1) Но на многих, возможно, большинстве, указатели компиляторов на функции не являются адресами, а больше, чем адрес (как правило, в 2 раза, иногда больше), или на самом деле являются указателями на структуру в памяти, которая содержит адреса функций и тому подобное постоянный пул.

(2) Указатели на элементы данных и указатели на методы часто даже более странны.

(3) Устаревший код x86 с проблемами указателей FAR и NEAR

(4) Несколько примеров, особенно IBM AS / 400, с безопасными «жирными указателями».

Я уверен, что вы можете найти больше.

ДЕТАЛЬ:

UMMPPHHH !!!!! Многие из ответов до сих пор являются довольно типичными ответами "программиста", но не "компилятора" или "аппаратного обеспечения". Так как я притворяюсь аппаратным и часто работаю с компилятором, позвольте мне добавить два моих цента:

На многих, возможно, на большинстве компиляторов Си указатель на данные типа Tявляется, по сути, адресом T.

Хорошо.

Но даже на многих из этих компиляторов некоторые указатели НЕ являются адресами. Вы можете сказать это, посмотрев на sizeof(ThePointer).

Например, указатели на функции иногда намного больше, чем обычные адреса. Или они могут включать уровень косвенности. Эта статьяпредоставляет одно описание, включающее процессор Intel Itanium, но я видел другие. Как правило, для вызова функции вы должны знать не только адрес кода функции, но также адрес пула констант функции - область памяти, из которой константы загружаются с помощью одной инструкции загрузки, а не компилятор, который должен генерировать 64-битная константа из нескольких команд Load Immediate и Shift и OR. Таким образом, вместо одного 64-битного адреса, вам нужно 2 64-битных адреса. Некоторые ABI (двоичные интерфейсы приложений) перемещают это как 128 бит, тогда как другие используют уровень косвенности, причем указатель функции фактически является адресом дескриптора функции, который содержит 2 фактических адреса, которые только что упомянуты. Что лучше? Зависит от вашей точки зрения: производительность, размер кода, и некоторые проблемы совместимости - часто код предполагает, что указатель может быть приведен к длинному или длинному длинному, но может также предполагать, что длинный длинный составляет ровно 64 бита. Такой код может не соответствовать стандартам, но, тем не менее, клиенты могут захотеть, чтобы он работал.

У многих из нас остались болезненные воспоминания о старой сегментированной архитектуре Intel x86 с БЛИЖАЙШИМИ И ДАЛЬНЕЙШИМИ указателями. К счастью, они почти вымерли, так что только краткое резюме: в 16-битном реальном режиме реальный линейный адрес был

LinearAddress = SegmentRegister[SegNum].base << 4 + Offset

В то время как в защищенном режиме это может быть

LinearAddress = SegmentRegister[SegNum].base + offset

с результирующим адресом, проверяемым по ограничению, установленному в сегменте. Некоторые программы использовали не совсем стандартные объявления указателей FAR и NEAR в C / C ++, но многие только что сказали *T- но были переключатели компилятора и компоновщика, так что, например, указатели кода могли быть рядом с указателями, просто 32-битное смещение относительно того, что находится в регистр CS (кодовый сегмент), в то время как указатели данных могут быть указателями FAR, указывая как 16-битный номер сегмента, так и 32-битное смещение для 48-битного значения. Теперь, оба эти количества, безусловно, связаны с адресом, но, поскольку они не одинакового размера, какой из них является адресом? Кроме того, сегменты также имели разрешения - только чтение, чтение-запись, исполняемый файл - в дополнение к материалам, связанным с фактическим адресом.

Более интересным примером, IMHO, является (или, возможно, был) семейство IBM AS / 400. Этот компьютер был одним из первых, кто внедрил ОС на C ++. Указатели на этом механизме обычно в 2 раза превышают реальный размер адреса - например, в этой презентацииговорит, 128-битные указатели, но фактические адреса были 48-64 бит, и, опять же, некоторая дополнительная информация, которая называется возможностью, которая предоставляла разрешения, такие как чтение, запись, а также ограничение для предотвращения переполнения буфера. Да, вы можете сделать это совместимо с C / C ++ - и если бы это было повсеместно, китайская PLA и славянская мафия не взломали бы так много западных компьютерных систем. Но исторически большинство программирования на C / C ++ пренебрегали безопасностью для производительности. Самое интересное, что семейство AS400 позволило операционной системе создавать безопасные указатели, которые можно было бы дать непривилегированному коду, но которые непривилегированный код не мог подделать или подделать. Опять же, безопасность и, в то время как совместимый со стандартами, много неаккуратного, не отвечающего стандартам кода C / C ++ не будут работать в такой защищенной системе. Опять же, есть официальные стандарты,

Теперь я избавлюсь от своего мыла безопасности и упомяну о некоторых других способах, которыми указатели (различных типов) часто не адресованы в действительности: указатели на элементы данных, указатели на методы функций-членов и их статические версии больше, чем обычный адрес. Как говорится в этом посте :

Есть много способов решения этой проблемы [проблемы, связанные с единичным или множественным наследованием и виртуальным наследованием]. Вот как компилятор Visual Studio решает справиться с этим: указатель на функцию-член класса с множественным наследованием - это действительно структура. «И они продолжают говорить:« Приведение указателя на функцию может изменить его размер! ».

Как вы, вероятно, догадываетесь из моих понтификаций по (в) безопасности, я принимал участие в аппаратных / программных проектах на C / C ++, где указатель рассматривался скорее как возможность, чем необработанный адрес.

Я мог бы продолжить, но я надеюсь, что вы поняли идею.

КРАТКОЕ РЕЗЮМЕ (которое я также поставлю вверху):

(0) представление об указателях как об адресах часто является хорошим средством обучения и часто является реальной реализацией указателей на обычные типы данных.

(1) Но на многих, возможно, большинстве, указатели компиляторов на функции не являются адресами, но больше, чем адрес (обычно в 2 раза, иногда больше), или на самом деле являются указателями на структуру в памяти, которая содержит адреса функций и тому подобное постоянный пул.

(2) Указатели на элементы данных и указатели на методы часто даже более странны.

(3) Устаревший код x86 с проблемами указателей FAR и NEAR

(4) Несколько примеров, особенно IBM AS / 400, с безопасными «жирными указателями».

Я уверен, что вы можете найти больше.


В реальном режиме 16 бит LinearAddress = SegmentRegister.Selector * 16 + Offset(обратите внимание, раз 16, а не сдвиг на 16). В защищенном режиме LinearAddress = SegmentRegister.base + offset(без умножения любого вида; база сегмента сохраняется в GDT / LDT и кэшируется в регистре сегмента как есть ).
Алексей Фрунзе

Вы также правы в отношении сегмента базы. Я ошибся. Это предел сегмента, который может быть кратен 4K. База сегментов должна быть просто расшифрована аппаратным обеспечением, когда она загружает дескриптор сегмента из памяти в регистр сегмента.
Крейзи Глеу

4

Указатель - это просто еще одна переменная, которая используется для хранения адреса ячейки памяти (обычно это адрес памяти другой переменной).


Итак, pointee - это адрес памяти? Вы не согласны с автором? Просто пытаюсь понять.
d0rmLife

Основная функция указателя - указывать на что-то. Как именно это достигается и есть ли реальный адрес или нет, не определено. Указатель может быть просто идентификатором / дескриптором, а не реальным адресом.
Алексей Фрунзе

4

Вы можете видеть это таким образом. Указатель - это значение, представляющее адрес в адресуемой области памяти.


2
Указатель не обязательно должен содержать в себе реальный адрес памяти. Смотрите мой ответ и комментарий под ним.
Алексей Фрунзе

what .... указатель на первую переменную в стеке не печатает 0. он печатает верх (или низ) фрейма стека в зависимости от того, как он реализован.
Тханг

@thang Для первой переменной верх и низ совпадают. И каков адрес вершины или низа в этом случае стека?
Валентин Раду

@ValentinRadu, почему бы вам не попробовать это ... очевидно, вы не пробовали это.
Тханг

2
@thang Вы правы, я сделал несколько очень плохих предположений, для моей защиты здесь 5 утра.
Валентин Раду

3

Указатель - это просто еще одна переменная, которая может содержать адрес памяти, обычно другой переменной. Указатель, являющийся переменной, тоже имеет адрес памяти.


1
Не обязательно адрес. Кстати, вы читали существующие ответы и комментарии, прежде чем опубликовать свой ответ?
Алексей Фрунзе

3

Указатель переменного тока очень похож на адрес памяти, но с удаленными машинно-зависимыми деталями, а также некоторыми функциями, которых нет в наборе команд более низкого уровня.

Например, указатель C относительно богато типизирован. Если вы увеличиваете указатель через массив структур, он приятно переходит из одной структуры в другую.

Указатели подчиняются правилам преобразования и обеспечивают проверку типов во время компиляции.

Существует специальное значение «нулевого указателя», которое переносимо на уровне исходного кода, но представление которого может отличаться. Если вы назначаете целочисленную константу, значение которой равно нулю, указателю, этот указатель принимает значение нулевого указателя. То же самое, если вы инициализируете указатель таким образом.

Указатель может использоваться в качестве логической переменной: он проверяет истину, если она отлична от нуля, и ложь, если она равна нулю.

В машинном языке, если нулевой указатель представляет собой забавный адрес, такой как 0xFFFFFFFF, вам, возможно, придется иметь явные тесты для этого значения. С скрывает это от вас. Даже если нулевой указатель равен 0xFFFFFFFF, вы можете проверить его, используя if (ptr != 0) { /* not null! */}.

Использование указателей, которые подрывают систему типов, приводит к неопределенному поведению, тогда как подобный код на машинном языке может быть хорошо определен. Ассемблеры будут собирать написанные вами инструкции, но компиляторы C будут оптимизировать, исходя из предположения, что вы не сделали ничего плохого. Если float *pуказатель указывает на long nпеременную и *p = 0.0выполняется, компилятору не требуется обрабатывать это. Последующее использование nне обязательно будет считывать битовую комбинацию значения с плавающей запятой, но, возможно, это будет оптимизированный доступ, основанный на предположении о "строгом псевдониме", nкоторое не было затронуто! То есть предположение, что программа хорошо себя ведет, и поэтому pне должно указывать на n.

В C указатели на код и указатели на данные различны, но на многих архитектурах адреса совпадают. Могут быть разработаны компиляторы C, которые имеют «жирные» указатели, хотя целевая архитектура этого не делает. Жирные указатели означают, что указатели являются не просто машинными адресами, но содержат другую информацию, такую ​​как информация о размере объекта, на который указывает объект, для проверки границ. Портативно написанные программы легко портировать на такие компиляторы.

Итак, вы можете видеть, что есть много семантических различий между машинными адресами и C-указателями.


Указатели NULL не работают так, как вы думаете, они работают на всех платформах - см. Мой ответ на CiscoIPPhone выше. NULL == 0 - предположение, которое справедливо только для платформ на основе x86. Конвенция гласит, что новые платформы должны соответствовать x86, однако, особенно во встроенном мире, это не так. Изменить: Кроме того, C не делает ничего, чтобы абстрагировать значение указателя пути от оборудования - "ptr! = 0" не будет работать как тест NULL на платформе, где NULL! = 0.
DX-MON

1
DX-MON, это совершенно неправильно для стандарта C. NULL имеет значение 0, и они могут использоваться взаимозаменяемо в выражениях. Независимо от того, является ли представление NULL-указателя в аппаратном обеспечении всеми 0 битами, не имеет значения, как оно представлено в исходном коде.
Марк Бесси

@ DX-MON Боюсь, вы не работаете с правильными фактами. В C выражение с интегральной константой служит константой нулевого указателя, независимо от того, является ли нулевой указатель нулевым адресом. Если вы знаете, что компилятор C ptr != 0не является нулевым тестом, пожалуйста, покажите его личность (но прежде чем сделать это, отправьте отчет об ошибке поставщику).
Каз

Я понимаю, к чему вы клоните, но ваши комментарии о нулевых указателях непоследовательны, потому что вы путаете указатели и адреса памяти - именно то, чего советует избегать цитата, приведенная в вопросе! Правильное утверждение: C определяет нулевой указатель как нулевой, независимо от того, является ли адрес памяти со смещением ноль допустимым или нет.
Алексис

1
@alexis Глава и стих, пожалуйста. C не определяет нулевой указатель как ноль. C определяет ноль (или любое целочисленное константное выражение, значение которого равно нулю) в качестве синтаксиса для обозначения константы нулевого указателя. faqs.org/faqs/C-faq/faq (раздел 5).
Kaz

3

Прежде чем понимать указатели, мы должны понимать объекты. Объекты - это сущности, которые существуют и имеют спецификатор местоположения, называемый адресом. Указатель - это просто переменная, как и любые другие переменные Cс типом, pointerчье содержимое интерпретируется как адрес объекта, который поддерживает следующую операцию.

+ : A variable of type integer (usually called offset) can be added to yield a new pointer
- : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
  : A variable of type pointer can be subtracted to yield an integer (usually called offset)
* : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
++: It's just `+= 1`
--: It's just `-= 1`

Указатель классифицируется в зависимости от типа объекта, на который он ссылается в данный момент. Единственная часть информации, которая имеет значение, - это размер объекта.

Любой объект поддерживает операцию &(адрес), которая извлекает спецификатор местоположения (адрес) объекта как указатель типа объекта. Это должно уменьшить путаницу, связанную с номенклатурой, так как это имеет смысл называть &операцией объекта, а не указателем, результирующий тип которого является указателем типа объекта.

Примечание. В этом объяснении я опускаю понятие памяти.


Мне нравится ваше объяснение абстрактной реальности общего указателя в общей системе. Но, возможно, обсуждение памяти будет полезно. На самом деле, говоря от себя, я знаю, что это будет ...! Я думаю, что обсуждение связи может быть очень полезным для понимания общей картины. +1 в любом случае :)
d0rmLife

@ d0rmLife: у вас достаточно объяснений в других ответах, которые охватывают общую картину. Я просто хотел дать математическое абстрактное объяснение в качестве другого взгляда. Также ИМХО, это вызвало бы меньше путаницы при вызове &«Address of», так как он больше привязан к объекту, чем к указателю как таковому »
Abhijit

Не обижайся, но я сам решу, что такое достаточное объяснение. Одного учебника недостаточно для полного объяснения структур данных и распределения памяти. ;) .... в любом случае, ваш ответ по- прежнему полезен , даже если он не нов.
d0rmLife

Нет смысла обращаться с указателями без понятия памяти . Если объект существует без памяти, он должен находиться в месте, где нет адреса - например, в регистрах. Чтобы быть в состоянии использовать «&» предполагает память.
Аки Суйхконен

3

Адрес используется для идентификации части памяти фиксированного размера, обычно для каждого байта, как целое число. Это точно называется байтовым адресом , который также используется ISO C. Могут быть некоторые другие методы для создания адреса, например, для каждого бита. Однако, так часто используется только байтовый адрес, мы обычно опускаем «байт».

Технически, адрес никогда не является значением в C, потому что определение термина «значение» в (ISO) C:

точное значение содержимого объекта при интерпретации как имеющего определенный тип

(Подчеркнуто мной.) Однако в C. нет такого «типа адреса».

Указатель не тот же. Указатель является своего рода типом в языке Си. Существует несколько различных типов указателей. Они не обязательно подчиняются одинаковому набору правил языка, например, влиянию ++значения типа int*против char*.

Значение в C может иметь тип указателя. Это называется значением указателя . Чтобы было ясно, значение указателя не является указателем на языке Си. Но мы привыкли смешивать их вместе, потому что в C это вряд ли будет неоднозначным: если мы называем выражение pкак «указатель», это просто значение указателя, но не тип, так как именованный тип в C не является выражается выражением , но именем типа или именем определения типа .

Некоторые другие вещи тонки. Как пользователь C, во-первых, нужно знать, что objectозначает:

область хранения данных в среде исполнения, содержимое которой может представлять значения

Объект - это объект, представляющий значения определенного типа. Указатель является типом объекта . Таким образом, если мы объявляем int* p;, то pозначает «объект типа указателя» или «объект указателя».

Обратите внимание, что в стандарте отсутствует «переменная», нормативно определенная стандартом (фактически, в нормативном тексте ISO C никогда не используется как существительное). Однако, неофициально, мы называем объект переменной, как это делает другой язык. (Но все же не совсем так, например, в C ++ переменная может иметь нормативный тип ссылки , который не является объектом.) Фразы «объект-указатель» или «переменная-указатель» иногда обрабатываются как «значение указателя», как указано выше, с возможная небольшая разница. (Еще один набор примеров - «массив».)

Так как указатель является типом, а адрес в C фактически «не имеет типа», значение указателя примерно «содержит» адрес. А выражение типа указателя может дать адрес, например

ISO C11 6.5.2.3

3 Унарный &оператор выдает адрес своего операнда.

Обратите внимание, что эта формулировка введена WG14 / N1256, то есть ISO C99: TC3. В с99 есть

3 Унарный &оператор возвращает адрес своего операнда.

Он отражает мнение комитета: адрес не является значением указателя, возвращаемого унарным &оператором.

Несмотря на вышеприведенную формулировку, в стандартах все еще есть некоторые проблемы.

ISO C11 6.6

9 Адресная константа - это нулевой указатель, указатель на l-значение, обозначающее объект статической длительности хранения, или указатель на указатель функции

ISO C ++ 11 5.19

3 ... Выражение константы адреса - это выражение основной переменной типа prvalue типа указателя, которое оценивает адрес объекта со статической продолжительностью хранения, адрес функции или значение нулевого указателя, или выражение постоянной переменной ядра prvalue. типа std::nullptr_t. ...

(В недавнем черновом варианте стандарта C ++ используется другая формулировка, поэтому этой проблемы не существует.)

На самом деле и «адресная константа» в C, и «адресная константа-выражение» в C ++ являются константным выражением типов указателей (или, по крайней мере, «подобных указателям» типов начиная с C ++ 11).

И встроенный унарный &оператор называется «address-of» в C и C ++; аналогично, std::addressofвводится в C ++ 11.

Эти названия могут привести к неправильному представлению. Результате выражение имеет тип указателя, так что они будут интерпретироваться как: результат содержит / дает адрес, а не является адресом.


2

Он говорит: «потому что это сбивает с толку тех, кто не знает, о чем идет речь», а также, это правда: если вы узнаете, о чем речь, вы не будете смущены. Теоретически указатель представляет собой переменную, которая указывает на другую, практически содержит адрес, который является адресом переменной, на которую он указывает. Я не знаю, почему должен скрывать этот факт, это не ракетостроение. Если вы понимаете указатели, вы на шаг приблизитесь к пониманию работы компьютеров. Преуспевать!


2

Если подумать, я думаю, что это вопрос семантики. Я не думаю, что автор прав, так как стандарт C ссылается на указатель как на удерживающий адрес объекта, на который есть ссылка, как уже упоминалось здесь другими. Однако адрес! = Адрес памяти. Адрес может быть действительно любым в соответствии со стандартом C, хотя в конечном итоге он приведет к адресу памяти, сам указатель может быть идентификатором, смещение + селектор (x86), действительно любым, если он может описать (после отображения) любую память адрес в адресном пространстве.


Указатель содержит адрес (или нет, если он нулевой). Но это совсем не то, что это адрес: например, два указателя на один и тот же адрес, но с другим типом, не эквивалентны во многих ситуациях.
Жиль "ТАК - перестань быть злым"

@Gilles Если вы видите «бытие», как в int i=5-> я равен 5, тогда указатель - это адрес да. Кроме того, у нуля есть адрес. Обычно неверный адрес записи (но не обязательно, см. Режим x86-real), но адрес тем не менее. На самом деле существует только 2 требования для нуля: гарантируется сравнение неравного с указателем на реальный объект, а любые два нулевых указателя будут сравниваться одинаково.
Валентин Раду

Напротив, нулевой указатель гарантированно не будет равен адресу любого объекта. Разыменование нулевого указателя - неопределенное поведение. Большая проблема с утверждением, что «указатель является адресом», заключается в том, что они работают по-разному. Если pуказатель, p+1не всегда адрес увеличивается на 1.
Жиль "ТАК, перестань быть злым"

Прочитайте еще раз комментарий, пожалуйста it's guaranteed to compare unequal to a pointer to an actual object. Что касается арифметики указателей, то я не вижу смысла, значение указателя по-прежнему является адресом, даже если операция «+» не обязательно добавит к нему один байт.
Валентин Раду

1

Еще один способ отличия указателя на C или C ++ от простого адреса памяти из-за различных типов указателей, которые я не видел в других ответах (хотя, учитывая их общий размер, я мог его не заметить). Но это, наверное, самый важный, потому что даже опытные программисты на C / C ++ могут запутаться:

Компилятор может предположить, что указатели несовместимых типов не указывают на один и тот же адрес, даже если они явно это делают, что может привести к поведению, которое было бы невозможным при использовании простой модели указателя == адреса. Рассмотрим следующий код (при условии sizeof(int) = 2*sizeof(short)):

unsigned int i = 0;
unsigned short* p = (unsigned short*)&i;
p[0]=p[1]=1;

if (i == 2 + (unsigned short)(-1))
{
  // you'd expect this to execute, but it need not
}

if (i == 0)
{
  // you'd expect this not to execute, but it actually may do so
}

Обратите внимание, что есть исключение для char*, поэтому манипулирование значениями char*возможно (хотя и не очень переносимо).


0

Краткий обзор: адрес AC - это значение, обычно представляемое в виде адреса памяти на уровне компьютера, с определенным типом.

Безусловное слово «указатель» неоднозначно. C имеет объекты указателя (переменные), типы указателя, выражения указателя и значения указателя .

Очень часто слово «указатель» используется для обозначения «объект указателя», и это может привести к некоторой путанице - вот почему я пытаюсь использовать «указатель» как прилагательное, а не как существительное.

Стандарт C, по крайней мере в некоторых случаях, использует слово «указатель» для обозначения «значение указателя». Например, описание malloc говорит, что оно «возвращает либо нулевой указатель, либо указатель на выделенное пространство».

Так какой адрес в С? Это значение указателя, т. Е. Значение определенного типа указателя. (За исключением того, что значение нулевого указателя не обязательно упоминается как «адрес», так как это не адрес чего-либо).

Стандартное описание унарного &оператора говорит, что оно «дает адрес своего операнда». Вне стандарта C слово «адрес» обычно используется для обозначения (физического или виртуального) адреса памяти, обычно одного слова размером (независимо от того, «слово» в данной системе).

«Адрес» AC обычно реализуется как машинный адрес - так же, как intзначение C обычно реализуется как машинное слово. Но адрес C (значение указателя) - это больше, чем просто адрес машины. Это значение, обычно представляемое в виде машинного адреса, и это значение определенного типа .


0

Значение указателя является адресом. Переменная-указатель - это объект, который может хранить адрес. Это правда, потому что именно так стандарт определяет указатель. Важно сообщить об этом новичкам на C, поскольку новички на C часто не понимают разницы между указателем и тем, на что он указывает (то есть они не знают разницы между оболочкой и зданием). Понятие адреса (у каждого объекта есть адрес, и это то, что хранит указатель) очень важно, потому что оно улаживает это.

Тем не менее, стандарт говорит на определенном уровне абстракции. Те, о которых автор говорит о том, кто «знает, о каких адресах», но кто не знаком с C, обязательно должны были узнать об адресах на другом уровне абстракции - возможно, с помощью языка ассемблера программирования. Нет гарантии, что реализация C использует то же представление для адресов, что и опкоды ЦП (называемые в этом отрывке «адресом хранилища»), о которых эти люди уже знают.

Он продолжает говорить об «совершенно разумных манипуляциях с адресом». Что касается стандарта C, то в принципе не существует такого понятия, как «совершенно разумное манипулирование адресом». Дополнение определяется по указателям, и это в основном это. Конечно, вы можете преобразовать указатель в целое число, выполнить несколько побитовых или арифметических операций, а затем преобразовать его обратно. Это не гарантирует работоспособность по стандарту, поэтому, прежде чем писать этот код, вам лучше узнать, как ваша конкретная реализация C представляет указатели и выполняет это преобразование. Это , вероятно , использует адрес представительства вы ожидаете, но он это делает не то, что это ваша вина , потому что вы не читали руководство. Это не путаница, это неправильная процедура программирования ;-)

Короче говоря, C использует более абстрактную концепцию адреса, чем автор.

Понятие адреса автора, конечно, также не является словом самого низкого уровня по этому вопросу. Что касается карт виртуальной памяти и адресации физической оперативной памяти для нескольких микросхем, то число, которое вы указываете ЦП как «адрес магазина», к которому вы хотите получить доступ, в основном не имеет ничего общего с тем, где данные, которые вы хотите, фактически находятся на аппаратном уровне. Это все слои косвенности и представления, но автор выбрал один для привилегии. Если вы собираетесь это сделать, когда говорите о C, выберите уровень C для привилегий !

Лично я не думаю, что замечания автора являются настолько полезными, кроме как в контексте введения Си программистам на ассемблере. Тем, кто прибывает из языков более высокого уровня, определенно не полезно говорить, что значения указателя не являются адресами. Было бы намного лучше признать сложность, чем сказать, что ЦПУ обладает монополией на то, чтобы сказать, что такое адрес, и, таким образом, значения указателя С "не являются" адресами. Это адреса, но они могут быть написаны не на тех языках, которые он имеет в виду. Я думаю, было бы целесообразно различать две вещи в контексте C как «адрес» и «адрес магазина».


0

Проще говоря, указатели фактически являются смещенной частью механизма сегментации, который преобразуется в линейный адрес после сегментации, а затем в физический адрес после подкачки. Физические адреса на самом деле адресованы вам, баран.

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.