Identificatore di formato corretto per stampare il puntatore o l'indirizzo?


194

Quale identificatore di formato dovrei usare per stampare l'indirizzo di una variabile? Sono confuso tra il lotto sottostante.

% u - numero intero senza segno

% x - valore esadecimale

% p - puntatore vuoto

Quale sarebbe il formato ottimale per stampare un indirizzo?

Risposte:


240

La risposta più semplice, supponendo che non ti dispiaccia i capricci e le variazioni di formato tra piattaforme diverse, è la %pnotazione standard .

Lo standard C99 (ISO / IEC 9899: 1999) dice in §7.19.6.1 ¶8:

pL'argomento deve essere un puntatore a void. Il valore del puntatore viene convertito in una sequenza di caratteri di stampa, in modo definito dall'implementazione.

(In C11 - ISO / IEC 9899: 2011 - le informazioni sono in §7.21.6.1 ¶8.)

Su alcune piattaforme, questo includerà un carattere iniziale 0xe su altri no, e le lettere potrebbero essere in minuscolo o maiuscolo, e lo standard C non definisce nemmeno che deve essere un output esadecimale sebbene io sappia di nessuna implementazione dove non lo è.

È in qualche modo aperto a discutere se è necessario convertire esplicitamente i puntatori con un (void *)cast. È esplicito, che di solito è buono (quindi è quello che faccio), e lo standard dice "l'argomento deve essere un puntatore a void". Sulla maggior parte delle macchine, si eviterebbe di omettere un cast esplicito. Tuttavia, importerebbe su una macchina in cui la rappresentazione dei bit di un char *indirizzo per una determinata posizione di memoria è diversa dall'indirizzo " qualunque altro puntatore " per la stessa posizione di memoria. Questa sarebbe una macchina indirizzata a parole, anziché indirizzata a byte. Tali macchine non sono comuni (probabilmente non disponibili) in questi giorni, ma la prima macchina su cui ho lavorato dopo l'università è stata una di queste (ICL Perq).

Se non sei soddisfatto del comportamento definito dall'implementazione di %p, usa C99 <inttypes.h>e uintptr_tinvece:

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

Ciò consente di ottimizzare la rappresentazione in base alle proprie esigenze. Ho scelto di avere le cifre esadecimali in lettere maiuscole in modo che il numero abbia uniformemente la stessa altezza e che quindi il calo caratteristico all'inizio di, 0xA1B2CDEFappaia, non come quello 0xa1b2cdefche scende su e giù lungo il numero. La tua scelta però, entro limiti molto ampi. Il (uintptr_t)cast è raccomandato in modo inequivocabile da GCC quando può leggere la stringa di formato in fase di compilazione. Penso che sia corretto richiedere il cast, anche se sono sicuro che ci sono alcuni che ignorerebbero l'avvertimento e se ne andrebbero per la maggior parte del tempo.


Kerrek chiede nei commenti:

Sono un po 'confuso su promozioni standard e argomenti vari. Tutti i puntatori vengono promossi standard su void *? Altrimenti, se int*fossero, diciamo, due byte e void*fossero 4 byte, allora sarebbe chiaramente un errore leggere quattro byte dall'argomento, no?

Avevo l'illusione che lo standard C affermasse che tutti i puntatori a oggetti dovevano avere le stesse dimensioni, quindi void *e int *non potevano avere dimensioni diverse. Tuttavia, ciò che penso sia la sezione pertinente dello standard C99 non è così enfatico (anche se non conosco un'implementazione in cui ciò che ho suggerito sia vero è in realtà falso):

§6.2.5 Tipi

¶26 Un puntatore a vuoto deve avere gli stessi requisiti di rappresentazione e allineamento di un puntatore a un tipo di carattere. 39) Allo stesso modo, i puntatori a versioni qualificate o non qualificate di tipi compatibili devono avere gli stessi requisiti di rappresentazione e allineamento. Tutti i puntatori ai tipi di struttura devono avere gli stessi requisiti di rappresentazione e allineamento reciproci. Tutti i puntatori ai tipi di unione devono avere gli stessi requisiti di rappresentazione e allineamento reciproci. I puntatori ad altri tipi non devono necessariamente avere gli stessi requisiti di rappresentazione o allineamento.

