c++ - Comportamento indefinito, non specificato e definito dall'implementazione





undefined-behavior c++-faq unspecified-behavior (9)


Forse una formulazione semplice potrebbe essere più facile da capire rispetto alla rigorosa definizione degli standard.

comportamento definito dall'implementazione
Il linguaggio dice che abbiamo tipi di dati. I produttori di compilatori specificano quali dimensioni devono utilizzare e forniscono una documentazione di ciò che hanno fatto.

comportamento indefinito
Stai facendo qualcosa di sbagliato. Ad esempio, hai un valore molto grande in un int che non si adatta al char . Come si mette quel valore in char ? in realtà non c'è modo! Qualcosa potrebbe accadere, ma la cosa più sensata sarebbe prendere il primo byte di quell'int e metterlo in char . È semplicemente sbagliato farlo per assegnare il primo byte, ma è quello che succede sotto il cofano.

comportamento non specificato
Quale funzione di questi due viene eseguita per prima?

void fun(int n, int m);

int fun1()
{
  cout << "fun1";
  return 1;
}
int fun2()
{
  cout << "fun2";
  return 2;
}
...
fun(fun1(), fun2()); // which one is executed first?

La lingua non specifica la valutazione, da sinistra a destra o da destra a sinistra! Quindi un comportamento non specificato può o non può portare a un comportamento indefinito, ma certamente il tuo programma non dovrebbe produrre un comportamento non specificato.

@ eSKay Penso che la tua domanda valga la pena di modificare la risposta per chiarire di più :)

per fun(fun1(), fun2()); non è il comportamento "implementazione definita"? Il compilatore deve scegliere uno o l'altro corso, dopotutto?

La differenza tra l'implementazione definita e non specificata è che il compilatore dovrebbe scegliere un comportamento nel primo caso ma non nel secondo caso. Ad esempio, un'implementazione deve avere una sola definizione di sizeof(int) . Quindi, non si può dire che sizeof(int) sia 4 per una parte del programma e 8 per gli altri. A differenza del comportamento non specificato, in cui il compilatore può dire OK valuterò questi argomenti da sinistra a destra e gli argomenti della prossima funzione verranno valutati da destra a sinistra. Può accadere nello stesso programma, è per questo che viene chiamato non specificato . In effetti, C ++ avrebbe potuto essere reso più semplice se fossero stati specificati alcuni comportamenti non specificati. Dai un'occhiata qui alla risposta del Dr. Stroustrup per questo :

Si sostiene che la differenza tra ciò che può essere prodotto dando al compilatore questa libertà e richiedendo "una valutazione ordinaria da sinistra a destra" può essere significativa. Non sono convinto, ma con innumerevoli compilatori "là fuori" che sfruttano la libertà e alcune persone che difendono con passione quella libertà, un cambiamento sarebbe difficile e potrebbe richiedere decenni per penetrare negli angoli più lontani dei mondi C e C ++. Sono deluso dal fatto che non tutti i compilatori mettono in guardia dal codice come ++ i + i ++. Allo stesso modo, l'ordine di valutazione degli argomenti non è specificato.

IMO troppe "cose" sono lasciate indefinite, non specificate, definite dall'implementazione, ecc. Tuttavia, è facile da dire e anche dare esempi, ma difficili da risolvere. Va anche notato che non è poi così difficile evitare la maggior parte dei problemi e produrre codice portatile.

Qual è la differenza tra comportamento non definito, non specificato e definito dall'implementazione in C e C ++?




Storicamente, sia il comportamento definito dall'implementazione sia il comportamento indefinito hanno rappresentato situazioni in cui gli autori dello Standard si aspettavano che le persone che scrivevano implementazioni di qualità usassero il giudizio per decidere quali garanzie comportamentali, se ce ne fossero, sarebbero utili per i programmi nel campo di applicazione previsto in esecuzione sul obiettivi previsti. Le esigenze del codice high-end di crimpatura del numero sono molto diverse da quelle del codice di sistemi di basso livello, e sia UB che IDB offrono agli scrittori di compilatori la flessibilità necessaria per soddisfare queste diverse esigenze. Nessuna delle due categorie impone che le implementazioni si comportino in un modo che è utile per uno scopo particolare o anche per qualsivoglia scopo. Le implementazioni di qualità che pretendono di essere adatte per uno scopo particolare, tuttavia, dovrebbero comportarsi in un modo che si addice a tale scopo, indipendentemente dal fatto che lo Standard lo richieda o meno .

