“Int * nums = {5, 2, 1, 4}” provoca un errore di segmentazione

int *nums = {5, 2, 1, 4}; printf("%d\n", nums[0]); 

causa un segfault, mentre

 int nums[] = {5, 2, 1, 4}; printf("%d\n", nums[0]); 

non lo fa. Adesso:

 int *nums = {5, 2, 1, 4}; printf("%d\n", nums); 

stampa 5.

Sulla base di ciò, ho ipotizzato che la notazione di inizializzazione dell’array {} carichi ciecamente questi dati in qualunque variabile sia a sinistra. Quando è int [], la matrice viene riempita come desiderato. Quando è int *, il puntatore viene riempito da 5 e le posizioni di memoria dopo il punto in cui è memorizzato il puntatore vengono riempite da 2, 1 e 4. Quindi nums [0] tenta di deref 5, causando un segfault.

Se sbaglio, per favore correggimi. E se ho ragione, ti prego di elaborare, perché non capisco perché gli inizializzatori di array funzionano come loro.

C’è una (stupida) regola in C che dice che qualsiasi variabile normale può essere inizializzata con un elenco di inizializzazione incluso, come se fosse una matrice.

Ad esempio puoi scrivere int x = {0}; , che è completamente equivalente a int x = 0; .

Quindi quando scrivi int *nums = {5, 2, 1, 4}; stai effettivamente dando una lista di inizializzatori a una singola variabile puntatore. Tuttavia, è solo una singola variabile in modo che venga assegnato solo il primo valore 5, il resto della lista viene ignorato (in realtà non penso che il codice con inizializzatori in eccesso debba essere compilato con un compilatore rigoroso) – non lo fa scrivi per niente alla memoria. Il codice è equivalente a int *nums = 5; . Il che significa che i nums dovrebbero puntare all’indirizzo 5 .

A questo punto dovresti già aver ricevuto due avvisi / errori del compilatore:

  • Assegnazione di un intero al puntatore senza cast.
  • Elementi in eccesso nell’elenco di inizializzazione.

E poi, naturalmente, il codice si bloccherà e brucerà poiché 5 probabilmente non è un indirizzo valido a cui è permesso il dereference con nums[0] .

Come nota a printf , dovresti printf indirizzi del puntatore con l’identificatore %p o altrimenti stai invocando un comportamento indefinito.


Non sono sicuro di cosa stai provando a fare qui, ma se vuoi impostare un puntatore per puntare a un array, dovresti fare:

 int nums[] = {5, 2, 1, 4}; int* ptr = nums; // or equivalent: int* ptr = (int[]){5, 2, 1, 4}; 

O se vuoi creare una serie di puntatori:

 int* ptr[] = { /* whatever makes sense here */ }; 

MODIFICARE

Dopo alcune ricerche posso dire che la “lista di inizializzazione degli elementi in eccesso” non è effettivamente valida C – è un’estensione GCC .

Lo standard di inizializzazione 6.7.9 dice (sottolineatura mia):

2 Nessun inizializzatore tenta di fornire un valore per un object non contenuto nell’entity framework che viene inizializzata.

/ – /

11 L’inizializzatore per uno scalare deve essere una singola espressione, facoltativamente racchiusa tra parentesi graffe. Il valore iniziale dell’object è quello dell’espressione (dopo la conversione); si applicano gli stessi vincoli e conversioni di tipo per l’assegnazione semplice, prendendo il tipo di scalare come versione non qualificata del suo tipo dichiarato.

“Tipo scalare” è un termine standard che si riferisce a singole variabili che non sono di tipo array, struct o union (quelle sono chiamate “tipo aggregato”).

Quindi, in inglese semplice, lo standard dice: “quando si inizializza una variabile, sentirsi liberi di aggiungere alcune parentesi extra attorno all’espressione di inizializzazione, solo perché è ansible.”

SCENARIO 1

 int *nums = {5, 2, 1, 4}; // <-- assign multiple values to a pointer variable printf("%d\n", nums[0]); // segfault 

Perché questo segfault?

Hai dichiarato nums come puntatore a int - cioè che nums dovrebbe contenere l'indirizzo di un intero in memoria.

