L’errore di segmentazione è un comportamento non definito effettivo quando si fa riferimento a un membro dati non statico

Avevo letto la seguente regola e ho cercato di scrivere un esempio, che ne rifletta uno. La regola è da 3.8 / 5 N3797:

Prima che la vita di un object sia iniziata ma dopo che è stata allocata la memoria che l’object occuperà, o dopo che la durata di un object è terminata e prima che la memoria occupata dall’object sia riutilizzata o rilasciata, qualsiasi puntatore che si riferisce alla memoria il luogo in cui l’object sarà o era stato individuato può essere usato, ma solo in modi limitati. Per un object in costruzione o distruzione, vedere 12.7. Altrimenti , tale puntatore si riferisce alla memoria allocata (3.7.4.2) e l’uso del puntatore come se il puntatore fosse di tipo void* è ben definito. L’indirazione attraverso tale puntatore è consentita ma il valore risultante può essere utilizzato solo in modi limitati, come descritto di seguito. Il programma ha un comportamento indefinito se:

[…]

– il puntatore viene utilizzato per accedere a un membro dati non statico o chiamare una funzione membro non statica dell’object o

[…]

L’esempio che ho scritto per:

 #include  #include  using std::cout; using std::endl; struct A { int b = 5; static const int a = 5; }; int main() { A *p = (A*)0xa31a3442; cout <

a; //1, Well-fromed, there is no compile-time error cout <

b; //2, Segmentation fault is producing }

È vero che nel caso //1 è ben formato e non causa alcun UB , ma //2 prodotto un errore di segmentazione, che è UB ?

Comportamento indefinito significa che qualsiasi cosa può accadere con un’implementazione conforms standard. Davvero niente. (e il tuo punto 2 è UB)

Un’implementazione potrebbe

  • fai esplodere il tuo computer e ferirti fisicamente
  • crea un buco nero che inghiotte l’intero sistema solare
  • non fare nulla di serio
  • accendi qualche LED sulla tua tastiera
  • fai un viaggio nel tempo e uccidi tutti i tuoi nonni prima della nascita dei tuoi genitori
  • eccetera….

ed essere conforms (nel caso di UB); leggi anche l’idea più familiare dei demoni nasali .

Quindi quello che succede su UB non è prevedibile e non è riproducibile (in generale).

Più seriamente, pensa un po ‘a cosa potrebbe significare UB nel computer collegato ai freni ABS della tua auto, o in qualche cuore artificiale , o alla guida di qualche centrale nucleare.

In particolare, potrebbe funzionare a volte. Poiché la maggior parte dei sistemi operativi hanno ASLR, il codice ha una piccola possibilità di funzionare (ad esempio se 0xa31a3442 punta a qualche posizione valida , ad esempio nello stack, ma non lo riprodurrà alla prossima esecuzione!)

UB è un modo per dare libertà agli implementatori (ad esempio di compilatori o sistemi operativi) e ai computer di fare tutto ciò che “vogliono”, in altre parole di non preoccuparsi delle conseguenze. Ciò consente ad esempio intelligenti ottimizzazioni o piacevoli trucchi di implementazione. Ma dovresti preoccuparti (e le conseguenze sono diverse se stai codificando il sistema di controllo di volo incorporato di un aeroplano, o solo alcuni LED di illuminazione demo hacky con un RasberryPi, o un semplice esempio di un corso in C ++ in esecuzione su Linux).

Ricorda che gli standard linguistici non richiedono nemmeno un computer (o qualsiasi hardware) nell’implementazione: potresti “eseguire” il tuo codice C ++ con un gruppo di umani schiavi, ma ciò sarebbe altamente immorale (e costoso e inaffidabile).

Vedi anche qui per ulteriori riferimenti. Dovresti almeno leggere il blog di Lattner su Undefined Behavior (la maggior parte di ciò che ha scritto per C si applica a C ++ e molti altri linguaggi con UB).


(aggiunto a dicembre 2015 e giugno 2016)

NB. Lo strumento valgrind e varie opzioni -fsanitize= debug per i recenti GCC o Clang / LLVM sono abbastanza utili. Inoltre, abilita tutti gli avvisi e le informazioni di debug nel tuo compilatore (es. g++ -Wall -Wextra -g ), e usa le opzioni di strumentazione appropriate come -fsanitize=undefined . Essere consapevoli che è imansible rilevare staticamente ed esaurientemente in fase di compilazione tutti i casi di UB (che sarebbe equivalente al problema di interruzione ).

PS. La risposta sopra non è specifica per C ++; si adatta anche a C!

Hai chiesto:

È vero che nel caso // 1 è ben formato e non causa alcun UB?

Le parti dello standard che hai citato non menzionano nulla a riguardo.

Hai anche chiesto:

ma // 2 ha prodotto un errore di segmentazione, che è UB?

Le parti dello standard che hai citato non corrispondono a questo particolare comportamento. Stai vedendo UB a causa di dove p punti. Punta alla memoria che non contiene un object valido.

La regola 3.8 / 5 riguarda il tempo al di fuori della costruzione / distruzione di un object ma all’interno dell’allocazione / rilascio della memoria in cui risiede l’object. Quanto segue dimostra i punti al di fuori della durata di un object:

 void *buffer = malloc(sizeof(A)); // outside of lifetime of a // a->b is undefined A* a = new (buffer) A(); // within lifetime of a // a->b is valid a->~A(); // outside of lifetime of a // a->b is undefined free(buffer); 

Tecnicamente, il tuo post in realtà non riflette la regola 3.8 / 5, perché non stai accedendo all’object al di fuori della sua durata. Stai semplicemente gettando memoria casuale come un’istanza.