Richtiger Formatbezeichner zum Drucken von Zeiger oder Adresse?

192

Welchen Formatbezeichner soll ich verwenden, um die Adresse einer Variablen zu drucken? Ich bin verwirrt zwischen dem folgenden Los.

% u - Ganzzahl ohne Vorzeichen

% x - Hexadezimalwert

% p - ungültiger Zeiger

Welches wäre das optimale Format zum Drucken einer Adresse?

San
quelle

Antworten:

239

Die einfachste Antwort ist die Standardnotation, vorausgesetzt, Sie haben nichts gegen die Unklarheiten und Formatunterschiede zwischen verschiedenen Plattformen %p.

Die C99-Norm (ISO / IEC 9899: 1999) besagt in §7.19.6.1 ¶8:

pDas Argument soll ein Zeiger auf sein void. Der Wert des Zeigers wird implementierungsdefiniert in eine Folge von Druckzeichen konvertiert.

(In C11 - ISO / IEC 9899: 2011 - sind die Informationen in §7.21.6.1 ¶8 enthalten.)

Auf einigen Plattformen wird dies ein führendes 0xund auf anderen nicht einschließen , und die Buchstaben können in Klein- oder Großbuchstaben geschrieben sein, und der C-Standard definiert nicht einmal, dass es sich um eine hexadezimale Ausgabe handelt, obwohl ich weiß Keine Implementierung, wo es nicht ist.

Es ist etwas offen zu diskutieren, ob Sie die Zeiger explizit mit einer (void *)Besetzung konvertieren sollten . Es ist explizit, was normalerweise gut ist (so ist es, was ich tue), und der Standard sagt, dass das Argument ein Zeiger auf sein soll void. Auf den meisten Maschinen würden Sie eine explizite Besetzung weglassen. Auf einer Maschine, auf der sich die Bitdarstellung einer char *Adresse für einen bestimmten Speicherort von dem Zeiger " Alles andere" unterscheidet, wäre dies jedoch von Bedeutung Adresse " für denselben Speicherort unterscheidet. Dies wäre eine Maschine mit Wortadresse anstelle einer Maschine mit Byteadressierung. Solche Maschinen sind heutzutage nicht üblich (wahrscheinlich nicht verfügbar), aber die erste Maschine, an der ich nach dem Studium gearbeitet habe, war eine solche (ICL Perq).

Wenn Sie mit dem implementierungsdefinierten Verhalten von nicht zufrieden sind %p, verwenden Sie C99 <inttypes.h>und uintptr_tstattdessen:

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

Auf diese Weise können Sie die Darstellung an Ihre Bedürfnisse anpassen. Ich habe mich dafür entschieden, die hexadezimalen Ziffern in Großbuchstaben zu schreiben, damit die Zahl gleichmäßig gleich hoch ist und die charakteristische Neigung zu Beginn so 0xA1B2CDEFerscheint, nicht wie 0xa1b2cdefdie, die auch entlang der Zahl auf und ab fällt . Ihre Wahl jedoch in sehr weiten Grenzen. Die (uintptr_t)Besetzung wird von GCC eindeutig empfohlen, wenn die Formatzeichenfolge zur Kompilierungszeit gelesen werden kann. Ich denke, es ist richtig, die Besetzung anzufordern, obwohl ich sicher bin, dass es einige gibt, die die Warnung ignorieren und die meiste Zeit damit durchkommen.


Kerrek fragt in den Kommentaren:

Ich bin etwas verwirrt über Standardaktionen und verschiedene Argumente. Werden alle Zeiger standardmäßig auf void * hochgestuft? Andernfalls, wenn beispielsweise int*zwei Bytes und void*4 Bytes wären , wäre es eindeutig ein Fehler, vier Bytes aus dem Argument zu lesen, nicht?

Ich war in der Illusion , dass der C - Standard sagt , dass alle Objektzeiger müssen gleich groß sein, so void *und int *nicht unterschiedlich groß sein können. Was ich jedoch für den relevanten Abschnitt des C99-Standards halte, ist nicht so nachdrücklich (obwohl ich keine Implementierung kenne, bei der das, was ich vorgeschlagen habe, wahr ist, tatsächlich falsch ist):

§6.2.5 Typen

¶26 Ein Zeiger auf void muss dieselben Darstellungs- und Ausrichtungsanforderungen haben wie ein Zeiger auf einen Zeichentyp. 39) Ebenso müssen Zeiger auf qualifizierte oder nicht qualifizierte Versionen kompatibler Typen dieselben Darstellungs- und Ausrichtungsanforderungen haben. Alle Zeiger auf Strukturtypen müssen dieselben Darstellungs- und Ausrichtungsanforderungen haben. Alle Zeiger auf Vereinigungstypen müssen dieselben Darstellungs- und Ausrichtungsanforderungen haben. Zeiger auf andere Typen müssen nicht dieselben Darstellungs- oder Ausrichtungsanforderungen haben.

39) Dieselben Darstellungs- und Ausrichtungsanforderungen sollen Austauschbarkeit als Argumente für Funktionen, Rückgabewerte von Funktionen und Gewerkschaftsmitglieder implizieren.

