Правильний специфікатор формату для друку вказівника чи адреси?


194

Який специфікатор формату я повинен використовувати для друку адреси змінної? Я плутаюсь між нижченаведеною партією.

% u - непідписане ціле число

% x - шістнадцяткове значення

% p - недійсний покажчик

Який оптимальний формат для друку адреси?

Відповіді:


240

Найпростіша відповідь, якщо припустити, що ви не заперечуєте проти капризів та змін у форматі між різними платформами, - це стандартне %pпозначення.

Стандарт C99 (ISO / IEC 9899: 1999) говориться в § 7.19.6.1 ¶8:

pАргумент повинен бути вказівником на void. Значення вказівника перетворюється на послідовність друку символів у визначеному реалізацією способі.

(В C11 - ISO / IEC 9899: 2011 - інформація наведена в § 7.21.6.1 ¶8.)

На деяких платформах це буде включати провідне, 0xа на інших - не, а літери можуть бути малі або великі, а стандарт C навіть не визначає, що він повинен бути шістнадцятковим виведенням, хоча я знаю жодної реалізації там, де її немає.

Дещо відкриті для дискусії про те, чи слід явно перетворювати покажчики в (void *)лінійку. Це явне, що, як правило, добре (так це я і роблю), а стандарт говорить, що "аргумент має бути вказівником на void". На більшості машин ви б уникнули, якщо пропустити чіткий склад. Однак, це мало б значення для машини, де бітове представлення char *адреси для певного місця пам'яті відрізняється від адреси " все інше вказівника " для того самого місця пам'яті. Це буде машина, адресована словом, а не байтом. Такі машини не часто (мабуть, не доступні) в наші дні, але перша машина, над якою я працював після університету, була однією з таких (ICL Perq).

Якщо ви не задоволені визначеною реалізацією поведінкою %p, використовуйте C99 <inttypes.h>і uintptr_tзамість цього:

printf("0x%" PRIXPTR "\n", (uintptr_t)your_pointer);

Це дозволяє точно налаштувати представництво під себе. Я вирішив мати шістнадцяткові цифри у верхньому регістрі, щоб число рівномірно було однаковим по висоті, а характерне опускання на початку 0xA1B2CDEFз'являється таким чином, не так, як 0xa1b2cdefопускається вгору і вниз уздовж числа. Ваш вибір, проте, в дуже широких межах. (uintptr_t)За нке литим однозначно рекомендуються , коли він може читати формат рядок під час компіляції. Я вважаю, що правильно запитувати виступ, хоча я впевнений, що є такі, хто б ігнорував попередження і пішов з ним більшу частину часу.


Керрек питає в коментарях:

Я трохи розгублений щодо стандартних акцій та різноманітних аргументів. Чи всі покажчики стандартно підвищуються до недійсних *? Інакше, якби int*, скажімо, були два байти і void*були 4 байти, то явно було б помилкою прочитати чотири байти з аргументу, чи не так?

Я був під ілюзією , що стандарт C каже , що всі покажчики об'єктів повинні бути однакового розміру, тому void *і int *не може бути різних розмірів. Однак те, що, на мою думку, є відповідним розділом стандарту C99, не настільки наполегливе (хоча я не знаю про реалізацію, де те, що я запропонував, є дійсно хибним):

§6.2.5 Види

¶26 Покажчик недійсного має відповідати вимогам представлення та вирівнювання, як і вказівник на тип символів. 39) Аналогічно, покажчики на кваліфіковані або некваліфіковані версії сумісних типів повинні мати однакові вимоги щодо представлення та вирівнювання. Усі вказівники на типи структур мають однакові вимоги щодо представлення та вирівнювання, як один до одного. Усі вказівники на типи об'єднання мають однакові вимоги щодо представлення та вирівнювання, як один до одного. Покажчики інших типів не повинні мати однакових вимог щодо представлення чи вирівнювання.

39) Ті ж вимоги щодо представлення та вирівнювання мають на увазі взаємозамінність як аргументи до функцій, повернення значень функцій та членів союзів.