L'unica differenza tra il comportamento definito dall'implementazione e il comportamento indefinito è che il primo richiede che le implementazioni definiscano e documentino un comportamento coerente anche nei casi in cui nulla potrebbe essere utile all'implementazione . La linea di demarcazione tra loro non è se sia generalmente utile per le implementazioni definire comportamenti (gli scrittori di compilatori dovrebbero definire comportamenti utili quando è pratico se lo Standard richiede o meno) ma se ci potrebbero essere implementazioni in cui la definizione di un comportamento sarebbe allo stesso tempo costosa e inutile . Un giudizio che tali implementazioni potrebbero esistere non ha alcun senso, forma o forma, implica alcun giudizio sull'utilità di supportare un comportamento definito su altre piattaforme.

Sfortunatamente, a partire dalla metà degli anni '90, i redattori di compilatori hanno iniziato a interpretare la mancanza di mandati comportamentali come un giudizio sul fatto che le garanzie comportamentali non valgono il costo anche nei campi applicativi in ​​cui sono vitali, e persino su sistemi in cui costano praticamente nulla. Invece di trattare UB come un invito a esercitare un ragionevole giudizio, gli scrittori di compilatori hanno iniziato a considerarlo come una scusa per non farlo.

Ad esempio, dato il seguente codice:

int scaled_velocity(int v, unsigned char pow)
{
  if (v > 250)
    v = 250;
  if (v < -250)
    v = -250;
  return v << pow;
}

un'implementazione a due complementi non dovrebbe richiedere alcuno sforzo per trattare l'espressione v << pow come spostamento a complemento a due, senza considerare se v fosse positivo o negativo.

La filosofia preferita da alcuni degli odierni scrittori di compilatori, tuttavia, suggerirebbe che poiché v può essere negativo solo se il programma sta per entrare in un comportamento indefinito, non c'è motivo di avere il clip del programma nell'intervallo negativo di v . Anche se lo spostamento a sinistra dei valori negativi era supportato su ogni singolo compilatore di significatività, e una grande quantità di codice esistente si basa su quel comportamento, la filosofia moderna interpreterebbe il fatto che lo standard dice che i valori negativi a spostamento di sinistra sono UB come sottintendendo che gli scrittori di compilatori dovrebbero sentirsi liberi di ignorarlo.




C ++ standard n3337 § 1.3.10 comportamento definito dall'implementazione

comportamento, per un programma ben strutturato costruire e correggere i dati, che dipende dall'implementazione e che ogni documento di implementazione

A volte C ++ Standard non impone un comportamento particolare su alcuni costrutti, ma afferma invece che un particolare comportamento ben definito deve essere scelto e descritto da un'implementazione particolare (versione della libreria). Quindi l'utente può ancora sapere esattamente come si comporterà il programma anche se Standard non lo descrive.

C ++ standard n3337 § 1.3.24 comportamento non definito

