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

StackOverflow https://stackoverflow.com/questions/367633

Question

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

Dis, comme:

a[i] = i++;

Était-ce utile?

La solution

pointeur

  • déréférencement d'un pointeur NULL
  • déréférencement d'un pointeur retourné par une « nouvelle » allocation de taille zéro
  • Utilisation des pointeurs sur des objets dont la durée de vie est terminée (par exemple, empiler des objets alloués ou objets supprimés)
  • déréférencement d'un pointeur qui n'a pas encore été définitivement initialisé
  • effectuer une opération arithmétique de pointeur qui donne un résultat en dehors des limites (soit au-dessus ou au-dessous) d'un tableau.
  • déréférencement du pointeur à un emplacement au-delà de la fin d'un tableau.
  • Conversion des pointeurs vers des objets de types incompatibles
  • En utilisant des tampons pour copier memcpy se chevauchent .

Dépassements de tampon

  • lecture ou d'écriture à un objet ou d'une matrice avec un décalage qui est négatif, ou au-delà de la taille de cet objet (pile / débordement de pile)

Entier Débordements

  • Signé débordement d'entier
  • L'évaluation d'une expression qui n'est pas mathématiquement définie
  • Les valeurs de décalage gauche par une quantité négative (décalages à droite par des montants négatifs sont définis mise en œuvre)
  • Déplacement des valeurs d'une quantité supérieure ou égale au nombre de bits dans le nombre (par exemple int64_t i = 1; i <<= 72 est défini)

Types, Moulage et Const

  • Coulée une valeur numérique en une valeur qui ne peut pas être représenté par le type de cible (soit directement, soit par l'intermédiaire d'static_cast)
  • En utilisant une variable automatique avant qu'il ait été définitivement affecté (par exemple, int i; i++; cout << i;)
  • Utilisation de la valeur d'un objet de type autre que ou volatile à la réception sig_atomic_t d'un signal
  • tentant de modifier une chaîne de caractères ou tout autre objet const au cours de sa durée de vie
  • Concaténation une étroite avec une grande chaîne littérale pendant prétraiter

