Prawidłowy specyfikator formatu do drukowania wskaźnika lub adresu?


194

Jakiego specyfikatora formatu powinienem użyć do wydrukowania adresu zmiennej? Jestem zdezorientowany między poniższą częścią.

% u - liczba całkowita bez znaku

% x - wartość szesnastkowa

% p - pusty wskaźnik

Jaki byłby optymalny format do wydrukowania adresu?

Odpowiedzi:


240

Najprostsza odpowiedź, przy założeniu, że nie przeszkadzają ci kaprysy i różnice w formacie między różnymi platformami, to standardowa %pnotacja.

Norma C99 (ISO / IEC 9899: 1999) mówi w §7.19.6.1 ¶8:

pArgument powinien być wskaźnikiem do void. Wartość wskaźnika jest konwertowana na ciąg znaków drukujących w sposób zdefiniowany w implementacji.

(W C11 - ISO / IEC 9899: 2011 - informacje podano w §7.21.6.1 ¶8.)

Na niektórych platformach, które będą zawierać wiodące, 0xa na innych nie, a litery mogą być pisane małymi lub dużymi literami, a standard C nawet nie definiuje, że będzie to wyjście szesnastkowe, chociaż wiem o brak implementacji tam, gdzie jej nie ma.

W pewnym sensie można dyskutować, czy należy jawnie przekonwertować wskaźniki za pomocą (void *)obsady. Jest to jawne, co zwykle jest dobre (tak właśnie robię), a standard mówi „argument powinien być wskaźnikiem do void”. Na większości komputerów można uniknąć pominięcia jawnej obsady. Miałoby to jednak znaczenie na komputerze, w którym bitowa reprezentacja char *adresu dla danej lokalizacji w pamięci różni się od adresu „ wskaźnika innego ” dla tej samej lokalizacji w pamięci. Byłaby to maszyna adresowana słowem, a nie bajtem. Takie maszyny nie są obecnie powszechne (prawdopodobnie niedostępne), ale pierwsza maszyna, nad którą pracowałem po studiach, była taka (ICL Perq).

Jeśli nie jesteś zadowolony z zachowania zdefiniowanego w implementacji %p, użyj C99 <inttypes.h>i uintptr_tzamiast tego:

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

Pozwala to na dostrojenie reprezentacji do własnych potrzeb. Wybrałem, aby cyfry szesnastkowe były pisane dużymi literami, aby liczba była równomiernie tej samej wysokości, a więc charakterystyczny spadek na początku 0xA1B2CDEFpojawia się, a nie tak, 0xa1b2cdefktóre spadają w górę i w dół wzdłuż liczby. Twój wybór jednak w bardzo szerokich granicach. (uintptr_t)Obsada jest jednoznacznie zalecane przez GCC kiedy można go odczytać ciąg formatu w czasie kompilacji. Wydaje mi się, że należy poprosić o obsadę, ale jestem pewien, że są tacy, którzy zignorowaliby ostrzeżenie i unikali go przez większość czasu.


Kerrek pyta w komentarzach:

Jestem trochę zdezorientowany standardowymi promocjami i argumentami variadic. Czy wszystkie wskaźniki są standardowo promowane do unieważnienia *? W przeciwnym razie, gdyby int*były powiedzmy dwa bajty, a void*były 4 bajty, to oczywistym błędem byłoby odczytanie czterech bajtów z argumentu, non?

Byłem w iluzji, że standard C mówi, że wszystkie wskaźniki obiekt musi być tej samej wielkości, więc void *i int *nie mogą mieć różne rozmiary. Jednak to, co uważam za odpowiednią sekcję standardu C99, nie jest tak wyraźne (chociaż nie znam implementacji, w której to, co zasugerowałem, jest prawdą, jest w rzeczywistości fałszywe):

§6.2.5 Typy

26 Wskaźnik do unieważnienia powinien mieć takie same wymagania dotyczące reprezentacji i wyrównania, jak wskaźnik do typu znaku. 39) Podobnie wskaźniki do kwalifikowanych lub niekwalifikowanych wersji kompatybilnych typów powinny mieć takie same wymagania dotyczące reprezentacji i wyrównania. Wszystkie wskaźniki do typów konstrukcji powinny mieć takie same wymagania dotyczące reprezentacji i wyrównania. Wszystkie wskaźniki do typów unii mają takie same wymagania dotyczące reprezentacji i wyrównania. Wskaźniki do innych typów nie muszą mieć takich samych wymagań dotyczących reprezentacji lub wyrównania.

39) Te same wymagania dotyczące reprezentacji i wyrównania mają oznaczać zamienność argumentów do funkcji, zwracanych wartości z funkcji i członków związków.

