¿Cuáles son todos los comportamientos indefinidos comunes que un progtwigdor de C ++ debe conocer?

¿Cuáles son todos los comportamientos indefinidos comunes que un progtwigdor de C ++ debe conocer?

Diga, como:

a[i] = i++; 

Puntero

  • Desreferenciando un puntero NULL
  • Desreferenciando un puntero devuelto por una “nueva” asignación de tamaño cero
  • Usar punteros a objetos cuya vida útil ha finalizado (por ejemplo, astackr objetos asignados u objetos eliminados)
  • Desreferenciando un puntero que aún no se ha inicializado definitivamente
  • Realización de aritmética de puntero que arroja un resultado fuera de los límites (arriba o abajo) de una matriz.
  • Desreferenciando el puntero en una ubicación más allá del final de una matriz.
  • Conversión de punteros a objetos de tipos incompatibles
  • Usando memcpy para copiar los búferes superpuestos .

Desbordamientos de búfer

  • Lectura o escritura en un objeto o matriz en un desplazamiento que es negativo, o más allá del tamaño de ese objeto (desbordamiento de stack / stack)

Desbordamientos enteros

  • Desbordamiento de enteros con signo
  • Evaluar una expresión que no está definida matemáticamente
  • Los valores de desplazamiento a la izquierda en una cantidad negativa (los cambios a la derecha en cantidades negativas se definen en la implementación)
  • Cambiar los valores por una cantidad mayor o igual a la cantidad de bits en el número (por ejemplo, int64_t i = 1; i <<= 72 no está definido)

Tipos, Cast y Const

  • Convertir un valor numérico en un valor que no puede ser representado por el tipo de destino (ya sea directamente o mediante static_cast)
  • Usar una variable automática antes de asignarla definitivamente (p. Ej., int i; i++; cout << i; )
  • Usar el valor de cualquier objeto de tipo distinto de volatile o sig_atomic_t al recibir una señal
  • Intentando modificar un literal de cadena o cualquier otro objeto const durante su vida útil
  • Concatenar un estrecho con un literal de cuerda ancho durante el preprocesamiento

Función y plantilla

  • No devuelve un valor de una función de devolución de valor (directamente o al salir de un bloque de prueba)
  • Múltiples definiciones diferentes para la misma entidad (clase, plantilla, enumeración, función en línea, función de miembro estático, etc.)
  • Recursión infinita en la creación de instancias de plantillas
  • Llamar a una función usando diferentes parámetros o enlaces a los parámetros y enlaces que la función se define como usar.

OOP

  • Destrucciones en cascada de objetos con duración de almacenamiento estático
  • El resultado de la asignación a objetos parcialmente superpuestos
  • Reincorporar recursivamente una función durante la inicialización de sus objetos estáticos
  • Realización de llamadas de funciones virtuales a funciones virtuales puras de un objeto desde su constructor o destructor
  • Refiriéndose a los miembros no estáticos de objetos que no han sido construidos o que ya han sido destruidos

Archivo fuente y preprocesamiento

  • Un archivo fuente no vacío que no termina con una nueva línea o termina con una barra invertida (anterior a C ++ 11)
  • Una barra invertida seguida de un carácter que no forma parte de los códigos de escape especificados en un carácter o constante de cadena (esto está definido en la implementación en C ++ 11).
  • Exceder los límites de implementación (número de bloques nesteds, número de funciones en un progtwig, espacio de stack disponible ...)
  • Valores numéricos del preprocesador que no pueden ser representados por una long int
  • Directiva de preprocesamiento en el lado izquierdo de una definición de macro de tipo función
  • #if dinámicamente el token definido en una expresión #if

Para ser clasificado

  • Salida de llamada durante la destrucción de un progtwig con duración de almacenamiento estático

El orden en que se evalúan los parámetros de la función es un comportamiento no especificado . (Esto no hará que su progtwig falle, explote o pida pizza … a diferencia del comportamiento indefinido ).

El único requisito es que todos los parámetros se deben evaluar completamente antes de llamar a la función.