(C11 говорить точно так само в розділі §6.2.5, ¶28 та виносці 48.)

Отже, всі вказівники на структури повинні бути однакового розміру, і вони повинні мати однакові вимоги до вирівнювання, хоча структури, на які вказують вказівники, можуть мати різні вимоги до вирівнювання. Аналогічно і для спілок. Покажчики символів та недійсні покажчики повинні мати однакові вимоги щодо розміру та вирівнювання. Покажчики на варіації на int(значення unsigned intта signed int) повинні мати однакові вимоги до розміру та вирівнювання, як один до одного; аналогічно для інших типів. Але стандарт С офіційно цього не говорить sizeof(int *) == sizeof(void *). Ну добре, так добре, щоб змусити вас перевірити свої припущення.

Останнє стандарт C не вимагає, щоб покажчики функцій були такого ж розміру, як вказівники об'єкта. Це було необхідно, щоб не порушувати різні моделі пам’яті на DOS-подібних системах. Там ви можете мати 16-бітні покажчики даних, але 32-бітні функціональні вказівники, або навпаки. Ось чому стандарт C не передбачає, що функціональні вказівники можуть бути перетворені в об'єктні покажчики і навпаки.

На щастя (для програмістів, націлених на POSIX), POSIX вступає в порушення та виконує мандат, що покажчики та покажчики даних мають однаковий розмір:

§2.12.3 Типи вказівників

Усі типи вказівників функцій повинні мати те саме представлення, що і покажчик типу на недійсні. Перетворення покажчика функції в void *не повинно змінювати подання. void *Значення в результаті такого перетворення може бути перетворена назад у вихідний тип покажчика функції, використовуючи явне приведення, без втрати інформації.

Примітка: Стандарт ISO C цього не вимагає, але він необхідний для відповідності POSIX.

Отже, здається, що явні касти void *настійно доцільні для досягнення максимальної надійності коду при передачі покажчика на різноманітну функцію, таку як printf(). У системах POSIX можна безпечно вводити покажчик функції на недійсний покажчик для друку. В інших системах це не обов'язково безпечно, а також не обов'язково безпечно передавати вказівники, окрім як void *без черги.


3
Я трохи розгублений щодо стандартних акцій та різноманітних аргументів. Чи всі вказівники отримують стандартну рекламу void*? Інакше, якби int*, скажімо, були два байти і void*були 4 байти, то явно було б помилкою прочитати чотири байти з аргументу, чи не так?
Керрек СБ

Зауважте, що оновлення POSIX (POSIX 2013) видалило розділ 2.12.3, перемістивши натомість більшість вимог до dlsym()функції. Одного разу я напишу зміну ... але "один день" - це не "сьогодні".
Джонатан Леффлер

Чи стосується ця відповідь також покажчиків на функції? Чи можна їх перетворити void *? Хм, я бачу ваш коментар тут . Оскільки потрібна лише конверсія на один ват (функція вказівника на void *), то вона працює?
chux

@chux: Суворо, відповідь "ні", але на практиці відповідь "так". Стандарт C не гарантує, що функціональні вказівники можуть бути перетворені на a void *і назад без втрати інформації. Прагматично, дуже мало машин, де розмір вказівника функції не такий, як розмір вказівника об'єкта. Я не думаю, що стандарт пропонує метод друку вказівника функції на машинах, де перетворення проблематично.
Джонатан Леффлер

"і назад без втрати інформації" не стосується друку. Чи допомагає це?
chux

50

pє специфікатором перетворення для покажчиків друку. Використовуй це.

int a = 42;

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

Пам’ятайте, що пропускати трансляцію - це невизначена поведінка і що друк із pспецифікатором перетворення виконується певним чином.


2
Пробачте, чому пропускати акторський склад - це "невизначена поведінка"? Чи має значення ця адреса, для якої змінної вона є, якщо все, що вам потрібно, це адреса, а не значення?
valdo

9
@valdo тому, що C каже це (C99, 7.19.6.1p8) "p Аргумент повинен бути покажчиком на недійсне".
оуа

