Quels sont tous les comportements non définis communs qu'un programmeur C ++ devrait connaître?
Dites, comme:
a[i] = i++;
Quels sont tous les comportements non définis communs qu'un programmeur C ++ devrait connaître?
Dites, comme:
a[i] = i++;
Réponses:
NULL
pointeurmemcpy
pour copier des tampons qui se chevauchent .int64_t i = 1; i <<= 72
n'est pas défini)int i; i++; cout << i;
)volatile
ousig_atomic_t
à la réception d'un signallong int
#if
expressionL'ordre dans lequel les paramètres de fonction sont évalués est un comportement non spécifié . (Cela ne fera pas planter votre programme, exploser ou commander une pizza ... contrairement au comportement non défini .)
La seule exigence est que tous les paramètres doivent être entièrement évalués avant l'appel de la fonction.
Ce:
// 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);
Cela peut être soit; c'est au compilateur. Le résultat peut être important, selon les effets secondaires.
Le compilateur est libre de réorganiser les parties d'évaluation d'une expression (en supposant que la signification est inchangée).
De la question d'origine:
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;
Verrouillage à double contrôle. Et une simple erreur à 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.
Mon préféré est "Récursion infinie dans l'instanciation des modèles" car je crois que c'est le seul où le comportement indéfini se produit au moment de la compilation.
Outre le comportement non défini , il existe également un comportement défini par l' implémentation tout aussi désagréable .
Un comportement non défini se produit lorsqu'un programme fait 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 la question Stack Overflow Y a-t-il un compilateur C qui ne parvient pas à le compiler? .
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 un portage!)
Les variables ne peuvent être mises à jour qu'une seule fois dans une expression (techniquement une fois entre des points de 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 à la section 5.2.4.1 de la spécification C. Voici quelques-uns;
J'étais en fait un peu surpris de la limite de 1023 étiquettes de cas pour une instruction switch, je peux prévoir que le dépassement pour le code / lex / analyseurs généré assez facilement.
Si ces limites sont dépassées, vous avez un comportement indéfini (plantages, failles de sécurité, etc ...).
Bon, je sais que cela vient de la spécification C, mais C ++ partage ces supports de base.
Permet memcpy
de copier entre des régions de mémoire qui se chevauchent. Par exemple:
char a[256] = {};
memcpy(a, a, sizeof(a));
Le comportement n'est pas défini selon la norme C, qui est subsumée par 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. Renvoie 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 se déroule comme si les n caractères de l'objet pointé par s2 sont d'abord copiés dans un tableau temporaire de n caractères qui ne chevauchent pas les objets pointés par s1 et s2, puis les n caractères du tableau temporaire sont copiés dans l'objet pointé par s1. Retour
3 La fonction memmove renvoie la valeur de s1.
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.