39) Gli stessi requisiti di rappresentazione e allineamento implicano l'intercambiabilità degli argomenti alle funzioni, restituiscono valori dalle funzioni e membri dei sindacati.

(C11 dice esattamente lo stesso nella sezione §6.2.5, ¶28 e nota 48).

Pertanto, tutti i puntatori a strutture devono avere le stesse dimensioni l'uno dell'altro e devono condividere gli stessi requisiti di allineamento, anche se le strutture a cui puntano i puntatori possono avere requisiti di allineamento diversi. Allo stesso modo per i sindacati. I puntatori carattere e i puntatori vuoti devono avere gli stessi requisiti di dimensioni e allineamento. I puntatori alle variazioni su int(significato unsigned inte signed int) devono avere le stesse dimensioni e requisiti di allineamento reciproci; allo stesso modo per altri tipi. Ma lo standard C non lo dice formalmente sizeof(int *) == sizeof(void *). Oh bene, SO è buono per farti ispezionare i tuoi presupposti.

Lo standard C in modo definitivo non richiede che i puntatori a funzione abbiano le stesse dimensioni dei puntatori a oggetti. Ciò era necessario per non rompere i diversi modelli di memoria su sistemi simili a DOS. Lì potresti avere puntatori di dati a 16 bit ma puntatori di funzione a 32 bit, o viceversa. Questo è il motivo per cui lo standard C non impone che i puntatori a funzioni possano essere convertiti in puntatori a oggetti e viceversa.

Fortunatamente (per i programmatori che scelgono POSIX), POSIX entra nella violazione e impone che i puntatori a funzione e puntatori a dati abbiano le stesse dimensioni:

§2.12.3 Tipi di puntatori

Tutti i tipi di puntatori di funzione devono avere la stessa rappresentazione del puntatore di tipo da annullare. La conversione di un puntatore a funzione void *non deve alterare la rappresentazione. Un void *valore risultante da tale conversione può essere riconvertito nel tipo di puntatore a funzione originale, usando un cast esplicito, senza perdita di informazioni.

Nota: lo standard ISO C non lo richiede, ma è necessario per la conformità POSIX.

Quindi, sembra che i cast espliciti void *siano fortemente raccomandabili per la massima affidabilità nel codice quando si passa un puntatore a una funzione variadica come printf(). Sui sistemi POSIX, è sicuro eseguire il cast di un puntatore a un puntatore vuoto per la stampa. Su altri sistemi, non è necessariamente sicuro farlo, né è necessariamente sicuro passare puntatori diversi da quelli void *senza cast.


3
Sono un po 'confuso su promozioni standard e argomenti vari. A tutti i puntatori viene promosso lo standard void*? Altrimenti, se int*fossero, diciamo, due byte e void*fossero 4 byte, allora sarebbe chiaramente un errore leggere quattro byte dall'argomento, no?
Kerrek SB,

Si noti che un aggiornamento a POSIX (POSIX 2013) ha rimosso la sezione 2.12.3, spostando invece la maggior parte dei requisiti nella dlsym()funzione. Un giorno scriverò il resto ... ma "un giorno" non è "oggi".
Jonathan Leffler,

Questa risposta si applica anche ai puntatori alle funzioni? Possono essere convertiti in void *? Vedo il tuo commento qui . Dal momento che è necessaria solo una conversione di un wat (puntatore a void *), funziona allora?
chux - Ripristina Monica il

@chux: rigorosamente, la risposta è "no", ma in pratica la risposta è "sì". Lo standard C non garantisce che i puntatori a funzione possano essere convertiti in ae void *viceversa senza perdita di informazioni. Pragmaticamente, ci sono pochissime macchine in cui la dimensione di un puntatore a funzione non corrisponde alla dimensione di un puntatore a oggetto. Non credo che lo standard fornisca un metodo per stampare un puntatore a funzione su macchine in cui la conversione è problematica.
Jonathan Leffler,

"e ritorno senza perdita di informazioni" non è rilevante per la stampa. Questo aiuta?
chux - Ripristina Monica il

50

pè l'identificatore di conversione per stampare i puntatori. Usa questo.

int a = 42;

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

Ricorda che l'omissione del cast è un comportamento indefinito e che la stampa con lo pspecificatore di conversione viene eseguita in modo definito dall'implementazione.


