Quali sono tutti i comuni comportamenti indefiniti che un programmatore C ++ dovrebbe conoscere?
Dì, come:
a[i] = i++;
Quali sono tutti i comuni comportamenti indefiniti che un programmatore C ++ dovrebbe conoscere?
Dì, come:
a[i] = i++;
Risposte:
NULL
puntatorememcpy
per copiare buffer sovrapposti .int64_t i = 1; i <<= 72
definito)int i; i++; cout << i;
)volatile
o sig_atomic_t
alla ricezione di un segnalelong int
#if
un'espressioneL'ordine in cui vengono valutati i parametri della funzione è un comportamento non specificato . (Questo non farà schiantare, esplodere o ordinare la pizza del tuo programma ... diversamente dal comportamento indefinito .)
L'unico requisito è che tutti i parametri devono essere valutati completamente prima di chiamare la funzione.
Questo:
// The simple obvious one.
callFunc(getA(),getB());
Può essere equivalente a questo:
int a = getA();
int b = getB();
callFunc(a,b);
O questo:
int b = getB();
int a = getA();
callFunc(a,b);
Può essere uno dei due; dipende dal compilatore. Il risultato può essere importante, a seconda degli effetti collaterali.
Il compilatore è libero di riordinare le parti di valutazione di un'espressione (supponendo che il significato sia invariato).
Dalla domanda originale:
a[i] = i++;
// This expression has three parts:
(a) a[i]
(b) i++
(c) Assign (b) to (a)
// (c) is guaranteed to happen after (a) and (b)
// But (a) and (b) can be done in either order.
// See n2521 Section 5.17
// (b) increments i but returns the original value.
// See n2521 Section 5.2.6
// Thus this expression can be written as:
int rhs = i++;
int lhs& = a[i];
lhs = rhs;
// or
int lhs& = a[i];
int rhs = i++;
lhs = rhs;
Doppio bloccaggio controllato. E un semplice errore da fare.
A* a = new A("plop");
// Looks simple enough.
// But this can be split into three parts.
(a) allocate Memory
(b) Call constructor
(c) Assign value to 'a'
// No problem here:
// The compiler is allowed to do this:
(a) allocate Memory
(c) Assign value to 'a'
(b) Call constructor.
// This is because the whole thing is between two sequence points.
// So what is the big deal.
// Simple Double checked lock. (I know there are many other problems with this).
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
a = new A("Plop"); // (Point A).
}
}
a->doStuff();
// Think of this situation.
// Thread 1: Reaches point A. Executes (a)(c)
// Thread 1: Is about to do (b) and gets unscheduled.
// Thread 2: Reaches point B. It can now skip the if block
// Remember (c) has been done thus 'a' is not NULL.
// But the memory has not been initialized.
// Thread 2 now executes doStuff() on an uninitialized variable.
// The solution to this problem is to move the assignment of 'a'
// To the other side of the sequence point.
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
A* tmp = new A("Plop"); // (Point A).
a = tmp;
}
}
a->doStuff();
// Of course there are still other problems because of C++ support for
// threads. But hopefully these are addresses in the next standard.
Il mio preferito è "Ricorsione infinita nell'istanza di modelli" perché credo che sia l'unico in cui il comportamento indefinito si verifica al momento della compilazione.
Oltre al comportamento indefinito , esiste anche il comportamento ugualmente brutto definito dall'implementazione .
Il comportamento indefinito si verifica quando un programma fa qualcosa il cui risultato non è specificato dallo standard.
Il comportamento definito dall'implementazione è un'azione di un programma il cui risultato non è definito dallo standard, ma che l'implementazione è richiesta per documentare. Un esempio è "Letterali di caratteri multibyte", dalla domanda Stack Overflow Esiste un compilatore C che non riesce a compilare questo? .
Il comportamento definito dall'implementazione ti morde solo quando inizi il porting (ma anche l'aggiornamento alla nuova versione del compilatore sta effettuando il porting!)
Le variabili possono essere aggiornate solo una volta in un'espressione (tecnicamente una volta tra i punti sequenza).
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
Una comprensione di base dei vari limiti ambientali. L'elenco completo è nella sezione 5.2.4.1 della specifica C. Eccone alcuni;
In realtà sono rimasto un po 'sorpreso dal limite di 1023 etichette case per un'istruzione switch, posso prevedere che il superamento per codice generato / lex / parser sia abbastanza semplice.
Se questi limiti vengono superati, si ha un comportamento indefinito (arresti anomali, falle nella sicurezza, ecc ...).
Bene, so che proviene dalla specifica C, ma C ++ condivide questi supporti di base.
Utilizzare memcpy
per copiare tra aree di memoria sovrapposte. Per esempio:
char a[256] = {};
memcpy(a, a, sizeof(a));
Il comportamento non è definito secondo lo standard C, che è incluso nello standard C ++ 03.
Sinossi
1 / #include void * memcpy (void * restring s1, const void * restring s2, size_t n);
Descrizione
2 / La funzione memcpy copia n caratteri dall'oggetto puntato da s2 nell'oggetto puntato da s1. Se la copia avviene tra oggetti che si sovrappongono, il comportamento non è definito. Restituisce 3 La funzione memcpy restituisce il valore di s1.
Sinossi
1 #include void * memmove (void * s1, const void * s2, size_t n);
Descrizione
2 La funzione memmove copia n caratteri dall'oggetto puntato da s2 nell'oggetto puntato da s1. La copia avviene come se gli n caratteri dell'oggetto indicato da s2 vengano prima copiati in un array temporaneo di n caratteri che non si sovrappongono agli oggetti indicati da s1 e s2, quindi gli n caratteri dell'array temporaneo vengono copiati in l'oggetto indicato da s1. ritorna
3 La funzione memmove restituisce il valore di s1.
L'unico tipo per il quale C ++ garantisce una dimensione è char
. E la dimensione è 1. La dimensione di tutti gli altri tipi dipende dalla piattaforma.