Quindi si è tentato di inizializzare i nums su una matrice di più valori. Quindi, senza scavare in molti dettagli, ciò è concettualmente scorretto : non ha senso assegnare più valori a una variabile che dovrebbe contenere un valore. A questo proposito, vedresti esattamente lo stesso effetto se lo fai:

 int nums = {5, 2, 1, 4}; // <-- assign multiple values to an int variable printf("%d\n", nums); // also print 5 

In entrambi i casi (assegnare più valori a un puntatore o una variabile int), ciò che accade è che la variabile otterrà il primo valore che è 5 , mentre i valori rimanenti vengono ignorati. Questo codice è conforms ma riceverai avvertimenti per ogni valore aggiuntivo che non dovrebbe essere incluso nell'assegnazione:

warning: excess elements in scalar initializer .

Nel caso di assegnare più valori alla variabile puntatore, il programma segfaults quando si accede a nums[0] , il che significa che si sta differenziando ciò che è memorizzato nell'indirizzo 5 letteralmente. In questo caso non è stata allocata alcuna memoria valida per i nums puntatore.

Vale la pena notare che non esiste un segfault per il caso di assegnare più valori alla variabile int (non si sta denigrando alcun puntatore non valido qui).


SCENARIO 2

 int nums[] = {5, 2, 1, 4}; 

Questo non segfault, perché stai allocando legalmente un array di 4 ints nello stack.


SCENARIO 3

 int *nums = {5, 2, 1, 4}; printf("%d\n", nums); // print 5 

Questo non segfault come previsto, perché si sta stampando il valore del puntatore stesso - NON si tratta di dereferenziazione (che è un accesso non valido alla memoria).


Altri

È quasi sempre destinato a segfault ogni volta che si codifica il valore di un puntatore come questo (perché è compito del sistema operativo determinare quale processo può accedere a quale posizione di memoria).

 int *nums = 5; // <-- segfault 

Pertanto, una regola empirica consiste nell'inizializzare sempre un puntatore all'indirizzo di alcune variabili allocate , ad esempio:

 int a; int *nums = &a; 

o,

 int a[] = {5, 2, 1, 4}; int *nums = a; 

int *nums = {5, 2, 1, 4}; è un codice mal formato. C’è un’estensione GCC che tratta questo codice come:

 int *nums = (int *)5; 

tentando di formare un puntatore all’indirizzo di memoria 5. (Questo non mi sembra un’estensione utile, ma suppongo che la base degli sviluppatori lo voglia).

Per evitare questo comportamento (o almeno ricevere un avvertimento) potresti compilare in modalità standard, ad esempio -std=c11 -pedantic .

Una forma alternativa di codice valido sarebbe:

 int *nums = (int[]){5, 2, 1, 4}; 

che punta a un valore mutabile della stessa durata di memorizzazione di nums . Tuttavia, la versione int nums[] è generalmente migliore in quanto utilizza meno spazio di archiviazione ed è ansible utilizzare sizeof per rilevare la lunghezza dell’array.

 int *nums = {5, 2, 1, 4}; 

nums è un puntatore di tipo int . Quindi dovresti fare questo punto su qualche posizione di memoria valida. num[0] stai provando a dereferenziare una posizione di memoria casuale e quindi l’errore di segmentazione.

Sì, il puntatore contiene il valore 5 e stai cercando di dereferenziarlo che è un comportamento non definito sul tuo sistema. (Sembra che 5 non sia una posizione di memoria valida sul tuo sistema)

Mentre

 int nums[] = {1,2,3,4}; 

è una dichiarazione valida in cui si dice che nums è una matrice di tipo int e la memoria è allocata in base al numero di elementi passati durante l’inizializzazione.

Assegnando {5, 2, 1, 4}

 int *nums = {5, 2, 1, 4}; 

stai assegnando da 5 a nums (dopo un typecast implicito da int a pointer a int). Dereferendolo effettua una chiamata di accesso alla posizione di memoria su 0x5 . Potrebbe non essere consentito al tuo programma di accedere.

Provare

 printf("%p", (void *)nums);