12
@valdo: Не обов’язково так, що всі вказівники мають однаковий розмір / представлення.
caf

32

Використовуйте %pдля "вказівника" і не використовуйте нічого іншого *. Ви не гарантуєте стандартом, що вам дозволено трактувати покажчик, як будь-який конкретний тип цілого числа, так що ви фактично отримаєте не визначене поведінку з цілісними форматами. (Наприклад, %uочікує unsigned int, але що робити, якщо void*вимоги щодо розміру чи вирівнювання мають інший розмір, ніж unsigned int?)

*) [Див. Точну відповідь Джонатана!] Альтернативно %p, ви можете використовувати макроси, орієнтовані на вказівники <inttypes.h>, додані в C99.

Усі вказівники об'єкта неявно перетворюються void*в C, але для того, щоб передати вказівник як різноманітний аргумент, його потрібно чітко надати (оскільки довільні об’єкти покажчики є лише конвертованими , але не ідентичними покажчикам недійсних):

printf("x lives at %p.\n", (void*)&x);

2
Усі вказівники об'єкта можуть бути конвертовані void *(хоча для printf()вас технічно потрібен чіткий склад, оскільки це варіативна функція). Покажчики функцій не обов'язково перетворюються на void *.
caf

@caf: О, я не знав про різні аргументи - виправлено! Дякую!
Керрек СБ

2
Стандарт C не вимагає, щоб покажчики функцій перетворювались void *на функціональний вказівник та поверталися до нього без втрат; на щастя, однак POSIX цього прямо вимагає (зазначивши, що він не є частиною стандарту C). Так, на практиці ви можете піти від цього (перетворюючись void (*function)(void)на void *та назад void (*function)(void)), але суворо це не передбачено стандартом С.
Джонатан Леффлер

2
Джонатан і Р .: Це все дуже цікаво, але я впевнений, що ми не намагаємося надрукувати тут покажчики функцій, тому, можливо, це не зовсім правильне місце для обговорення цього питання. Я набагато швидше побачив би якусь підтримку, щоб моє наполягання не використовувати %u!
Керрек СБ

2
%uі %luпомиляються на всіх машинах , а не на деяких машинах. У специфікації printfдуже зрозуміло, що коли тип, що передається, не відповідає типу, який вимагає специфікатор формату, поведінка не визначена. Не має значення розмір відповідності типів (який може бути правдивим чи хибним, залежно від машини); це типи, які повинні відповідати, і вони ніколи не будуть.
R .. GitHub СТОП ДОПОМОГАТИ ДВІ

9

В якості альтернативи іншим (дуже хорошим) відповідям, ви можете подати на ( uintptr_tабо intptr_tвід stdint.h/ inttypes.h) і використовувати відповідні цілі специфікатори перетворення. Це дозволило б забезпечити більшу гнучкість у форматуванні покажчика, але строго кажучи, для надання цих типівdede не потрібно виконання.


вважаєте, #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } чи не визначена поведінка надрукувати адресу змінної за допомогою %uспецифікатора формату? Адреса змінної у більшості випадків позитивна, тому я можу використовувати %uзамість %p?
Руйнівник

1
@Destructor: Ні, %uце формат для unsigned intтипу і його не можна використовувати з аргументом вказівника на printf.
R .. GitHub СТОП ДОПОМОГАТИ ДВІ

-1

Ви можете використовувати %xабо %Xабо %p; всі вони правильні.

  • Якщо ви використовуєте %x, адреса задається як малі, наприклад:a3bfbc4
  • Якщо ви використовуєте %X, адреса задається як великі регістри, наприклад:A3BFBC4

І те й інше правильно.

Якщо ви використовуєте %xабо %Xрозглядаєте шість позицій для адреси, а якщо ви використовуєте, %pце розглядає вісім позицій для адреси. Наприклад:


1
Ласкаво просимо до SO. Будь ласка, знайдіть трохи часу, щоб переглянути інші відповіді, вони чітко пояснюють ряд деталей, які ви не помічаєте.
AntoineL
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.