web-dev-qa-db-fra.com

Quels sont tous les comportements non définis communs qu'un programmeur C ++ devrait connaître?

Quels sont tous les comportements non définis communs qu'un programmeur C++ devrait connaître?

Dites, comme:

a[i] = i++;
201
yesraaj

Aiguille

  • Déréférencement d'un pointeur NULL
  • Déréférencement d'un pointeur renvoyé par une "nouvelle" allocation de taille zéro
  • Utilisation de pointeurs sur des objets dont la durée de vie est terminée (par exemple, empiler des objets alloués ou des objets supprimés)
  • Déréférencement d'un pointeur qui n'a pas encore été définitivement initialisé
  • Effectuer une arithmétique de pointeur qui produit un résultat en dehors des limites (supérieures ou inférieures) d'un tableau.
  • Déréférencement du pointeur à un emplacement situé au-delà de la fin d'un tableau.
  • Conversion de pointeurs en objets de types incompatibles
  • tilisation de memcpy pour copier des tampons se chevauchant .

Débordement de tampon

  • Lecture ou écriture sur un objet ou un tableau avec un décalage négatif ou supérieur à la taille de cet objet (débordement pile/tas)

Débordements entiers

  • Débordement d'entier signé
  • Evaluer une expression qui n'est pas définie mathématiquement
  • Les valeurs décalées à gauche d'un montant négatif (les décalages à droite de montants négatifs sont définis par la mise en œuvre)
  • Décalage des valeurs d'un montant supérieur ou égal au nombre de bits du nombre (par exemple, int64_t i = 1; i <<= 72 n'est pas défini)

Types, Cast et Const

  • Conversion d'une valeur numérique en une valeur qui ne peut pas être représentée par le type de cible (directement ou via static_cast)
  • Utiliser une variable automatique avant qu’elle ait été définitivement affectée (par exemple, int i; i++; cout << i;)
  • Utiliser la valeur de tout objet de type autre que volatile ou sig_atomic_t à la réception d'un signal
  • Tentative de modification d'un littéral de chaîne ou de tout autre objet const au cours de sa durée de vie
  • Concaténer un étroit avec un littéral de chaîne large lors du prétraitement

Fonction et modèle

  • Ne pas renvoyer une valeur d'une fonction renvoyant une valeur (directement ou en s'éloignant d'un bloc try)
  • Plusieurs définitions différentes pour la même entité (classe, modèle, énumération, fonction en ligne, fonction membre statique, etc.)
  • Récursion infinie dans l'instanciation des modèles
  • Appel d'une fonction à l'aide de différents paramètres ou liaison aux paramètres et à la liaison définie par la fonction.

OOP

  • Destructions en cascade d'objets avec une durée de stockage statique
  • Résultat de l'affectation d'objets se chevauchant partiellement
  • Ré-entrer récursivement dans une fonction lors de l'initialisation de ses objets statiques
  • Faire des appels de fonctions virtuelles aux fonctions virtuelles pures d'un objet à partir de son constructeur ou de son destructeur
  • Référence à des membres non statiques d'objets qui n'ont pas été construits ou qui ont déjà été détruits

Fichier source et prétraitement

  • Un fichier source non vide qui ne se termine pas par une nouvelle ligne ou une barre oblique inverse (antérieure à C++ 11)
  • Une barre oblique inversée suivie d'un caractère ne faisant pas partie des codes d'échappement spécifiés dans une constante de caractère ou de chaîne (défini par C++ 11).
  • Dépassement des limites d'implémentation (nombre de blocs imbriqués, nombre de fonctions dans un programme, espace de pile disponible ...)
  • Valeurs numériques du préprocesseur qui ne peuvent pas être représentées par un long int
  • Directive de prétraitement à gauche d'une définition de macro de type fonction
  • Générer dynamiquement le jeton défini dans un #if expression

À classer

  • Appel de sortie lors de la destruction d'un programme avec une durée de stockage statique
233
Diomidis Spinellis

L'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.

31
Martin York

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.
27
Martin York

Affectation à une constante après l'extraction de constness à l'aide de const_cast<>:

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

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.

5
Daniel Earwicker

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!)

5
Constantin

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.
4
Martin York

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;

  • 127 paramètres dans une définition de fonction
  • 127 arguments dans un appel de fonction
  • 127 paramètres dans une macro définition
  • 127 arguments en une invocation de macro
  • 4095 caractères dans une ligne source logique
  • 4095 caractères dans un littéral de chaîne de caractères ou littéral de chaîne large (après concaténation)
  • 65535 octets dans un objet (dans un environnement hébergé uniquement)
  • 15 niveaux de suppression pour le nombre de fichiers inclus
  • 1023 étiquettes de cas pour une instruction switch (à l'exclusion de celles pour les instructions switch imbriquées)

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.

3
RandomNickName42

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.

2
yesraaj

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.

2
JaredPar

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.

7.21.2.1 La fonction memcpy

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.

7.21.2.2 La fonction memmove

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.

2
John Dibling