Если printf является адресом переменной, зачем использовать void *? - PullRequest
11 голосов
/ 03 сентября 2011

Я видел использование (void*) в printf().

Если я хочу напечатать адрес переменной, могу ли я сделать это так:

int a = 19;
printf("%d", &a);
  1. Я думаю, &a - это адрес a, который является просто целым числом, верно?
  2. Во многих прочитанных мной статьях используется что-то вроде этого:

    printf("%p", (void*)&a);
    

  1. Что означает %p?(Указатель?)
  2. Зачем использовать (void*)?Разве я не могу использовать (int)&a вместо этого?

Ответы [ 3 ]

17 голосов
/ 03 сентября 2011

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

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

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

5 голосов
/ 03 сентября 2011

Здесь так много безумия ...

%p - это, как правило, правильный спецификатор формата, который нужно использовать, если вы просто хотите распечатать представление указателя.Никогда, никогда не используйте %d.

Длина int и длина указателя (void* или другое) имеют никакого отношения .Большинство моделей данных на i386 просто имеют 32-битные int s и 32-битные указатели - другие платформы, включая x86-64, не одинаковы!(Это также исторически известно как «синдром VAX всего мира».) http://en.wikipedia.org/wiki/64-bit#64-bit_data_models

Если по какой-то причине вы хотите сохранить адрес памяти в интегральной переменной, используйте правильные типы!intptr_t и uintptr_t.Они в stdint.h.См http://en.wikipedia.org/wiki/Stdint.h#Integers_wide_enough_to_hold_pointers

1 голос
/ 03 сентября 2011

В C void * - нетипизированный указатель. void не означает пустоту ... это значит что-нибудь. Таким образом, приведение к void * будет таким же, как приведение к «указателю» на другом языке.

Использование (int *)&a тоже должно сработать ... но стилистический смысл высказывания (void *) состоит в том, чтобы сказать - мне наплевать на тип - только то, что это указатель.

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

...