Problemi con “Puntatore dal numero intero / numero intero dal puntatore senza cast”

Questa domanda è intesa come una voce di FAQ per tutte le inizializzazioni / assegnazioni tra numeri interi e problemi del puntatore.


Voglio scrivere codice in cui un puntatore è impostato su un indirizzo di memoria specifico, ad esempio 0x12345678 . Ma quando compilo questo codice con il compilatore gcc, ottengo “inizializzazione rende puntatore da un intero senza cast” avvisi / errori:

 int* p = 0x12345678; 

Allo stesso modo, questo codice fornisce “l’inizializzazione rende intero dal puntatore senza cast”:

 int* p = ...; int i = p; 

Se faccio lo stesso al di fuori della linea di dichiarazione delle variabili, il messaggio è lo stesso ma dice “assegnazione” invece di “inizializzazione”:

 p = 0x12345678; // "assignment makes pointer from integer without a cast" i = p; // "assignment makes integer from pointer without a cast" 

I test con altri compilatori popolari forniscono anche messaggi di errore / avviso:

  • clang dice “intero incompatibile alla conversione del puntatore”
  • icc dice “non è ansible utilizzare un valore di tipo int per inizializzare un’ quadro di tipo int*
  • MSVC (cl) dice “inizializzare int* differisce in livelli di riferimento indiretto da int “.

Domanda: gli esempi sopra sono validi C?


E una domanda successiva:

Questo non fornisce alcun avviso / errore:

 int* p = 0; 

Perchè no?

No, non è valido C e non è mai stato valido C. Questi esempi sono la cosiddetta violazione del vincolo dello standard.

Lo standard non consente di inizializzare / assegnare un puntatore a un numero intero, né un intero a un puntatore. Devi forzare manualmente una conversione di tipo con un cast:

 int* p = (int*) 0x1234; int i = (int)p; 

Se non si utilizza il cast, il codice non è valido C e il compilatore non è autorizzato a consentire il passaggio del codice senza visualizzare un messaggio. Nello specifico, questo è regolato dalle regole di semplice assegnazione , C17 6.5.16.1 §1:

6.5.16.1 Assegnazione semplice

vincoli

Uno dei seguenti dovrà contenere:

  • l’operando di sinistra ha un tipo aritmetico atomico, qualificato o non qualificato e il diritto ha un tipo aritmetico;
  • l’operando di sinistra ha una versione atomica, qualificata o non qualificata di una struttura o tipo di unione compatibile con il tipo del diritto;
  • l’operando di sinistra ha un tipo di puntatore atomico, qualificato o non qualificato e (considerando il tipo che l’operando di sinistra avrebbe dopo la conversione di lvalue) entrambi gli operandi sono puntatori a versioni qualificate o non qualificate di tipi compatibili, e il tipo puntato a sinistra ha tutto i qualificatori del tipo indicato dal diritto;
  • l’operando di sinistra ha un tipo di puntatore atomico, qualificato o non qualificato e (considerando il tipo che l’operando di sinistra avrebbe dopo la conversione di lvalue) un operando è un puntatore a un tipo di object e l’altro è un puntatore a una versione qualificata o non qualificata di vuoto, e il tipo puntato da sinistra ha tutti i qualificatori del tipo puntato da destra;
  • l’operando di sinistra è un puntatore atomico, qualificato o non qualificato e la destra è una costante di puntatore nullo; o
  • l’operando di sinistra ha tipo _Bool atomico, qualificato o non qualificato e il diritto è un puntatore.

In caso di int* p = 0x12345678; , l’operando di sinistra è un puntatore e il diritto è un tipo aritmetico.
In caso di int i = p; , l’operando di sinistra è un tipo aritmetico e il diritto è un puntatore.
Nessuno di questi si accorda con nessuno dei vincoli citati sopra.

Per quanto riguarda il motivo int* p = 0; funziona, è un caso speciale. L’operando di sinistra è un puntatore e la destra è una costante di puntatore nullo . Maggiori informazioni sulla differenza tra puntatori nulli, costanti del puntatore nullo e macro NULL .


Alcune cose importanti:

  • Se si assegna un indirizzo raw a un puntatore, è probabile che il puntatore sia qualificato come volatile , dato che punta a qualcosa come un registro hardware o una posizione di memoria EEPROM / Flash, che può cambiare il suo contenuto in fase di esecuzione.

  • La conversione di un puntatore in un numero intero non è affatto garantita per funzionare anche con il cast. Lo standard (C17 6.3.2.3 §5 e §6 dice):

    Un numero intero può essere convertito in qualsiasi tipo di puntatore. Ad eccezione di quanto specificato in precedenza, il risultato è definito dall’implementazione, potrebbe non essere allineato correttamente, potrebbe non puntare a un’entity framework del tipo referenziato e potrebbe essere una rappresentazione trap. 68)

    Qualsiasi tipo di puntatore può essere convertito in un tipo intero. Ad eccezione di quanto specificato in precedenza, il risultato è definito dall’implementazione. Se il risultato non può essere rappresentato nel tipo intero, il comportamento non è definito. Il risultato non deve essere compreso nell’intervallo di valori di qualsiasi tipo intero.

    Nota informativa del piede:

    68) Le funzioni di mapping per convertire un puntatore in un intero o un intero in un puntatore devono essere coerenti con la struttura di indirizzamento dell’ambiente di esecuzione.

    Inoltre, l’indirizzo da un puntatore potrebbe essere maggiore di quello che si adatta a un int , come nel caso della maggior parte dei sistemi a 64 bit. Pertanto è meglio usare uintptr_t da