C++
Comportamento non specificato
Ricerca…
Osservazioni
Se il comportamento di un costrutto non è specificato, lo standard pone alcuni vincoli sul comportamento, ma lascia una certa libertà all'implementazione, che non è necessaria per documentare ciò che accade in una determinata situazione. Contrasta con il comportamento definito dall'implementazione , in cui è richiesta l'implementazione per documentare ciò che accade e un comportamento indefinito, in cui qualsiasi cosa può accadere.
Ordine di inizializzazione di globals su TU
Mentre all'interno di un'unità di traduzione viene specificato l'ordine di inizializzazione delle variabili globali, l'ordine di inizializzazione tra le unità di traduzione non è specificato.
Quindi programma con i seguenti file
foo.cpp
#include <iostream> int dummyFoo = ((std::cout << "foo"), 0);
bar.cpp
#include <iostream> int dummyBar = ((std::cout << "bar"), 0);
main.cpp
int main() {}
potrebbe produrre come output:
foobar
o
barfoo
Ciò potrebbe portare all'ordine di inizializzazione statica Fiasco .
Valore di un enum fuori limite
Se un enum di ambito viene convertito in un tipo integrale troppo piccolo per contenere il suo valore, il valore risultante non è specificato. Esempio:
enum class E {
X = 1,
Y = 1000,
};
// assume 1000 does not fit into a char
char c1 = static_cast<char>(E::X); // c1 is 1
char c2 = static_cast<char>(E::Y); // c2 has an unspecified value
Inoltre, se un numero intero viene convertito in un enum e il valore dell'intero è al di fuori dell'intervallo dei valori dell'enumerazione, il valore risultante non è specificato. Esempio:
enum Color {
RED = 1,
GREEN = 2,
BLUE = 3,
};
Color c = static_cast<Color>(4);
Tuttavia, nel prossimo esempio, il comportamento non è non specificato, poiché il valore di origine è compreso nell'intervallo dell'enumerazione, sebbene non sia uguale a tutti gli enumeratori:
enum Scale {
ONE = 1,
TWO = 2,
FOUR = 4,
};
Scale s = static_cast<Scale>(3);
Qui s
avrà il valore 3 e sarà disuguale a ONE
, TWO
e FOUR
.
Cast statico dal valore di bogus void *
Se un valore void*
viene convertito in un puntatore al tipo di oggetto, T*
, ma non è allineato correttamente per T
, il valore del puntatore risultante non è specificato. Esempio:
// Suppose that alignof(int) is 4
int x = 42;
void* p1 = &x;
// Do some pointer arithmetic...
void* p2 = static_cast<char*>(p1) + 2;
int* p3 = static_cast<int*>(p2);
Il valore di p3
non è specificato perché p2
non può puntare a un oggetto di tipo int
; il suo valore non è un indirizzo correttamente allineato.
Risultato di alcune reinterpret_cast conversioni
Il risultato di un reinterpret_cast
da un tipo di puntatore a un altro, o un tipo di riferimento a un altro, non è specificato. Esempio:
int f();
auto fp = reinterpret_cast<int(*)(int)>(&f); // fp has unspecified value
Il risultato di un reinterpret_cast
da un tipo di puntatore a un altro oggetto o un tipo di riferimento a un altro non è specificato. Esempio:
int x = 42;
char* p = reinterpret_cast<char*>(&x); // p has unspecified value
Tuttavia, con la maggior parte dei compilatori, questo era equivalente a static_cast<char*>(static_cast<void*>(&x))
quindi il puntatore risultante p
puntava al primo byte di x
. Questo è stato reso il comportamento standard in C ++ 11. Vedi la conversione della punteggiatura di tipo per maggiori dettagli.
Risultato di alcuni confronti tra puntatori
Se due puntatori vengono confrontati utilizzando <
, >
, <=
o >=
, il risultato non è specificato nei seguenti casi:
I puntatori indicano diversi array. (Un oggetto non array è considerato un array di dimensioni 1.)
int x; int y; const bool b1 = &x < &y; // unspecified int a[10]; const bool b2 = &a[0] < &a[1]; // true const bool b3 = &a[0] < &x; // unspecified const bool b4 = (a + 9) < (a + 10); // true // note: a+10 points past the end of the array
I puntatori indicano lo stesso oggetto, ma per i membri con controllo di accesso diverso.
class A { public: int x; int y; bool f1() { return &x < &y; } // true; x comes before y bool f2() { return &x < &z; } // unspecified private: int z; };
Spazio occupato da un riferimento
Un riferimento non è un oggetto e, a differenza di un oggetto, non è garantito che occupi alcuni byte contigui di memoria. Lo standard lascia non specificato se un riferimento richiede alcuna memoria. Un certo numero di caratteristiche del linguaggio cospirano per rendere impossibile esaminare in maniera portabile qualsiasi memoria che il riferimento potrebbe occupare:
- Se
sizeof
viene applicato a un riferimento, restituisce la dimensione del tipo di riferimento, senza fornire informazioni sul fatto che il riferimento occupi qualsiasi spazio di archiviazione. - Le matrici di riferimenti sono illegali, quindi non è possibile esaminare gli indirizzi di due elementi consecutivi di un ipotetico riferimento di matrici al fine di determinare la dimensione di un riferimento.
- Se viene preso l'indirizzo di un riferimento, il risultato è l'indirizzo del referente, quindi non possiamo ottenere un puntatore al riferimento stesso.
- Se una classe ha un membro di riferimento, il tentativo di estrarre l'indirizzo di quel membro usando
offsetof
produce un comportamento indefinito poiché tale classe non è una classe di layout standard. - Se una classe ha un membro di riferimento, la classe non è più un layout standard, quindi tenta di accedere a qualsiasi dato utilizzato per memorizzare i risultati del riferimento in un comportamento indefinito o non specificato.
In pratica, in alcuni casi una variabile di riferimento può essere implementata in modo simile a una variabile puntatore e quindi occupa la stessa quantità di memoria di un puntatore, mentre in altri casi un riferimento non può occupare spazio poiché può essere ottimizzato. Ad esempio, in:
void f() {
int x;
int& r = x;
// do something with r
}
il compilatore è libero di trattare semplicemente r
come alias per x
e sostituire tutte le occorrenze di r
nel resto della funzione f
con x
, e non allocare alcuna memoria per contenere r
.
Ordine di valutazione degli argomenti della funzione
Se una funzione ha più argomenti, non è specificato in quale ordine vengono valutati. Il seguente codice potrebbe stampare x = 1, y = 2
o x = 2, y = 1
ma non specificato.
int f(int x, int y) {
printf("x = %d, y = %d\n", x, y);
}
int get_val() {
static int x = 0;
return ++x;
}
int main() {
f(get_val(), get_val());
}
In C ++ 17, l'ordine di valutazione degli argomenti delle funzioni rimane non specificato.
Tuttavia, ogni argomento di funzione viene completamente valutato e l'oggetto chiamante è garantito valutato prima che gli argomenti di una funzione siano.
struct from_int {
from_int(int x) { std::cout << "from_int (" << x << ")\n"; }
};
int make_int(int x){ std::cout << "make_int (" << x << ")\n"; return x; }
void foo(from_int a, from_int b) {
}
void bar(from_int a, from_int b) {
}
auto which_func(bool b){
std::cout << b?"foo":"bar" << "\n";
return b?foo:bar;
}
int main(int argc, char const*const* argv) {
which_func( true )( make_int(1), make_int(2) );
}
questo deve stampare:
bar
make_int(1)
from_int(1)
make_int(2)
from_int(2)
o
bar
make_int(2)
from_int(2)
make_int(1)
from_int(1)
potrebbe non stampare la bar
dopo una qualsiasi delle make
o from
, e potrebbe non stampare:
bar
make_int(2)
make_int(1)
from_int(2)
from_int(1)
o simili. Prima della bar
stampa C ++ 17 dopo che make_int
s era legale, come facevano entrambi i make_int
s prima di fare qualsiasi from_int
s.
Spostato dallo stato della maggior parte delle classi di libreria standard
Tutti i contenitori di libreria standard vengono lasciati in uno stato valido ma non specificato dopo essere stati spostati da. Ad esempio, nel codice seguente, v2
conterrà {1, 2, 3, 4}
dopo lo spostamento, ma non è garantito che v1
sia vuoto.
int main() {
std::vector<int> v1{1, 2, 3, 4};
std::vector<int> v2 = std::move(v1);
}
Alcune classi hanno uno stato spostato con precisione definito. Il caso più importante è quello di std::unique_ptr<T>
, che è garantito come null dopo essere stato spostato da.