2
Perdonate, perché omettere il cast è "comportamento indefinito"? È importante l'indirizzo di quale variabile è, se tutto ciò che serve è l'indirizzo, non il valore?
valdo,

9
@valdo perché lo dice C (C99, 7.19.6.1p8) "p L'argomento deve essere un puntatore al vuoto."
ouah

12
@valdo: non è necessariamente il caso che tutti i puntatori abbiano la stessa dimensione / rappresentazione.
Caf

32

Usa %p, per "puntatore", e non usare nient'altro *. Non sei garantito dallo standard che ti sia permesso di trattare un puntatore come un particolare tipo di numero intero, quindi otterrai effettivamente un comportamento indefinito con i formati integrali. (Ad esempio, si %uaspetta un unsigned int, ma cosa succede se void*ha dimensioni o requisiti di allineamento diversi rispetto a unsigned int?)

*) [Vedi la bella risposta di Jonathan!] In alternativa a %p, puoi usare macro specifiche per puntatori <inttypes.h>, aggiunte in C99.

Tutti i puntatori di oggetti sono implicitamente convertibili in void*in C, ma per passare il puntatore come argomento variadico, devi lanciarlo esplicitamente (poiché i puntatori di oggetti arbitrari sono solo convertibili , ma non identici ai puntatori vuoti):

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

2
Tutti i puntatori oggetto sono convertibili in void *(anche printf()se tecnicamente è necessario il cast esplicito, poiché è una funzione variadica). I puntatori a funzione non sono necessariamente convertibili in void *.
Caf

@caf: Oh, non sapevo degli argomenti variadici - risolto! Grazie!
Kerrek SB,

2
Lo standard C non richiede che i puntatori a funzione siano convertibili in void *e indietro in puntatore a funzione senza perdita; fortunatamente, POSIX lo richiede esplicitamente (osservando che non fa parte dello standard C). Quindi, in pratica, puoi cavartela (convertendola void (*function)(void)in void *e viceversa void (*function)(void)), ma rigorosamente non è obbligatoria per lo standard C.
Jonathan Leffler,

2
Jonathan e R .: Tutto questo è molto interessante, ma sono abbastanza sicuro che non stiamo provando a stampare i puntatori a funzioni qui, quindi forse questo non è proprio il posto giusto per discuterne. Preferirei vedere un po 'di supporto qui per la mia insistenza di non usare %u!
Kerrek SB,

2
%ue %lusono sbagliate su tutte le macchine , non su alcune macchine. La specifica di printfè molto chiara che quando il tipo passato non corrisponde al tipo richiesto dall'identificatore di formato, il comportamento non è definito. Non importa se la dimensione dei tipi corrisponde (che può essere vera o falsa, a seconda della macchina); sono i tipi che devono corrispondere e non lo faranno mai.
R .. GitHub smette di aiutare ICE il

9

In alternativa alle altre (ottime) risposte, è possibile eseguire il cast su uintptr_to intptr_t(da stdint.h/ inttypes.h) e utilizzare i corrispondenti identificatori di conversione di numeri interi. Ciò consentirebbe una maggiore flessibilità nel modo in cui il puntatore è formattato, ma a rigor di termini non è necessaria un'implementazione per fornire questi typedef.


considera #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } un comportamento indefinito stampare l'indirizzo della variabile usando l' %uidentificatore di formato? L'indirizzo della variabile nella maggior parte dei casi è positivo, quindi posso usare %uinvece di %p?
Distruttore

1
@Destruttore: No, %uè un formato per unsigned inttipo e non può essere utilizzato con un argomento puntatore a printf.
R .. GitHub smette di aiutare ICE il

-1

Puoi usare %xo %Xo %p; sono tutti corretti.

  • Se si utilizza %x, l'indirizzo viene fornito in minuscolo, ad esempio:a3bfbc4
  • Se lo usi %X, l'indirizzo viene indicato in maiuscolo, ad esempio:A3BFBC4

Entrambi sono corretti.

Se usi %xo %Xsta prendendo in considerazione sei posizioni per l'indirizzo e se usi %pconsidera otto posizioni per l'indirizzo. Per esempio:


1
Benvenuti in SO. Per favore, prenditi un po 'di tempo per rivedere le altre risposte, stanno spiegando chiaramente una serie di dettagli che stai trascurando.
AntoineL,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.