(C11 sagt genau dasselbe in Abschnitt §6.2.5, ¶28 und Fußnote 48.)

Daher müssen alle Zeiger auf Strukturen dieselbe Größe haben und dieselben Ausrichtungsanforderungen haben, auch wenn die Strukturen, auf die die Zeiger zeigen, unterschiedliche Ausrichtungsanforderungen haben können. Ähnliches gilt für Gewerkschaften. Zeichenzeiger und Leerzeiger müssen die gleichen Anforderungen an Größe und Ausrichtung haben. Zeiger auf Variationen von int(Bedeutung unsigned intund signed int) müssen die gleichen Größen- und Ausrichtungsanforderungen haben; ähnlich für andere Typen. Aber der C-Standard sagt das formal nicht sizeof(int *) == sizeof(void *). Na ja, SO ist gut dafür, dass Sie Ihre Annahmen überprüfen.

Der C-Standard verlangt definitiv nicht, dass Funktionszeiger dieselbe Größe wie Objektzeiger haben. Dies war notwendig, um die verschiedenen Speichermodelle auf DOS-ähnlichen Systemen nicht zu beschädigen. Dort könnten Sie 16-Bit-Datenzeiger, aber 32-Bit-Funktionszeiger haben oder umgekehrt. Aus diesem Grund schreibt der C-Standard nicht vor, dass Funktionszeiger in Objektzeiger konvertiert werden können und umgekehrt.

Glücklicherweise (für Programmierer, die auf POSIX abzielen) tritt POSIX in die Sicherheitslücke ein und schreibt vor, dass Funktionszeiger und Datenzeiger dieselbe Größe haben:

§2.12.3 Zeigertypen

Alle Funktionszeigertypen müssen dieselbe Darstellung haben wie der Typzeiger für ungültig. Die Konvertierung eines Funktionszeigers in void *darf die Darstellung nicht verändern. Ein void *Wert, der sich aus einer solchen Konvertierung ergibt, kann ohne Informationsverlust mithilfe einer expliziten Umwandlung wieder in den ursprünglichen Funktionszeigertyp konvertiert werden.

Hinweis: Der ISO C-Standard verlangt dies nicht, ist jedoch für die POSIX-Konformität erforderlich.

Es scheint also, dass explizite Casts void *für eine maximale Zuverlässigkeit des Codes dringend empfohlen werden, wenn ein Zeiger auf eine variable Funktion wie z printf(). Auf POSIX-Systemen ist es sicher, einen Funktionszeiger zum Drucken in einen ungültigen Zeiger umzuwandeln. Auf anderen Systemen ist dies nicht unbedingt sicher, und es ist auch nicht unbedingt sicher, andere Zeiger als void *ohne Besetzung zu übergeben.

Jonathan Leffler
quelle
3
Ich bin etwas verwirrt über Standardaktionen und verschiedene Argumente. Werden alle Zeiger standardmäßig befördert void*? Andernfalls, wenn beispielsweise int*zwei Bytes und void*4 Bytes wären , wäre es eindeutig ein Fehler, vier Bytes aus dem Argument zu lesen, nicht?
Kerrek SB
Beachten Sie, dass durch ein Update auf POSIX (POSIX 2013) Abschnitt 2.12.3 entfernt wurde und die meisten Anforderungen dlsym()stattdessen auf die Funktion verschoben wurden . Eines Tages werde ich die Änderung aufschreiben ... aber "eines Tages" ist nicht "heute".
Jonathan Leffler
Gilt diese Antwort auch für Zeiger auf Funktionen? Können sie konvertiert werden void *? Hmm ich sehe deinen Kommentar hier . Da nur eine Ein-Watt-Konvertierung benötigt wird (Funktionszeiger auf void *), funktioniert es dann?
chux
@chux: Streng genommen lautet die Antwort "Nein", aber in der Praxis lautet die Antwort "Ja". Der C-Standard garantiert nicht, dass Funktionszeiger void *ohne Informationsverlust in ein und zurück konvertiert werden können. Pragmatisch gesehen gibt es nur sehr wenige Maschinen, bei denen die Größe eines Funktionszeigers nicht mit der Größe eines Objektzeigers übereinstimmt. Ich glaube nicht, dass der Standard eine Methode zum Drucken eines Funktionszeigers auf Maschinen bietet, auf denen die Konvertierung problematisch ist.
Jonathan Leffler
"und zurück ohne Informationsverlust" ist für den Druck nicht relevant. Hilft das?
chux
50

pist der Konvertierungsspezifizierer zum Drucken von Zeigern. Benutze das.

int a = 42;

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

Denken Sie daran, dass das Weglassen der pUmwandlung ein undefiniertes Verhalten ist und dass das Drucken mit dem Konvertierungsspezifizierer auf implementierungsdefinierte Weise erfolgt.

