lenguaje - ¿Cuáles son todas las conductas indefinidas comunes que un programador de C++ debe conocer?
lenguaje c++ ejemplos (11)
Puntero
- Desreferenciación de un puntero
NULL
- Desreferenciación de un puntero devuelto por una asignación "nueva" de tamaño cero
- Uso de punteros a objetos cuya vida útil ha finalizado (por ejemplo, apilar objetos asignados u objetos eliminados)
- Desreferenciación de un puntero que aún no se ha inicializado definitivamente
- Realización de aritmética de punteros que produce un resultado fuera de los límites (ya sea arriba o abajo) de una matriz.
- Desreferenciación del puntero en una ubicación más allá del final de una matriz.
- Convertir punteros a objetos de tipos incompatibles.
- Usando
memcpy
para copiar buffers superpuestos .
Desbordamientos de búfer
- Leer o escribir en un objeto o matriz en un desplazamiento que es negativo, o más allá del tamaño de ese objeto (desbordamiento de pila / montón)
Desbordamientos de enteros
- Desbordamiento de entero firmado
- Evaluar una expresión que no está definida matemáticamente
- Valores de desplazamiento a la izquierda por una cantidad negativa (los cambios a la derecha por cantidades negativas están definidos por la implementación)
- Desplazar los valores en una cantidad mayor o igual al número 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 que se haya asignado definitivamente (por ejemplo,
int i; i++; cout << i;
) - Usando el valor de cualquier objeto de tipo distinto de
volatile
osig_atomic_t
al recibir una señal - Intentar modificar una cadena literal o cualquier otro objeto const durante su vida útil
- Concatenación de un literal estrecho con una cadena ancha durante el preprocesamiento
Función y plantilla
- No devolver un valor desde una función de retorno de valor (directamente o fluyendo desde un bloque de prueba)
- Múltiples definiciones diferentes para la misma entidad (clase, plantilla, enumeración, función en línea, función miembro estática, etc.)
- Recursión infinita en la instanciación de plantillas.
- Llamar a una función utilizando diferentes parámetros o vincularse a los parámetros y vincularse con la función que se define como uso.
OOP
- Desestructuración en cascada de objetos con duración de almacenamiento estático
- El resultado de asignar a objetos parcialmente superpuestos
- Recursivamente reingresando una función durante la inicialización de sus objetos estáticos
- Hacer llamadas a funciones virtuales a funciones virtuales puras de un objeto desde su constructor o destructor
- Refiriéndose a miembros no estáticos de objetos que no se han construido o ya se han destruido
Archivo fuente y preprocesamiento
- Un archivo fuente no vacío que no termina con una nueva línea, o termina con una barra invertida (antes de C ++ 11)
- Una barra diagonal inversa seguida de un carácter que no forma parte de los códigos de escape especificados en una constante de carácter o cadena (esto se define en la implementación en C ++ 11).
- Superar los límites de implementación (número de bloques anidados, número de funciones en un programa, espacio de pila disponible ...)
- Valores numéricos del preprocesador que no pueden representarse por un
long int
- Directiva de preprocesamiento en el lado izquierdo de una definición de macro similar a una función
-
#if
dinámicamente el token definido en una expresión#if
Ser clasificado
- Llamando a exit durante la destrucción de un programa con duración de almacenamiento estático
¿Cuáles son todas las conductas indefinidas comunes que un programador de C ++ debe conocer?
Decir, como
a[i] = i++;
Además del comportamiento indefinido , también existe el comportamiento definido por la implementación igualmente desagradable.
El comportamiento indefinido ocurre cuando un programa hace algo cuyo resultado no está especificado por el estándar.
El comportamiento definido por la implementación es una acción de un programa 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 pila ¿Hay un compilador de C que no compile esto? .
El comportamiento definido por la implementación solo lo morderá cuando comience a migrar (¡pero la actualización a una nueva versión del compilador también lo es!)
Asignación a una constante después de const_cast<>
utilizando const_cast<>
:
const int i = 10;
int *p = const_cast<int*>( &i );
*p = 1234; //Undefined
El único tipo para el que 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.
El compilador es libre de reordenar las partes de evaluación de una expresión (asumiendo que el significado no ha cambiado).
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;
Doble control de bloqueo. Y un error fácil de cometer.
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.
El orden en que se evalúan los parámetros de la función es un comportamiento no especificado . (Esto no hará que su programa se bloquee, explote o pida pizza ... a diferencia del comportamiento indefinido ).
El único requisito es que todos los parámetros deben evaluarse 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 de los dos; Depende del compilador. El resultado puede importar, dependiendo de los efectos secundarios.
Las variables solo se pueden actualizar una vez en una expresión (técnicamente una vez entre los puntos de secuencia).
int i =1;
i = ++i;
// Undefined. Assignment to ''i'' twice in the same expression.
Los objetos a nivel de espacio de nombres en unidades de compilación diferentes nunca deben depender entre sí para la inicialización, porque su orden de inicialización no está definido.
Mi favorito es "Recursión infinita en la creación de instancias de plantillas" porque creo que es la única en la que el comportamiento indefinido ocurre en el momento de la compilación.
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 función de fi nición
- 127 argumentos en una llamada de función
- 127 parámetros en una definición de macro
- 127 argumentos en una invocación de macro
- 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 interesantes para archivos #incluidos
- 1023 etiquetas de caso para una declaración de cambio (excluyendo aquellas para cualquier declaración de cambio anidada)
En realidad me sorprendió un poco el límite de 1023 etiquetas de casos para una declaración de cambio, puedo ver que se exceda para los códigos / lex / analizadores generados con bastante facilidad.
Si se superan estos límites, tiene un comportamiento indefinido (fallas, fallas de seguridad, etc.).
Correcto, sé que esto es 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á incluido en el Estándar C ++ 03.
7.21.2.1 La función memcpy
Sinopsis
1 / #include void * memcpy (void * restrict s1, const void * restrict s2, size_t n);
Descripción
2 / La función memcpy copia n caracteres del objeto al que apunta s2 en el objeto al que apunta s1. Si la copia tiene lugar 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 al que apunta s2 en el objeto al que apunta s1. La copia se realiza como si los n caracteres del objeto al que apunta s2 se copiaran primero en una matriz temporal de n caracteres que no se superponen con los objetos apuntados por s1 y s2, y luego los n caracteres de la matriz temporal se copian en El objeto apuntado por s1. Devoluciones
3 La función memmove devuelve el valor de s1.