Указатели не являются числами. Они часто внутренне представлены таким образом, но они концептуально различны.
void*
предназначен для общего типа указателя. Любое значение указателя (кроме указателя на функцию) может быть преобразовано в void*
и обратно без потери информации. Обычно это означает, что void*
по крайней мере такой же большой, как и другие типы указателей.
printf
с "%p"
формат требует аргумент типа void*
. Вот почему int*
должен быть приведен к void*
в этом контексте. (Нет неявного преобразования, потому что это переменная функция; объявленного параметра нет, поэтому компилятор не знает, во что его преобразовать.)
Неаккуратные практики, такие как печать указателей с "%d"
или передача int*
в printf
с форматом "%p"
, - это вещи, которые вы, вероятно, можете избежать на большинстве современных систем, но они делают ваш код не -portable. (Обратите внимание, что в 64-битных системах void*
и int
обычно бывают разных размеров, поэтому печать указателей с %d"
действительно непереносима, а не только теоретически.)
Кстати, выходной формат для "%p"
определяется реализацией. Шестнадцатеричный является обычным (в верхнем или нижнем регистре, с или без ведущего "0x"
или "0X"
), но это не единственная возможность. Все, на что вы можете рассчитывать, это то, что при разумной реализации это будет разумный способ представить значение указателя в удобочитаемой форме (и что scanf
будет понимать вывод printf
).
Статья, которую вы прочитали, полностью верна. Правильный способ печати значения int*
-
printf("%p", (void*)&a);
Не выходи ленивым; совсем нетрудно понять это правильно.
Рекомендуемое прочтение: Раздел 4 comp.lang.c FAQ . (Далее предлагается прочесть: все остальные разделы.
EDIT:
В ответ на вопрос Олкотта:
Есть еще одна вещь, которую я не совсем понимаю. int a = 10; int *p = &a;
, так что значение p это адрес a в mem, верно? Если верно, то значение p будет в диапазоне от 0 до 2 ^ 32-1 (если 32-битный процессор), а целое число - 4 байта в 32-битной ОС, верно? тогда в чем разница между значением p и целым числом? Может ли значение p выйти за пределы диапазона?
Разница в том, что они бывают разных типов.
Предположим, что в системе int
, int*
, void*
и float
- все 32 бита (это типично для современных 32-битных систем). Означает ли тот факт, что float
- 32 бита, означает, что его диапазон составляет от 0 до 2 32 -1? Или от -2 31 до 2 31 -1? Конечно, нет; диапазон с плавающей точкой (при условии представления IEEE) составляет приблизительно от -3,40282e + 38 до + 3,40282e + 38, с широко варьирующимся разрешением по всему диапазону, плюс экзотические значения, такие как отрицательный ноль, субнормализованные числа, денормализованные числа, бесконечности и NaN (не -число). int
и float
- 32 бита, и вы можете взять 32 бита объекта float
и трактовать его как представление int
, но результат не будет простым отношение к значению float
. Например, второй младший бит int
имеет особое значение; оно добавляет 0 к значению, если оно равно 0, и 2 к значению, если оно равно 1; соответствующий бит float
имеет значение, но он совсем другой (он дает значение, зависящее от значения показателя степени).
Ситуация с указателями очень похожа. Значение указателя имеет значение: это адрес какого-то объекта (или любой другой вещи, но мы пока отложим это). В большинстве современных систем интерпретация битов объекта-указателя как целочисленного дает вам что-то, что имеет смысл на уровне машины. Но сам язык не гарантирует и даже не намекает, что это так.
Указатели не являются числами.
Конкретный пример: несколько лет назад я наткнулся на некоторый код, который пытался вычислить разницу в байтах между двумя адресами путем приведения к целым числам.Это было примерно так:
unsigned char *p0;
unsigned char *p1;
long difference = (unsigned long)p1 - (unsigned long)p0;
Если вы предполагаете, что указатели - это просто числа, представляющие адреса в линейном монолитном адресном пространстве, то этот код имеет смысл.Но это предположение не поддерживается языком.И действительно, была система, в которой этот код предназначался для запуска (Cray T90), на которой он просто не работал бы.T90 имел 64-битные указатели, указывающие на 64-битные слова.Байтовые указатели были синтезированы в программном обеспечении путем сохранения смещения в 3 старших битах объекта указателя.Вычитание двух указателей указанным выше способом, если они оба имеют смещение 0, даст вам количество слов , а не байтов, между адресами.И если бы они имели смещения, отличные от 0, это дало бы вам бессмысленный мусор.(Преобразование из указателя в целое число просто скопировало бы биты; оно могло бы выполнить работу, чтобы дать вам значимый байтовый индекс, но этого не произошло.)
Решение былопросто: отбросьте приведения и используйте арифметику указателя:
long difference = p1 - p0;
Возможны другие схемы адресации.Например, адрес может состоять из дескриптора, который (возможно, косвенно) ссылается на блок памяти, плюс смещение в этом блоке.
Можно предположить, что адреса - это просто числа, что пространство адресов является линейным иМонолитно, что все указатели имеют одинаковый размер и имеют одинаковое представление, что указатель можно безопасно преобразовать в int
или long
и обратно без потери информации.И код, который вы пишете на основе этих предположений, вероятно, будет работать на большинстве современных систем.Но вполне возможно, что некоторые будущие системы снова будут использовать другую модель памяти, и ваш код сломается.
Если вы избежите каких-либо предположений, выходящих за рамки того, что язык фактически гарантирует, ваш код будет гораздо более ориентирован на будущее,И даже если оставить в стороне проблемы с переносимостью, это, вероятно, будет чище.