ouah
quelle
2
Verzeihung, warum ist das Auslassen der Besetzung "undefiniertes Verhalten"? Ist es wichtig, welche Adresse es ist, wenn Sie nur die Adresse und nicht den Wert benötigen?
Valdo
9
@valdo, weil C es sagt (C99, 7.19.6.1p8) "p Das Argument soll ein Zeiger auf void sein."
Ouah
12
@valdo: Es ist nicht unbedingt so, dass alle Zeiger die gleiche Größe / Darstellung haben.
Café
32

Verwenden Sie %pfür "Zeiger" und verwenden Sie nichts anderes *. Der Standard garantiert nicht, dass Sie einen Zeiger wie eine bestimmte Art von Ganzzahl behandeln dürfen, sodass Sie mit den Integralformaten tatsächlich ein undefiniertes Verhalten erhalten. (Erwartet zum Beispiel %ueine unsigned int, aber was ist, wenn void*eine andere Größe oder Ausrichtung erforderlich ist als unsigned int?)

*) [Siehe Jonathans feine Antwort!] Alternativ %p, Sie können Zeiger spezifischen Makros verwenden <inttypes.h>, hinzugefügt in C99.

Alle Objektzeiger sind implizit void*in C konvertierbar. Um den Zeiger jedoch als variadisches Argument zu übergeben, müssen Sie ihn explizit umwandeln (da beliebige Objektzeiger nur konvertierbar , aber nicht identisch mit ungültigen Zeigern sind):

printf("x lives at %p.\n", (void*)&x);
Kerrek SB
quelle
2
Alle Objektzeiger sind konvertierbar in void *(obwohl printf()Sie technisch die explizite Umwandlung benötigen, da es sich um eine variable Funktion handelt). Funktionszeiger sind nicht unbedingt in konvertierbar void *.
Café
@caf: Oh, ich wusste nichts über die verschiedenen Argumente - behoben! Vielen Dank!
Kerrek SB
2
Standard C erfordert nicht, dass Funktionszeiger void *ohne Verlust in Funktionszeiger konvertiert werden können. Glücklicherweise verlangt POSIX dies jedoch ausdrücklich (wobei zu beachten ist, dass es nicht Teil von Standard C ist). In der Praxis können Sie also damit durchkommen (Konvertieren void (*function)(void)in void *und zurück in void (*function)(void)), aber streng genommen ist dies nicht durch den C-Standard vorgeschrieben.
Jonathan Leffler
2
Jonathan und R.: Das ist alles sehr interessant, aber ich bin mir ziemlich sicher, dass wir hier nicht versuchen, Funktionszeiger zu drucken. Vielleicht ist dies nicht der richtige Ort, um dies zu diskutieren. Ich würde hier viel lieber Unterstützung sehen, wenn ich darauf bestehe, sie nicht zu nutzen %u!
Kerrek SB
2
%uund %lusind auf allen Maschinen falsch , nicht auf einigen Maschinen. Die Angabe von printfist sehr klar, dass das Verhalten undefiniert ist, wenn der übergebene Typ nicht mit dem vom Formatbezeichner geforderten Typ übereinstimmt. Ob die Größe der Typen übereinstimmt (was je nach Maschine wahr oder falsch sein kann), spielt keine Rolle. Es sind die Typen, die übereinstimmen müssen, und sie werden es niemals tun.
R .. GitHub STOP HELPING ICE
9

Alternativ zu den anderen (sehr guten) Antworten können Sie die entsprechenden Ganzzahlkonvertierungsspezifizierer in uintptr_toder intptr_t(von stdint.h/ inttypes.h) umwandeln und verwenden. Dies würde mehr Flexibilität bei der Formatierung des Zeigers ermöglichen, aber genau genommen ist keine Implementierung erforderlich, um diese Typedefs bereitzustellen.

R .. GitHub HÖREN SIE AUF, EIS ZU HELFEN
quelle
Überlegen #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } Sie, ob es ein undefiniertes Verhalten ist, die Adresse einer Variablen mit dem %uFormatbezeichner zu drucken . Die Adresse der Variablen ist in den meisten Fällen positiv. Kann ich sie %uanstelle von verwenden %p?
Zerstörer
1
@Destructor: Nein, %uist ein Format für den unsigned intTyp und kann nicht mit einem Zeigerargument auf verwendet werden printf.
R .. GitHub STOP HELPING ICE
-1

Sie können %xoder %Xoder verwenden %p; Alle von ihnen sind richtig.

  • Wenn Sie verwenden %x, wird die Adresse in Kleinbuchstaben angegeben, zum Beispiel:a3bfbc4
  • Wenn Sie verwenden %X, wird die Adresse in Großbuchstaben angegeben, zum Beispiel:A3BFBC4

Beide sind richtig.

Wenn Sie verwenden %xoder %Xsechs Positionen für die Adresse in Betracht ziehen, und wenn Sie verwenden %p, werden acht Positionen für die Adresse berücksichtigt. Beispielsweise:

Pascha
quelle
1
Willkommen bei SO. Bitte nehmen Sie sich etwas Zeit, um die anderen Antworten zu überprüfen. Sie erklären deutlich eine Reihe von Details, die Sie übersehen.
AntoineL