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

194

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

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

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

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

P.S.

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

  • 115
    Указатель - это переменная, которая содержит адрес. У этого также есть свой собственный адрес. Это принципиальная разница между указателем и массивом. Массив фактически является адресом (и, по сути, его адрес является самим собой ).
  • 2
    @WhozCraig Действительно, но в цитате особенно хотелось бы отметить, что & не возвращает фактический адрес памяти. Так что же тогда возвращается?
Показать ещё 29 комментариев
Теги:
pointers
memory-address

25 ответов

138

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

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

  • 0
    Не могли бы вы немного рассказать о обработчиках / идентификаторах? Я думаю, что это поможет моему пониманию. Я не сталкивался с этими условиями до сих пор!
  • 32
    Там не так много, чтобы объяснить. Каждая переменная имеет свой адрес в памяти. Но вам не нужно хранить их адреса в указателях на них. Вместо этого вы можете пронумеровать ваши переменные от 1 до чего угодно и сохранить это число в указателе. Это совершенно законно для языкового стандарта, если реализация знает, как преобразовать эти числа в адреса и как сделать арифметику указателей с этими числами и всеми другими вещами, требуемыми стандартом.
Показать ещё 25 комментариев
64

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

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

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

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

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

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

Что такое указатель? Посмотрите на стандарт C:

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

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

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

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

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

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

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

  • 3
    В интерпретаторе C указатель может содержать неадресный идентификатор / handle / etc.
  • 0
    Интерпретатор C не является языком C, описанным стандартом. Это «интерпретированный C», который отличается от животного и, насколько я знаю, не стандартизирован.
Показать ещё 16 комментариев
38

Изображение 4812

На этом рисунке

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

  • 6
    +1 Лучший способ объяснить, что именно это экономит, - это через картинку. Неплохо!
  • 15
    Мало того, что это не относится к понятию адреса, в отличие от указателя, но оно полностью пропускает точку, что адрес не просто целое число.
Показать ещё 3 комментария
29

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

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

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

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

В частности, указатель имеет тип. На большинстве платформ тип указателя не влияет на время выполнения, но имеет влияние, которое выходит за пределы типа во время компиляции. Если 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. Программа разделяет два указателя на один и тот же адрес и получает два разных значения!

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

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

  • 5
    +1. Другие ответы, кажется, пропускают, что указатель идет с информацией о типе. Это гораздо важнее, чем адрес / ID / любое обсуждение.
  • 0
    +1 Отличный балл о типе информации. Я не уверен, что примеры компиляторов верны, хотя ... Кажется очень маловероятным, например, что *p = 3 гарантированно успешно, когда p не был инициализирован.
Показать ещё 6 комментариев
19

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

Например:

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*/

Что это. Это просто.

Изображение 4813

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

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
    Это может запутать еще больше. Алиса, ты видишь кота? Нет, я вижу только улыбку кота. То есть, говоря, что указатель - это адрес, или указатель - это переменная, которая содержит адрес, или говоря, что указатель - это имя концепции, которая относится к идее адреса, как далеко могут зайти книжники в запутанных новостях?
  • 0
    @exebook для тех, кто закален в указателях, это довольно просто. Может быть, картина поможет?
Показать ещё 7 комментариев
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 (+/- разность в терминах sizeof (int)).

  • 1
    +1 для указания на указатель арифметика не совпадает с арифметикой на адресах.
  • 0
    В случае 16-битного 8086 адрес памяти описывается основанием сегмента + смещение, оба 16 бит. Существует много комбинаций сегмента base + offset, которые дают одинаковый адрес в памяти. Этот far указатель не просто "целое число".
Показать ещё 4 комментария
14

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

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

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

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 после того, как они будут увеличены.

  • 0
    Отметить очень хороший пример +1
  • 2
    +1 Спасибо. С помощью указателей ценность и тип неразделимы, так как человек может отделить тело человека от его души.
Показать ещё 1 комментарий
7
Марк Бесси уже сказал это, но это нужно еще раз подчеркнуть, пока не будет понято.

Указатель имеет столько же значение, что и переменная, чем буква 3.

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

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

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

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

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

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

  • 0
    +1 для назначения переменных.
7

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

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

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

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

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

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

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

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

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

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

  • 1
    Как правило, это ручка / ID. Обычно это простой адрес.
  • 0
    Я изменил свой ответ, чтобы быть немного более подходящим для ПК по определению Handle в Википедии. Мне нравится ссылаться на указатели как на конкретный экземпляр дескриптора, так как дескриптор может быть просто ссылкой на указатель.
5

КРАТКИЙ ОБЗОР (который я также поставил наверху):

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

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

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

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

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

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

ДЕТАЛЬ:

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

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

Fine.

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

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

У многих из нас есть мучительные воспоминания о старой сегментированной архитектуре Intel x86, с NEAR POINTERs и FAR POINTERS. К счастью, они почти исчезли, поэтому только краткое изложение: в реальном режиме в 16 бит фактический линейный адрес был

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

