printf (“% p”) e casting in (void *)

In una domanda recente, qualcuno ha detto che quando si stampa un valore di puntatore con printf, il chiamante deve lanciare il puntatore a void *, in questo modo:

int *my_ptr = .... printf("My pointer is: %p", (void *)my_ptr); 

Per la vita di me non riesco a capire perché. Ho trovato questa domanda , che è quasi la stessa. La risposta alla domanda è corretta – spiega che gli inte e i puntatori non hanno necessariamente la stessa lunghezza.

Questo è, ovviamente, vero, ma quando ho già un puntatore, come nel caso precedente, perché dovrei trasmettere da int * a void * ? Quando è un * int diverso dal vuoto *? In effetti, quando fa (void *)my_ptr genera un codice macchina diverso da my_ptr ?

AGGIORNAMENTO: i risponditori multipli esperti hanno citato lo standard, dicendo che passare il tipo sbagliato potrebbe comportare un comportamento indefinito. Come? Prevedo printf("%p", (int *)ptr) e printf("%p", (void *)ptr) per generare lo stesso identico stack frame. Quando le due chiamate genereranno diversi stack frame?

Nel linguaggio C tutti i tipi di puntatore possono differire nelle loro rappresentazioni. Quindi, sì, int * è diverso da void * . Una piattaforma di vita reale che illustrerebbe questa differenza potrebbe essere difficile (o imansible) da trovare, ma a livello concettuale la differenza è ancora lì.

In altre parole, in generale i diversi tipi di puntatore hanno rappresentazioni diverse. int * è diverso da void * e diverso da double * . Il fatto che la tua piattaforma usi la stessa rappresentazione per void * e int * non è altro che una coincidenza, per quanto riguarda il linguaggio C.

Il linguaggio afferma che alcuni tipi di puntatore devono avere rappresentazioni identiche, che include void * contro char * , puntatori a diversi tipi di struct o, per esempio, int * e const int * . Ma queste sono solo eccezioni dalla regola generale.

p specificatore di conversione p richiede un argomento di tipo void * . Se non si passa un argomento di tipo void * , la chiamata alla funzione richiama il comportamento non definito.

Dallo standard C:

(C11, 7.21.6.1p8 Funzioni di input / output formattate) “p L’argomento deve essere un puntatore a vuoto.”

Non è necessario che i tipi di puntatore in C abbiano la stessa dimensione o la stessa rappresentazione.

Un esempio di implementazione con rappresentazione di tipi di puntatori diversi è Cray PVP in cui la rappresentazione dei tipi di puntatore è 64-bit per void * e char * ma 32-bit per gli altri tipi di puntatore.

Vedi “Manuale di riferimento Cray C / C ++”, Tabella 3. in “9.1.2.2” http://docs.cray.com/books/004-2179-003/004-2179-003-manual.pdf

In realtà, ad eccezione di antichi mainframe / minis, è molto improbabile che diversi tipi di puntatori abbiano dimensioni diverse. Tuttavia hanno tipi diversi, e secondo le specifiche per printf , chiamandolo con l’argomento di tipo errato per lo specificatore di formato risulta in un comportamento indefinito . Questo significa che non farlo.

c11: 7.21.6 Funzioni di input / output formattate (p8):

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

Altre persone hanno affrontato adeguatamente il caso di passare un int * a una funzione prototipata con un numero fisso di argomenti che si aspetta un diverso tipo di puntatore.

printf non è una funzione del genere. È una funzione variadica, quindi le promozioni degli argomenti predefiniti vengono utilizzate per i relativi argomenti anonimi (ovvero tutto dopo la stringa di formato) e se il tipo promosso di ciascun argomento non corrisponde esattamente al tipo previsto dall’effector di formattazione, il comportamento non è definito. In particolare, anche se int * e void * hanno una rappresentazione identica,

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

ha un comportamento indefinito.

Questo perché il layout del frame di chiamata può dipendere dal tipo concreto esatto di ciascun argomento. Gli ABI che specificano aree argomento diverse per i tipi di puntatori e non puntatori si sono verificati nella vita reale (ad esempio, il Motorola 68000 vorrebbe mantenere i puntatori nei registri indirizzi e non puntatori nei registri dati nella misura massima ansible). Non sono a conoscenza di alcun ABI del mondo reale che segrega tipi di puntatore distinti, ma è permesso e non mi sorprenderebbe sentirne parlare.