C++
Comportement non spécifié
Recherche…
Remarques
Si le comportement d'une construction n'est pas spécifié, la norme impose des contraintes au comportement, mais laisse une certaine liberté à l'implémentation, ce qui n'est pas nécessaire pour documenter ce qui se passe dans une situation donnée. Cela contraste avec le comportement défini par l' implémentation , dans lequel l'implémentation est nécessaire pour documenter ce qui se passe, et un comportement non défini, dans lequel tout peut arriver.
Ordre d'initialisation des globales à travers TU
Alors qu'au sein d'une unité de traduction, l'ordre d'initialisation des variables globales est spécifié, l'ordre d'initialisation entre les unités de traduction n'est pas spécifié.
Donc programme avec les fichiers suivants
foo.cpp
#include <iostream> int dummyFoo = ((std::cout << "foo"), 0);
bar.cpp
#include <iostream> int dummyBar = ((std::cout << "bar"), 0);
main.cpp
int main() {}
pourrait produire comme sortie:
foobar
ou
barfoo
Cela peut conduire à un Fiasco d'ordre d'initialisation statique .
Valeur d'un enum hors gamme
Si un enum de portée est converti en un type intégral trop petit pour contenir sa valeur, la valeur résultante n'est pas spécifiée. Exemple:
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
De plus, si un entier est converti en enum et que la valeur de l'entier est en dehors de la plage des valeurs de l'énumération, la valeur résultante n'est pas spécifiée. Exemple:
enum Color {
RED = 1,
GREEN = 2,
BLUE = 3,
};
Color c = static_cast<Color>(4);
Cependant, dans l'exemple suivant, le comportement n'est pas spécifié, car la valeur source est comprise dans la plage de l'énumération, bien qu'elle soit inégale pour tous les énumérateurs:
enum Scale {
ONE = 1,
TWO = 2,
FOUR = 4,
};
Scale s = static_cast<Scale>(3);
s
aura la valeur 3 et sera inégale à ONE
, TWO
et FOUR
.
Cast statique à partir de la valeur nulle et bidon
Si une valeur void*
est convertie en un pointeur vers le type d'objet, T*
, mais n'est pas correctement alignée pour T
, la valeur du pointeur obtenue n'est pas spécifiée. Exemple:
// 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);
La valeur de p3
n'est pas spécifiée car p2
ne peut pas pointer vers un objet de type int
; sa valeur n'est pas une adresse correctement alignée.
Résultat de certaines conversions réinterprétées
Le résultat d'un reinterpret_cast
d'un type de pointeur de fonction à un autre, ou d'un type de référence de fonction à un autre, n'est pas spécifié. Exemple:
int f();
auto fp = reinterpret_cast<int(*)(int)>(&f); // fp has unspecified value
Le résultat d'un reinterpret_cast
d'un type de pointeur d'objet à un autre, ou d'un type de référence d'objet à un autre, n'est pas spécifié. Exemple:
int x = 42;
char* p = reinterpret_cast<char*>(&x); // p has unspecified value
Cependant, avec la plupart des compilateurs, cela équivalait à static_cast<char*>(static_cast<void*>(&x))
donc le pointeur résultant p
indiquait le premier octet de x
. Cela a été fait le comportement standard en C ++ 11. Voir la conversion de types pour plus de détails.
Résultat de certaines comparaisons de pointeurs
Si deux pointeurs sont comparés en utilisant <
, >
, <=
ou >=
, le résultat n'est pas spécifié dans les cas suivants:
Les pointeurs pointent vers différents tableaux. (Un objet non-tableau est considéré comme un tableau de taille 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
Les pointeurs pointent vers le même objet, mais vers des membres avec un contrôle d'accès différent.
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; };
Espace occupé par une référence
Une référence n'est pas un objet et contrairement à un objet, il n'est pas garanti qu'elle occupe certains octets de mémoire contigus. La norme ne précise pas si une référence nécessite un stockage. Un certain nombre de caractéristiques du langage se compliquent pour qu’il soit impossible d’examiner de manière portable tout stockage que la référence pourrait occuper:
- Si
sizeof
est appliqué à une référence, il retourne la taille du type référencé, ne donnant ainsi aucune information sur le fait de savoir si la référence occupe un stockage. - Les tableaux de références sont illégaux, il n'est donc pas possible d'examiner les adresses de deux éléments consécutifs d'une référence hypothétique de tableaux afin de déterminer la taille d'une référence.
- Si l'adresse d'une référence est prise, le résultat est l'adresse du référent, nous ne pouvons donc pas obtenir un pointeur sur la référence elle-même.
- Si une classe possède un membre de référence, la tentative d'extraction de l'adresse de ce membre à l'aide de
offsetof
un comportement indéfini, car cette classe n'est pas une classe d'agencement standard. - Si une classe a un membre de référence, la classe n'est plus une présentation standard. Par conséquent, les tentatives d'accès aux données utilisées pour stocker la référence entraînent un comportement indéfini ou non spécifié.
En pratique, dans certains cas, une variable de référence peut être implémentée de manière similaire à une variable de pointeur et occuper ainsi la même quantité de stockage qu'un pointeur, alors que dans d'autres cas, une référence peut ne pas occuper d'espace car elle peut être optimisée. Par exemple, dans:
void f() {
int x;
int& r = x;
// do something with r
}
le compilateur est libre de traiter simplement r
comme un alias pour x
et remplacer toutes les occurrences de r
dans le reste de la fonction f
à x
, et d' allouer un stockage pour contenir r
.
Ordre d'évaluation des arguments de fonction
Si une fonction a plusieurs arguments, l'ordre dans lequel ils sont évalués n'est pas spécifié. Le code suivant pourrait imprimer x = 1, y = 2
ou x = 2, y = 1
mais il n'est pas spécifié lequel.
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());
}
En C ++ 17, l'ordre d'évaluation des arguments de fonction reste indéterminé.
Cependant, chaque argument de fonction est complètement évalué et l'objet appelant est garanti évalué avant tout argument de fonction.
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) );
}
cela doit imprimer:
bar
make_int(1)
from_int(1)
make_int(2)
from_int(2)
ou
bar
make_int(2)
from_int(2)
make_int(1)
from_int(1)
il se peut qu’elle n’imprime pas de bar
après une make
ou from
bar
make_int(2)
make_int(1)
from_int(2)
from_int(1)
ou similaire. Avant C ++ 17 bar
impression après make_int
s était légale, comme le faisaient les deux make_int
s avant de faire des from_int
s.
Déplacé de l'état de la plupart des classes de bibliothèque standard
Tous les conteneurs de bibliothèque standard sont laissés dans un état valide mais non spécifié après avoir été déplacés. Par exemple, dans le code suivant, v2
contiendra {1, 2, 3, 4}
après le déplacement, mais il n'est pas garanti que v1
soit vide.
int main() {
std::vector<int> v1{1, 2, 3, 4};
std::vector<int> v2 = std::move(v1);
}
Certaines classes ont un état de déplacement défini avec précision. Le cas le plus important est celui de std::unique_ptr<T>
, qui est garanti nul après avoir été déplacé.