Esta:

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

Puede ser equivalente a esto:

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

O esto:

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

Puede ser cualquiera; depende del comstackdor. El resultado puede importar, dependiendo de los efectos secundarios.

El comstackdor puede reordenar las partes de evaluación de una expresión (suponiendo que el significado no se modifique).

De la pregunta original:

 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; 

Bloqueo doble controlado. Y un error fácil de hacer.

 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. 

Mi favorito es “Recursión infinita en la creación de instancias de plantillas” porque creo que es el único donde el comportamiento indefinido ocurre en tiempo de comstackción.

Asignando a una constante después de la const stripping usando const_cast<> :

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

Además del comportamiento indefinido , también existe el comportamiento igualmente desagradable definido por la implementación .

El comportamiento indefinido ocurre cuando un progtwig hace algo cuyo resultado no está especificado por el estándar.

El comportamiento definido por la implementación es una acción de un progtwig cuyo resultado no está definido por el estándar, pero que la implementación debe documentar. Un ejemplo es “literales de caracteres multibyte”, de la pregunta de desbordamiento de stack ¿Hay un comstackdor de C que no comstack esto? .

El comportamiento definido por la implementación solo te muerde cuando comienzas a portar (¡pero la actualización a la nueva versión del comstackdor también es portadora!)

Las variables solo se pueden actualizar una vez en una expresión (técnicamente una vez entre puntos de secuencia).

 int i =1; i = ++i; // Undefined. Assignment to 'i' twice in the same expression. 

Una comprensión básica de los diversos límites ambientales. La lista completa se encuentra en la sección 5.2.4.1 de la especificación C. Aquí hay algunos;

  • 127 parámetros en una definición de función
  • 127 argumentos en una llamada de función
  • 127 parámetros en una macrodefinición
  • 127 argumentos en una macro invocación
  • 4095 caracteres en una línea fuente lógica
  • 4095 caracteres en un literal de cadena de caracteres o literal de cadena ancha (después de la concatenación)
  • 65535 bytes en un objeto (solo en un entorno alojado)
  • 15 niveles de anidamiento para # archivos incluidos
  • 1023 tags de casos para una instrucción de conmutación (excluyendo aquellas para declaraciones de conmutación anynested)

De hecho, me sorprendió un poco el límite de 1023 tags de casos para una statement de cambio, puedo esperar que se supere para código / lex / analizadores generados de manera bastante sencilla.

Si se exceden estos límites, tiene un comportamiento indefinido (lockings, fallas de seguridad, etc.).

Correcto, sé que esto proviene de la especificación C, pero C ++ comparte estos soportes básicos.

Usando memcpy para copiar entre regiones de memoria superpuestas. Por ejemplo:

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

El comportamiento no está definido de acuerdo con el Estándar C, que está subsumido por el Estándar C ++ 03.

7.21.2.1 La función memcpy

Sinopsis

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

Descripción

2 / La función memcpy copia n caracteres del objeto señalado por s2 en el objeto apuntado por s1. Si la copia se lleva a cabo entre objetos que se superponen, el comportamiento no está definido. Devuelve 3 La función memcpy devuelve el valor de s1.

7.21.2.2 La función memmove

Sinopsis

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

Descripción

2 La función memmove copia n caracteres del objeto señalado por s2 en el objeto apuntado por s1. La copia se realiza como si los n caracteres del objeto apuntado por s2 se copiaran primero en una matriz temporal de n caracteres que no se solapen con los objetos apuntados por s1 y s2, y luego se copian los n caracteres de la matriz temporal. el objeto apuntado por s1. Devoluciones

3 La función memmove devuelve el valor de s1.

El único tipo para el cual C ++ garantiza un tamaño es char . Y el tamaño es 1. El tamaño de todos los demás tipos depende de la plataforma.

Los objetos de nivel de espacio de nombres en diferentes unidades de comstackción nunca deben depender el uno del otro para la inicialización, ya que su orden de inicialización no está definido.