(C11 mówi dokładnie to samo w rozdziale §6.2.5, ¶28 i przypis 48.)

Tak więc wszystkie wskaźniki do struktur muszą mieć taki sam rozmiar i muszą mieć te same wymagania dotyczące wyrównania, nawet jeśli struktury wskazane przez wskaźniki mogą mieć różne wymagania dotyczące wyrównania. Podobnie w przypadku związków. Wskaźniki znaków i pustki muszą mieć ten sam rozmiar i wymagania dotyczące wyrównania. Wskaźniki wariantów int(znaczenie unsigned inti signed int) muszą mieć takie same wymagania co do wielkości i wyrównania; podobnie dla innych typów. Ale standard C nie mówi tego formalnie sizeof(int *) == sizeof(void *). No cóż, SO jest dobry do tego, abyś sprawdził swoje założenia.

Standard C definitywnie nie wymaga, aby wskaźniki funkcji były tego samego rozmiaru co wskaźniki obiektów. Było to konieczne, aby nie zepsuć różnych modeli pamięci w systemach podobnych do DOS. Tam możesz mieć 16-bitowe wskaźniki danych, ale 32-bitowe wskaźniki funkcji lub odwrotnie. To dlatego standard C nie wymaga, aby wskaźniki funkcji mogły być konwertowane na wskaźniki obiektów i odwrotnie.

Na szczęście (dla programistów atakujących POSIX) POSIX wkracza w błąd i nakazuje, aby wskaźniki funkcji i wskaźniki danych miały ten sam rozmiar:

§2.12.3 Typy wskaźników

Wszystkie typy wskaźników funkcji powinny mieć taką samą reprezentację jak wskaźnik typu, który ma zostać unieważniony. Konwersja wskaźnika funkcji na void *nie powinna zmieniać reprezentacji. void *Wartość wynikająca z takiego przekształcenia można przekształcić z powrotem do pierwotnego typu wskaźnik funkcji, za pomocą wyraźnego obsady, bez utraty informacji.

Uwaga: Norma ISO C nie wymaga tego, ale jest wymagana do zgodności z POSIX.

Wydaje się więc, że jawne rzuty void *są zdecydowanie wskazane dla maksymalnej niezawodności w kodzie podczas przekazywania wskaźnika do funkcji variadic, takiej jak printf(). W systemach POSIX można bezpiecznie umieścić wskaźnik funkcji na pustym wskaźniku w celu wydrukowania. W innych systemach niekoniecznie jest to bezpieczne, a także przekazywanie wskaźników innych niż void *bez obsady.


3
Jestem trochę zdezorientowany standardowymi promocjami i argumentami variadic. Czy wszystkie wskaźniki są standardowo promowane void*? W przeciwnym razie, gdyby int*były powiedzmy dwa bajty, a void*były 4 bajty, to oczywistym błędem byłoby odczytanie czterech bajtów z argumentu, non?
Kerrek SB

Zauważ, że aktualizacja POSIX (POSIX 2013) usunęła sekcję 2.12.3, przenosząc większość wymagań do dlsym()funkcji. Pewnego dnia napiszę zmianę ... ale „jeden dzień” to nie „dziś”.
Jonathan Leffler

Czy ta odpowiedź dotyczy również wskaźników funkcji? Czy można je przekonwertować void *? Hmm, widzę twój komentarz tutaj . Ponieważ potrzebna jest tylko konwersja jednego wata (wskaźnik funkcji do void *), to działa?
chux - Przywróć Monikę

@chux: Dokładnie odpowiedź brzmi „nie”, ale w praktyce odpowiedź brzmi „tak”. Standard C nie gwarantuje, że wskaźniki funkcji mogą zostać przekonwertowane na void *ai wstecz bez utraty informacji. Pragmatycznie jest bardzo mało maszyn, w których rozmiar wskaźnika funkcji nie jest taki sam jak rozmiar wskaźnika obiektu. Nie sądzę, że standard zapewnia metodę drukowania wskaźnika funkcji na komputerach, na których konwersja jest problematyczna.
Jonathan Leffler

„i powrót bez utraty informacji” nie dotyczy drukowania. To pomaga?
chux - Przywróć Monikę

50

pjest specyfikatorem konwersji do drukowania wskaźników. Użyj tego.

int a = 42;

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

Pamiętaj, że pominięcie rzutowania jest nieokreślonym zachowaniem, a drukowanie ze pspecyfikatorem konwersji odbywa się w sposób zdefiniowany w implementacji.


2
Przepraszam, dlaczego pominięcie obsady to „niezdefiniowane zachowanie”? Czy ma to znaczenie dla jakiej zmiennej to jest, jeśli wszystko, czego potrzebujesz, to adres, a nie wartość?
valdo