Если в защищенном режиме это может быть

LinearAddress = SegmentRegister[SegNum].base + offset

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

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

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

Существует много способов решения этой проблемы (проблемы, связанные с одиночным и множественным бездействием и виртуальным наследованием). Здесь, как компилятор Visual Studio решает его обработать: указатель на функцию-член многократно унаследованного класса на самом деле является структурой ". И они продолжают говорить:" Приведение указателя функции может изменить его размер!".

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

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

КРАТКИЙ ОБЗОР (который я также поставил наверху):

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

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

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

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

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

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

  • 0
    В 16-битном реальном режиме LinearAddress = SegmentRegister.Selector * 16 + Offset (время записи 16, а не смещение на 16). В защищенном режиме LinearAddress = SegmentRegister.base + offset (никакого умножения нет; база сегментов сохраняется в GDT / LDT и кэшируется в регистре сегментов как есть ).
  • 0
    Вы правы насчет реального режима. Я исправил свой пост.
Показать ещё 1 комментарий
4

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

  • 2
    Указатель не обязательно должен содержать в себе реальный адрес памяти. Смотрите мой ответ и комментарий под ним.
  • 0
    what .... указатель на первую переменную в стеке не печатает 0. он печатает верх (или низ) фрейма стека в зависимости от того, как он реализован.
Показать ещё 4 комментария
4

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

  • 0
    Итак, pointee - это адрес памяти? Вы не согласны с автором? Просто пытаюсь понять.
  • 0
    Основная функция указателя - указывать на что-то. Как именно это достигается и есть ли реальный адрес или нет, не определено. Указатель может быть просто идентификатором / дескриптором, а не реальным адресом.
3

Адрес используется для идентификации части хранилища фиксированного размера, обычно для каждого байта, как целого. Это точно называют байтовым адресом, который также используется ИСО С. Могут быть некоторые другие методы для построения адреса, например. для каждого бита. Однако часто используется только адрес байта, мы обычно опускаем "байт".

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

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

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

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

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

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

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

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

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

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

ISO C11 6.5.2.3

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

Примечание. Эта формулировка вводится WG14/N1256, то есть ISO C99: TC3. На C99 есть

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

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

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

ISO C11 6.6

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

ISO С++ 11 5.19

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

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

Фактически, как "постоянная адреса" в C, так и "постоянное выражение адреса" в С++ являются постоянным выражением типов указателей (или, по крайней мере, "типами указателей" с С++ 11).

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

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

3

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

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

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

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

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

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

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

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

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

  • 0
    Указатели NULL работают не так, как вы думаете, на всех платформах - см. Мой ответ на CiscoIPPhone выше. NULL == 0 - предположение, которое справедливо только для платформ на основе x86. Конвенция гласит, что новые платформы должны соответствовать x86, однако, особенно во встроенном мире, это не так. Изменить: Кроме того, C не делает ничего, чтобы абстрагировать значение указателя пути от аппаратного обеспечения - "ptr! = 0" не будет работать как тест NULL на платформе, где NULL! = 0.
  • 1
    DX-MON, это совершенно неверно для стандарта C. NULL имеет значение 0 и может использоваться взаимозаменяемо в выражениях. Независимо от того, является ли представление NULL-указателя в аппаратном обеспечении всеми 0 битами, не имеет значения, как оно представлено в исходном коде.
Показать ещё 9 комментариев
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 just `+= 1`
--: It just `-= 1`

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

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

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

  • 0
    Мне нравится ваше объяснение абстрактной реальности общего указателя в общей системе. Но, возможно, обсуждение памяти будет полезно. На самом деле, говоря от себя, я знаю, что это будет ...! Я думаю, что обсуждение связи может быть очень полезным для понимания общей картины. +1 в любом случае :)
  • 0
    @ d0rmLife: у вас достаточно объяснений в других ответах, которые охватывают общую картину. Я просто хотел дать математическое абстрактное объяснение в качестве другого взгляда. Также ИМХО, это вызвало бы меньше путаницы в вызове & как 'Address of`, так как это больше связано с объектом, чем с указателем как таковым`
Показать ещё 2 комментария
3

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

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

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

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

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

1

Другой способ, с помощью которого указатель 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

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

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
0

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

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

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

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

Авторская концепция адреса, конечно, также не является словом самого низкого уровня по этому вопросу. Что касается виртуальных карт памяти и физической адресации ОЗУ на нескольких микросхемах, то число, которое вы указываете процессору, - это "адрес магазина", который вы хотите получить, в основном не имеет никакого отношения к тому, где нужные данные на самом деле находятся в аппаратном обеспечении. Это все слои косвенности и представления, но автор выбрал для себя привилегию. Если вы собираетесь делать это, когда говорите о C, выберите уровень C для привилегий!

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

0

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

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

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

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

Итак, какой адрес в C? Это значение указателя, то есть значение определенного типа указателя. (За исключением того, что значение нулевого указателя не обязательно упоминается как "адрес", поскольку оно не является адресом чего-либо).

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню