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

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

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

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

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

PS

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

25 ответов

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

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

Я не уверен в вашем источнике, но тип языка, который вы описываете, исходит из стандарта 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. Целое число может быть преобразовано в любой тип указателя. За исключением случаев, указанных ранее, результат определяется реализацией, может быть неправильно выровнен, может не указывать на объект ссылочного типа и может быть представлением прерывания.

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

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

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

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

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

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

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

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

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

  • адрес объекта, который может быть разыменован (если p содержит адрес x тогда выражение *p имеет то же значение, что и x);
  • нулевой указатель, из которых 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? (Большой обратный порядок байт). Но на моем 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 изменения. Программа разыменовывает два указателя на один и тот же адрес и получает два разных значения!

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

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

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

Например:

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;
}

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

Судя по всем написанным ответам, некоторые люди предполагают, что (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)).

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

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

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

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

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

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

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

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

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

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

Указатель имеет столько же общего с переменной, сколько с литералом 3.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ДЕТАЛЬ:

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

На многих, возможно, на большинстве компиляторов C указатель на данные типа 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

с результирующим адресом, проверяемым по ограничению, установленному в сегменте. Некоторые программы использовали не совсем стандартные C/C++ FAR и NEAR объявления указателей, но многие просто сказали *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, с безопасными "жирными указателями".

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

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

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

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

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

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

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

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

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

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

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

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

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

Адрес используется для идентификации части памяти фиксированного размера, обычно для каждого байта, как целое число. Это точно называется байтовым адресом, который также используется 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 типа указателя, которое вычисляется по адресу объекта со статической продолжительностью хранения, по адресу функции или по нулевому значению указателя, или по выражению ядра постоянной константы. типа std::nullptr_t,...

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

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

И встроенный одинарный & оператор называется "адрес-адрес" в C и C++;, подобным образом, std::addressof вводится в C++11.

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

Прежде чем понять указатели, мы должны понять объекты. Объекты - это сущности, которые существуют и имеют спецификатор местоположения, называемый адресом. Указатель - это просто переменная, как и любые другие переменные в 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`

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

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

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

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

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

Еще один способ отличия указателя на 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* возможно (хотя и не очень портативно).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам