Quali sono tutti i comuni undefined comportamenti che un programmatore C++ dovrebbe conoscere?[chiuso]

StackOverflow https://stackoverflow.com/questions/367633

Domanda

Quali sono tutti i comuni undefined comportamenti che un programmatore C++ dovrebbe conoscere?

Dire, come:

a[i] = i++;

È stato utile?

Soluzione

Puntatore

  • La dereferenziazione di un NULL puntatore
  • La dereferenziazione di un puntatore restituito da un "nuovo" assegnazione di dimensione zero
  • Utilizzo di puntatori ad oggetti la cui vita è finita (per esempio, stack allocato oggetti o gli oggetti eliminati)
  • La dereferenziazione di un puntatore che non è ancora stato sicuramente inizializzato
  • Esecuzione di aritmetica dell'indicatore che si ottiene un risultato al di fuori dei confini (al di sopra o al di sotto) di un array.
  • La risoluzione del riferimento il puntatore alla posizione oltre la fine di una matrice.
  • La conversione di puntatori a oggetti di tipi incompatibili
  • Utilizzando memcpy per copiare buffer sovrapposti.

Buffer overflow

  • La lettura o la scrittura di un oggetto o di un array in un offset negativo, o oltre la dimensione dell'oggetto (stack/heap overflow)

Integer Overflow

  • Integer overflow
  • La valutazione di un'espressione che non è definito matematicamente
  • A sinistra spostamento di valori da un importo negativo (di destra si sposta da gli importi negativi sono l'attuazione di definizione)
  • Spostando i valori di un importo maggiore o uguale al numero di bit del numero (ad es. int64_t i = 1; i <<= 72 è undefined)

Tipi di Cast e Const

  • Il cast di un valore numerico in un valore che non può essere rappresentato dal tipo di destinazione (direttamente o per il tramite di static_cast)
  • Utilizzando una variabile automatica prima è stato definitivamente assegnato (ad esempio, int i; i++; cout << i;)
  • Utilizzando il valore di qualsiasi oggetto di tipo diverso volatile o sig_atomic_t alla ricezione di un segnale
  • Il tentativo di modificare una stringa o di un qualsiasi altro oggetto const durante la sua vita
  • La concatenazione di una stretta, con una vasta stringa letterale durante la pre-elaborazione

Funzione e Modello

  • Non la restituzione di un valore da un valore di ritorno della funzione (direttamente o che scorre fuori da un blocco try -)
  • Più definizioni diverse per la stessa entità (di classe, di modello, di enumerazione, di una funzione inline, funzione membro static, etc.)
  • Ricorsione infinita nella creazione di modelli
  • La chiamata di una funzione con parametri diversi o di collegamento con i parametri e il collegamento che la funzione è definita come utilizzo.

OOP

  • Cascata di distruzioni di oggetti con durata di archiviazione statica
  • Il risultato dell'assegnazione di parziale sovrapposizione di oggetti
  • Ricorsivamente ri-entrare in funzione durante l'inizializzazione degli oggetti statici
  • Facendo virtuale chiamate a funzione virtuale pura funzioni di un oggetto dal suo costruttore, o distruttore
  • Riferendosi ai membri non statici di oggetti che non sono stati costruiti o sono già stati eliminati

File sorgente e pre-elaborazione

  • Non vuota file di origine che non finisce con un "a capo", o termina con una barra rovesciata (prima di C++11)
  • Un backslash seguito da un carattere che non è parte di una specifica codici di escape in un carattere o una stringa costante (questo è definito dall'implementazione in C++11).
  • Superiore di attuazione limiti (numero di blocchi annidati, il numero di funzioni in un programma, disponibile spazio di stack ...)
  • Il preprocessore valori numerici che non può essere rappresentato da un long int
  • La pre-elaborazione della direttiva sul lato sinistro di una funzione-come la definizione di macro
  • La generazione dinamica definiti token in una #if espressione

Per essere classificati

  • La chiamata di uscire durante la distruzione di un programma con durata di archiviazione statica

Altri suggerimenti

L'ordine che i parametri della funzione sono valutati è non specificato comportamento.(Non è per questo che il programma va in crash, esplodere, o di ordinare la pizza...a differenza di undefined comportamento.)

L'unico requisito è che tutti i parametri devono essere valutati attentamente prima della funzione.


Questo:

// The simple obvious one.
callFunc(getA(),getB());

Può essere equivalente a questa:

int a = getA();
int b = getB();
callFunc(a,b);

O questo:

int b = getB();
int a = getA();
callFunc(a,b);

Può essere;il compilatore.Il risultato è importante, a seconda del lato effetti.

Il compilatore è libero di riordinare le parti di valutazione di un'espressione (assumendo il significato è invariato).

Dalla domanda iniziale:

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;

ricontrollato bloccaggio. E un errore facile 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 nella creazione di istanze di modelli", perché credo che sia l'unico in cui il comportamento non definito si verifica in fase di compilazione.

Assegnazione di un costante dopo strippaggio const Ness utilizzando const_cast<>:

const int i = 10; 
int *p =  const_cast<int*>( &i );
*p = 1234; //Undefined

Oltre comportamento indefinito , c'è anche l'altrettanto brutto comportamento definito dall'implementazione .

comportamento non definito si verifica quando un programma fa qualcosa il cui risultato non è specificato dalla norma.

comportamento Attuazione definito è un'azione da un programma il cui risultato non è definito dallo standard, ma che è richiesta l'attuazione di documentare. Un esempio è "letterali carattere multibyte", da Stack Overflow domanda C'è un compilatore C che non riesce a compilare questo? .

comportamento definito dall'implementazione ti morde solo quando si avvia porting (ma l'aggiornamento alla nuova versione del compilatore è anche il porting!)

Le variabili possono essere aggiornati solo una volta in un'espressione (tecnicamente volta tra i punti di sequenza).

int i =1;
i = ++i;

// Undefined. Assignment to 'i' twice in the same expression.

Una conoscenza di base dei vari limiti ambientali. L'elenco completo è nella sezione 5.2.4.1 della specifica C. Qui ci sono alcuni;

  • 127 parametri in una funzione de fi nizione
  • 127 argomenti in una chiamata di funzione
  • 127 parametri in una macro de fi nizione
  • 127 argomenti in un'invocazione macro
  • 4095 caratteri in una linea logica fonte
  • 4095 caratteri in una stringa di caratteri letterale o larga stringa letterale (dopo concatenazione)
  • 65535 byte in un oggetto (in un ambiente ospitato solo)
  • livelli 15nesting per #included fi
  • 1023 etichette per caso un interruttore dichiarazione (esclusi quelli per istruzioni switch anynested)

Sono stato in realtà un po 'sorpreso al limite di 1023 etichette di case di un'istruzione switch, posso forsee che essere superato per codice / Lex / parser generati abbastanza easially.

Se tali limiti vengono superati, si ha un comportamento indefinito (crash, problemi di sicurezza, ecc ...).

A destra, so che questo è dalla specifica C, ma le azioni C ++ questi supporti di base.

Uso memcpy copiare tra sovrapposizione delle regioni di memoria. Ad esempio:

char a[256] = {};
memcpy(a, a, sizeof(a));

Il comportamento non è definito in base al C standard, che viene sussunto da C ++ 03 standard.

7.21.2.1 La funzione memcpy

  

Sinossi

     

1 / #include void * memcpy (void * limitare s1, const   void * limitare s2, size_t n);

     

Descrizione

     

2 / La funzione memcpy   copie n caratteri dall'oggetto puntato da s2 nell'oggetto   puntato da s1. Se la copia avviene tra gli oggetti che si sovrappongono,   il comportamento è indefinito. Restituisce 3 La funzione restituisce il memcpy   il valore di s1.

7.21.2.2 La funzione memmove

  

Sinossi

     

1 #include void * memmove (void * s1, const void * s2, size_t   n);

     

Descrizione

     

2 La funzione copia memmove n caratteri dall'oggetto indicavano   da s2 nell'oggetto puntato da s1. La copia avviene come se la   n caratteri dall'oggetto puntato da s2 vengono prima copiati in un   array temporaneo di n caratteri non sovrapposto gli oggetti   puntato da S1 e S2, e poi la N caratteri dalla temporanea   matrice vengono copiati nell'oggetto puntato da s1. Restituisce

     

3 La funzione memmove restituisce il valore di s1.

L'unico tipo per il quale C ++ garantisce una dimensione è char. E la dimensione è 1. Le dimensioni di tutti gli altri tipi dipende dalla piattaforma.

oggetti a livello namespace in un diverse unità di compilazione dovrebbero mai dipendono l'uno dall'altro per l'inizializzazione, perché il loro ordine di inizializzazione è indefinito.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top