comportamento per il quale questo Standard Internazionale non impone alcun requisito [Nota: ci si può aspettare un comportamento indefinito quando questo Standard Internazionale omette qualsiasi definizione esplicita di comportamento o quando un programma utilizza un costrutto errato o dati errati. Il comportamento indefinito ammissibile va dall'ignorare completamente la situazione con risultati imprevedibili, a comportarsi durante la traduzione o l'esecuzione del programma in un modo documentato caratteristico dell'ambiente (con o senza emissione di un messaggio diagnostico), a terminare una traduzione o un'esecuzione (con l'emissione di un messaggio diagnostico). Molti costrutti di programma errati non generano un comportamento indefinito; devono essere diagnosticati. - nota finale]

Quando il programma incontra un costrutto che non è definito secondo lo standard C ++, è autorizzato a fare tutto ciò che vuole (magari mandare una mail a me o magari mandare una email a te o forse ignorare completamente il codice).

C ++ standard n3337 § 1.3.25 comportamento non specificato

comportamento, per un programma ben strutturato costruire e correggere i dati, che dipende dall'implementazione [Nota: l'implementazione non è richiesta per documentare quale comportamento si verifica. La gamma di comportamenti possibili è solitamente delineata da questo standard internazionale. - nota finale]

Lo standard C ++ non impone un comportamento particolare su alcuni costrutti, ma afferma invece che un particolare, ben definito comportamento deve essere scelto ( bot non necessario descritto ) da un'implementazione particolare (versione della libreria). Quindi, nel caso in cui non sia stata fornita alcuna descrizione, può essere difficile per l'utente sapere esattamente come si comporterà il programma.




Il comportamento non definito e il comportamento non specificato ne hanno una breve descrizione.

Il loro riassunto finale:

Per riassumere, un comportamento non specificato è di solito qualcosa di cui non dovresti preoccuparti, a meno che il tuo software non sia richiesto per essere portabile. Viceversa, il comportamento non definito è sempre indesiderabile e non dovrebbe mai verificarsi.




Dal documento ufficiale C Rationale

I termini comportamento non specificato , comportamento non definito e comportamento definito dall'implementazione sono usati per categorizzare il risultato di programmi di scrittura le cui proprietà lo Standard non ha o non può descrivere completamente. L'obiettivo dell'adozione di questa categorizzazione è di consentire una certa varietà tra le implementazioni che consentono alla qualità dell'implementazione di essere una forza attiva nel mercato e di consentire alcune estensioni popolari, senza rimuovere il prestigio della conformità allo standard. L'Appendice F di Standard cataloga quei comportamenti che rientrano in una di queste tre categorie.

Il comportamento non specificato fornisce all'installatore un po 'di libertà nella traduzione dei programmi. Questa latitudine non si estende fino a non riuscire a tradurre il programma.

Il comportamento non definito consente alla licenza implementor di non rilevare alcuni errori di programma difficili da diagnosticare. Identifica inoltre le aree di possibile estensione linguistica conforme: l'implementatore può aumentare la lingua fornendo una definizione del comportamento ufficialmente indefinito.

Il comportamento definito dall'implementazione offre a un implementatore la libertà di scegliere l'approccio appropriato, ma richiede che questa scelta sia spiegata all'utente. I comportamenti definiti come definiti dall'implementazione sono generalmente quelli in cui un utente può prendere decisioni di codifica significative in base alla definizione dell'implementazione. Gli implementatori dovrebbero tenere presente questo criterio al momento di decidere in che misura una definizione di implementazione dovrebbe essere estesa. Come con un comportamento non specificato, il semplice fatto di non tradurre la sorgente che contiene il comportamento definito dall'implementazione non è una risposta adeguata.




Bene, questo è fondamentalmente un semplice copia-incolla dallo standard

3.4.1 1 comportamento definito dall'implementazione comportamento non specificato in cui ogni implementazione documenta come viene effettuata la scelta

2 ESEMPIO Un esempio di comportamento definito dall'implementazione è la propagazione del bit di ordine superiore quando un intero con segno è spostato a destra.

3.4.3 1 comportamento non definito del comportamento , in seguito all'uso di un costrutto di programma non portatile o errato o di dati errati, per i quali questo Standard internazionale non impone requisiti

2 NOTA I possibili comportamenti indefiniti vanno dall'ignorare completamente la situazione con risultati imprevedibili, a comportarsi durante la traduzione o l'esecuzione del programma in un modo documentato caratteristico dell'ambiente (con o senza emissione di un messaggio diagnostico), a terminare una traduzione o un'esecuzione (con l'emissione di un messaggio diagnostico).

3 ESEMPIO Un esempio di comportamento non definito è il comportamento sull'overflow dei numeri interi.

3.4.4 1 uso del comportamento non specificato di un valore non specificato o altro comportamento in cui questo Standard Internazionale offre due o più possibilità e non impone ulteriori requisiti su cui viene scelto in ogni caso

2 ESEMPIO Un esempio di comportamento non specificato è l'ordine in cui vengono valutati gli argomenti di una funzione.




Il comportamento non definito è uno di quegli aspetti del linguaggio C e C ++ che può sorprendere per i programmatori che provengono da altre lingue (altri linguaggi cercano di nasconderlo meglio). Fondamentalmente, è possibile scrivere programmi C ++ che non si comportano in modo prevedibile, anche se molti compilatori C ++ non segnaleranno alcun errore nel programma!

Diamo un'occhiata ad un esempio classico:

#include <iostream>

int main()
{
    char* p = "hello!\n";   // yes I know, deprecated conversion
    p[0] = 'y';
    p[5] = 'w';
    std::cout << p;
}

La variabile p punta alla stringa letterale "hello!\n" , e le due assegnazioni seguenti tentano di modificare quella stringa letterale. Cosa fa questo programma? Secondo la sezione 2.14.5 paragrafo 11 dello standard C ++, richiama il comportamento non definito :

L'effetto del tentativo di modificare una stringa letterale non è definito.

Riesco a sentire persone che urlano "Ma aspetta, posso compilare questo problema senza ottenere l'output yellow " o "Cosa intendi per non definito, i letterali stringa sono memorizzati nella memoria di sola lettura, quindi il primo tentativo di assegnazione si traduce in un core dump" . Questo è esattamente il problema con un comportamento indefinito. Fondamentalmente, lo standard consente a qualsiasi cosa di accadere una volta che si invoca un comportamento indefinito (anche demoni nasali). Se c'è un comportamento "corretto" secondo il tuo modello mentale del linguaggio, quel modello è semplicemente sbagliato; Lo standard C ++ ha il solo voto, periodo.

Altri esempi di comportamento non definito includono l'accesso a un array oltre i suoi limiti, il dereferenziamento del puntatore nullo , l' accesso agli oggetti dopo il loro ciclo di vita o la scrittura di espressioni presumibilmente intelligenti come i++ + ++i .

La sezione 1.9 dello standard C ++ menziona anche i due fratelli meno pericolosi del comportamento non definito , il comportamento non specificato e il comportamento definito dall'implementazione :

Le descrizioni semantiche di questo standard internazionale definiscono una macchina astratta non parametrica parametrizzata.

Alcuni aspetti e operazioni della macchina astratta sono descritti in questo Standard Internazionale come definito dall'implementazione (ad esempio, sizeof(int) ). Questi costituiscono i parametri della macchina astratta. Ogni implementazione deve includere la documentazione che descrive le sue caratteristiche e il comportamento in questi aspetti.

Alcuni altri aspetti e operazioni della macchina astratta sono descritti in questo Standard Internazionale come non specificato (ad esempio, l'ordine di valutazione di argomenti per una funzione). Ove possibile, questo standard internazionale definisce un insieme di comportamenti consentiti. Questi definiscono gli aspetti non deterministici della macchina astratta.

Alcune altre operazioni sono descritte in questo standard internazionale come non definite (ad esempio, l'effetto di dereferenziare il puntatore nullo). [ Nota : questo Standard Internazionale non impone alcun requisito sul comportamento dei programmi che contengono comportamenti non definiti. - nota finale ]

Nello specifico, la sezione 1.3.24 afferma:

Il comportamento indefinito ammissibile va dall'ignorare completamente la situazione con risultati imprevedibili , a comportarsi durante la traduzione o l'esecuzione del programma in un modo documentato caratteristico dell'ambiente (con o senza emissione di un messaggio diagnostico), a terminare una traduzione o un'esecuzione (con l'emissione di un messaggio diagnostico).

Cosa puoi fare per evitare di imbattersi in comportamenti indefiniti? Fondamentalmente, devi leggere buoni libri in C ++ di autori che sanno di cosa stanno parlando. Avvitare tutorial su Internet. Vite bullschildt.




Implementazione definita-

I realizzatori desiderano, dovrebbero essere ben documentati, lo standard dà delle scelte ma è sicuro di compilare

Non specificato -

Come definito dall'implementazione ma non documentato

Non definito-

Tutto può succedere, prenditi cura di lui.




I costruttori aggiungono la conversione implicita. Per sopprimere questa conversione implicita è necessario dichiarare un costruttore con un parametro esplicito.

In C ++ 11 è anche possibile specificare un "tipo di operatore ()" con tale parola chiave here Con tali specifiche è possibile utilizzare l'operatore in termini di conversioni esplicite e inizializzazione diretta dell'oggetto.

PS Quando si utilizzano le trasformazioni definite da USER (tramite costruttori e operatore di conversione del tipo) è consentito un solo livello di conversioni implicite utilizzate. Ma puoi combinare queste conversioni con altre conversioni linguistiche

  • su ranghi integrali (char to int, float to double);
  • conversioni standard (int per raddoppiare);
  • convertire puntatori di oggetti in classe base e annullare *;






c++ c undefined-behavior c++-faq unspecified-behavior