Quels sont tous les comportements non définis communs qu'un programmeur C++ devrait connaître?
Dites, comme:
a[i] = i++;
NULL
memcpy
pour copier des tampons se chevauchant .int64_t i = 1; i <<= 72
n'est pas défini)int i; i++; cout << i;
)volatile
ou sig_atomic_t
à la réception d'un signallong int
#if
expressionL'ordre dans lequel les paramètres de fonction sont évalués est ( comportement non spécifié ). (Cela ne fera pas planter, exploser ou commander une pizza dans votre programme ... contrairement à comportement indéfini .)
La seule exigence est que tous les paramètres doivent être entièrement évalués avant que la fonction soit appelée.
Cette:
// The simple obvious one.
callFunc(getA(),getB());
Peut être équivalent à ceci:
int a = getA();
int b = getB();
callFunc(a,b);
Ou ca:
int b = getB();
int a = getA();
callFunc(a,b);
Ce peut être soit; c'est au compilateur. Le résultat peut avoir de l'importance, en fonction des effets secondaires.
Le compilateur est libre de réorganiser les parties d'évaluation d'une expression (en supposant que la signification ne soit pas modifiée).
De la question initiale:
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;
Double contrôle de verrouillage. Et une erreur facile à commettre.
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.
Affectation à une constante après l'extraction de const
ness à l'aide de const_cast<>
:
const int i = 10;
int *p = const_cast<int*>( &i );
*p = 1234; //Undefined
Mon préféré est "Récursion infinie dans l'instanciation de modèles" car je crois que c'est le seul cas où le comportement indéfini se produit au moment de la compilation.
Outre le comportement non défini , il existe également le comportement tout aussi méchant défini par la mise en oeuvre .
Un comportement indéfini se produit lorsqu'un programme effectue quelque chose dont le résultat n'est pas spécifié par la norme.
Le comportement défini par l'implémentation est une action d'un programme dont le résultat n'est pas défini par la norme, mais que l'implémentation doit documenter. Un exemple est "Littéraux de caractères multi-octets", de Stack Overflow question Y a-t-il un compilateur C qui ne compile pas cela?.
Le comportement défini par l'implémentation ne vous mord que lorsque vous démarrez le portage (mais la mise à niveau vers une nouvelle version du compilateur est également portage!)
Les variables ne peuvent être mises à jour qu'une fois dans une expression (une fois techniquement entre les points de la séquence).
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
Une compréhension de base des différentes limites environnementales. La liste complète se trouve dans la section 5.2.4.1 de la spécification C. Voici quelques-uns;
En fait, j’ai été un peu surpris par la limite de 1023 étiquettes de cas pour une instruction switch, je peux donc prévoir qu’il est dépassé assez facilement pour le code généré/Lex/les analyseurs.
Si ces limites sont dépassées, vous avez un comportement non défini (blocages, failles de sécurité, etc.).
Oui, je sais que cela provient de la spécification C, mais C++ partage ces supports de base.
Les objets au niveau de l'espace de noms dans des unités de compilation différentes ne doivent jamais dépendre les uns des autres pour l'initialisation, car leur ordre d'initialisation n'est pas défini.
Le seul type pour lequel C++ garantit une taille est char
. Et la taille est 1. La taille de tous les autres types dépend de la plate-forme.
Utiliser memcpy
pour copier entre des régions de mémoire superposées. Par exemple:
char a[256] = {};
memcpy(a, a, sizeof(a));
Le comportement n'est pas défini selon la norme C, qui est assimilée à la norme C++ 03.
Synopsis
1/#include void * memcpy (void * restreindre s1, const void * restreindre s2, size_t n);
La description
2/La fonction memcpy copie n caractères de l’objet pointé par s2 dans l’objet pointé par s1. Si la copie a lieu entre des objets qui se chevauchent, le comportement n'est pas défini. Retourne 3 La fonction memcpy renvoie la valeur de s1.
Synopsis
1 #include void * memmove (void * s1, const void * s2, size_t n);
La description
2 La fonction memmove copie n caractères de l’objet pointé par s2 dans l’objet pointé par s1. La copie a lieu comme si les n caractères de l'objet pointé par s2 étaient d'abord copiés dans un tableau temporaire de n caractères qui ne chevauchait pas les objets pointés par s1 et s2, puis les n caractères du tableau temporaire étaient copiés dans l'objet pointé par s1. Résultats
3 La fonction memmove renvoie la valeur de s1.