Fonction et modèle

  • Pas de retour d'une valeur d'une fonction de valeur-retour (directement ou par l'écoulement hors d'un bloc try)
  • Plusieurs définitions différentes pour la même entité (classe, modèle, énumération, fonction en ligne, la fonction d'organe statique, etc.)
  • récursion infinie dans l'instanciation des modèles
  • Appel d'une fonction en utilisant différents paramètres ou de liaison pour les paramètres et les liens que la fonction est définie comme l'utilisation.

POO

  • destructions en cascade d'objets avec une durée de stockage statique
  • Le résultat de l'affectation à des objets se chevauchant partiellement
  • récursivement rentrant dans une fonction au cours de l'initialisation des objets statiques
  • Faire fonction virtuelle appels à des fonctions virtuelles pures d'un objet à partir de son constructeur ou destructor
  • En se référant aux membres non statiques des objets qui ne sont pas construits ou ont déjà été détruites

fichier source et Prétraitement

  • Un fichier source non vide qui ne se termine pas avec une nouvelle ligne, ou se termine par une barre oblique inverse (avant C ++ 11)
  • Une barre oblique inverse suivie par un caractère qui ne fait pas partie des codes d'échappement spécifiés dans un caractère ou une chaîne constante (ce qui est défini par l'implémentation en C de 11).
  • Dépasser les limites de mise en œuvre (nombre de blocs imbriqués, le nombre de fonctions dans un programme, l'espace disponible pour la pile ...)
  • préprocesseur valeurs numériques qui ne peuvent pas être représentés par un long int
  • directive Prétraitement sur le côté gauche d'une définition de macro fonction comme
  • générer dynamiquement le jeton défini dans une #if expression

Pour être classé

  • Appeler la sortie lors de la destruction d'un programme avec la durée de stockage statique

Autres conseils

L'ordre que les paramètres de la fonction sont évalués est non spécifié comportement . (Cela ne fera pas votre plantage du programme, exploser ou commander des repas ... contrairement à non défini comportement .)

La seule exigence est que tous les paramètres doivent être entièrement évalués avant que la fonction est appelée.


// The simple obvious one.
callFunc(getA(),getB());

Peut être équivalent à ceci:

int a = getA();
int b = getB();
callFunc(a,b);

Ou ceci:

int b = getB();
int a = getA();
callFunc(a,b);

Il peut être; il est au compilateur. Le résultat peut la matière, en fonction des effets secondaires.

Le compilateur est libre de commander à nouveau les parties de l'évaluation d'une expression (en supposant que le sens est inchangé).

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 verrouillage à carreaux. Et une erreur facile à faire.

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 » parce que je crois qu'il est le seul où le comportement est indéterminé au moment de la compilation.

Affectation à une constante après décapage ness const utilisant const_cast<>:

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

Outre le comportement undefined , il y a aussi le comportement aussi méchant défini par l'implémentation .

comportement non défini se produit lorsqu'un programme fait quelque chose dont le résultat n'est pas spécifié par la norme.

comportement défini par l'implémentation est une action par un programme dont le résultat est pas défini par la norme, mais la mise en œuvre doit documenter. Un exemple est question de débordement de la pile « caractère », multi-octets littéraux Y at-il un compilateur C qui ne parvient pas à compiler ce? .

comportement défini par l'implémentation que vous mord lorsque vous démarrez le portage (mais la mise à niveau vers la nouvelle version du compilateur est aussi le portage!)

Les variables ne peuvent être mis à jour une fois dans une expression (techniquement une fois entre les 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 est à la section 5.2.4.1 de la spécification C. Voici quelques-unes;

  • 127 paramètres dans une définition de fonction de fi
  • 127 arguments dans un appel de fonction
  • 127 paramètres dans une définition macro de fi
  • 127 arguments dans une invocation macro
  • 4095 caractères dans une ligne de source logique
  • 4095 caractères dans une chaîne de caractères littéral ou large chaîne littérale (après concaténation)
  • 65535 octets dans un objet (dans un environnement hébergé uniquement)
  • niveaux de 15nesting pour les #include fi
  • 1023 cas étiquettes pour un commutateur déclaration (sauf celles pour instructions switch anynested)

Je suis en fait un peu surpris à la limite de 1023 étiquettes de cas pour une instruction switch, je peux forsee que dépassement pour le code généré / / lex parseurs assez easially.

Si ces limites sont dépassées, vous avez un comportement non défini (se bloque, les failles de sécurité, etc ...).

Bon, je sais que c'est de la spécification C, mais les parts C de ces supports de base.

Utilisation de memcpy pour copier entre les régions de chevauchement de mémoire. Par exemple:

char a[256] = {};
memcpy(a, a, sizeof(a));

Le comportement est indéfini selon la norme C, qui est englobée par le 03 C ++ standard.

7.21.2.1 La fonction memcpy

  

Synopsis

     

1 / #include void * memcpy (void * restreindre s1, const   void * restreindre s2, size_t n);

     

Description de

     

2 / La fonction memcpy   copies n caractères de l'objet pointé par s2 dans l'objet   pointée par s1. Si la copie a lieu entre les objets qui se chevauchent,   le comportement est indéfini. Retours 3 La fonction memcpy retourne la   La valeur de s1.

7.21.2.2 La fonction memmove

  

Synopsis

     

1 #include void * memmove (void * s1, const void * s2, size_t   n);

     

Description de

     

2 La fonction copie de memmove n caractères de l'objet pointé   par s2 dans l'objet pointé par s1. Copie a lieu comme si le   n caractères de l'objet pointé par s2 sont d'abord copiées dans un   tableau temporaire de n caractères qui ne recouvre pas les objets   pointée par s1 et s2, puis les n caractères du temporaire   réseau sont copiés dans l'objet pointé par s1. Retours

     

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 est dépendant de la plateforme.

objets Namespace niveau dans une unité de compilation différentes ne doivent jamais dépendent les uns des autres pour l'initialisation, parce que leur ordre d'initialisation est définie.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top