9
@valdo, ponieważ C to mówi (C99, 7.19.6.1p8) „p Argument powinien być wskaźnikiem do unieważnienia”.
ouah

12
@valdo: Nie zawsze jest tak, że wszystkie wskaźniki mają ten sam rozmiar / reprezentację.
caf

32

Użyj %p, jako „wskaźnik” i nie używaj niczego innego *. Standard nie gwarantuje, że możesz traktować wskaźnik jak jakikolwiek konkretny typ liczb całkowitych, więc w rzeczywistości uzyskasz niezdefiniowane zachowanie dzięki formatom integralnym. (Na przykład %uoczekuje unsigned int, ale co, jeśli void*ma inny rozmiar lub wymaganie wyrównania niż unsigned int?)

*) [Patrz Jonathana porządku odpowiedź!] Alternatywnie %p, to może korzystać z makr wskaźnika od specyficznych <inttypes.h>, dodane w C99.

Wszystkie wskaźniki obiektów są domyślnie konwertowalne na void*C, ale aby przekazać wskaźnik jako argument variadic, musisz rzucić go jawnie (ponieważ dowolne wskaźniki obiektów są konwertowalne , ale nie identyczne jak wskaźniki void):

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

2
Wszystkie wskaźniki obiektów są konwertowalne void *(chociaż dla printf()ciebie technicznie potrzebujesz jawnego rzutowania, ponieważ jest to funkcja wariadyczna). Wskaźniki funkcji niekoniecznie są konwertowalne void *.
caf

@caf: Oh, nie wiedziałem o argumentach variadic - naprawione! Dzięki!
Kerrek SB

2
Standard C nie wymaga, aby wskaźniki funkcji były konwertowane na void *wskaźnik funkcji i wracały do ​​wskaźnika funkcji bez utraty; na szczęście POSIX wyraźnie tego wymaga (zauważając, że nie jest to część standardu C). Tak więc w praktyce możesz sobie z tym poradzić (konwersja void (*function)(void)do void *i powrót do void (*function)(void)), ale ściśle nie jest to wymagane przez standard C.
Jonathan Leffler

2
Jonathan i R .: To wszystko jest bardzo interesujące, ale jestem pewien, że nie próbujemy tutaj drukować wskaźników funkcji, więc być może nie jest to odpowiednie miejsce do dyskusji na ten temat. Wolałbym tutaj znaleźć poparcie dla mojego nalegania, aby nie korzystać %u!
Kerrek SB

2
%ui %lusą błędne na wszystkich komputerach , nie na niektórych komputerach. Specyfikacja printfjest bardzo jasna, że ​​gdy przekazany typ nie odpowiada typowi wymaganemu przez specyfikator formatu, zachowanie jest niezdefiniowane. Nie ma znaczenia, czy rozmiar typów pasuje (który może być prawdziwy, czy fałszywy, w zależności od maszyny); to typy, które muszą pasować i nigdy nie będą.
R .. GitHub ZATRZYMAJ LÓD

9

Jako alternatywę dla innych (bardzo dobrych) odpowiedzi można rzutować na uintptr_tlub intptr_tz stdint.h/ inttypes.hi używać odpowiednich specyfikatorów konwersji liczb całkowitych. Umożliwiłoby to większą elastyczność w sposobie formatowania wskaźnika, ale ściśle mówiąc, implementacja nie jest wymagana do zapewnienia tych typów czcionek.


rozważyć, #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } czy drukowanie adresu zmiennej przy użyciu %uspecyfikatora formatu jest niezdefiniowanym zachowaniem ? Adres zmiennej w większości przypadków jest dodatni, więc czy mogę użyć %uzamiast %p?
Destructor,

1
@Destructor: Nie, %ujest formatem unsigned inttypu i nie można go używać z argumentem wskaźnika do printf.
R .. GitHub ZATRZYMAJ LÓD

-1

Możesz użyć %xlub %Xlub %p; wszystkie są poprawne.

  • Jeśli używasz %x, adres jest podawany małymi literami, na przykład:a3bfbc4
  • Jeśli używasz %X, adres jest podawany wielkimi literami, na przykład:A3BFBC4

Oba są poprawne.

Jeśli używasz %xlub %Xrozważa sześć pozycji dla adresu, a jeśli używasz, bierze %ppod uwagę osiem pozycji dla adresu. Na przykład:


1
Witamy w SO. Poświęć trochę czasu na zapoznanie się z innymi odpowiedziami, które wyraźnie wyjaśniają niektóre szczegóły, które przeoczysz.
AntoineL
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.