¿Cuáles son todos los comportamientos no definidos comunes que un programador C ++ debe conocer? [cerrado]

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

Pregunta

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

decir, como:

a[i] = i++;

¿Fue útil?

Solución

Pointer

  • Desreferenciar un NULL puntero
  • Desreferenciar un puntero devuelto por un "nuevo" asignación de tamaño cero
  • Uso de punteros a objetos cuya vida útil ha terminado (por ejemplo, apilar objetos asignados o los objetos eliminados)
  • Desreferenciar un puntero que aún no ha sido definitivamente inicializado
  • Realización de la aritmética de punteros que produce un resultado fuera de los límites (ya sea por encima o por debajo) de una matriz.
  • desreferenciación del puntero en un lugar más allá del final de una matriz.
  • La conversión de punteros a objetos de tipos incompatibles
  • Usando memcpy para copiar la superposición de buffers .

Buffer desborda

  • lectura o escritura a un objeto o una matriz en un desplazamiento que es negativo, o más allá del tamaño de ese objeto (pila / desbordamiento del montón)

desbordamientos de enteros

  • Firmado desbordamiento de entero
  • Evaluación de una expresión que no se define matemáticamente
  • valores por una cantidad negativa desplazamiento izquierda (desplazamientos a la derecha por las cantidades negativas se define aplicación)
  • Desplazamiento de valores por una cantidad mayor que o igual que el número de bits en el número (por ejemplo int64_t i = 1; i <<= 72 es indefinido)

Tipos moldeada y Const

  • casting un valor numérico en un valor que no puede ser representado por el tipo de destino (ya sea directamente o por medio de static_cast)
  • Uso de una variable automática antes de que haya sido definitivamente asignado (por ejemplo, int i; i++; cout << i;)
  • Usando el valor de cualquier objeto de tipo distinto de volatile o sig_atomic_t en la recepción de una señal
  • El intento de modificar una cadena literal o cualquier otro objeto const durante su vida útil
  • La concatenación de un estrecho, con una amplia cadena literal en la decodificación previa

función y de la plantilla

  • No devolver un valor de una función de retorno de valor (directa o por que fluye fuera de un try-bloque)
  • Definiciones diferentes múltiples para la misma entidad (clase, plantilla, enumeración, función en línea, la función miembro estático, etc.)
  • recursión infinita en la creación de instancias de plantillas
  • Llamar a una función utilizando diferentes parámetros o la vinculación con los parámetros y vinculación que la función se define como el uso.

OOP

  • destrucciones de conexión en cascada de los objetos con una duración de almacenamiento estático
  • El resultado de asignar a superponen parcialmente objetos
  • recursiva volver a entrar en una función durante la inicialización de sus objetos estáticos
  • Hacer función virtual llama a las funciones virtuales puras de un objeto a partir de su constructor o destructor
  • Haciendo referencia a los miembros no estáticos de los objetos que no han sido construidos o ya se han destruido

archivo de origen y de preprocesamiento

  • Un archivo de origen no vacía que no termina con un carácter de nueva línea, o termina con una barra invertida (antes de C ++ 11)
  • una barra invertida seguida por un carácter que no es parte de los códigos de escape especificado en un carácter o constante de cadena (esto es definido por la implementación en C ++ 11).
  • Exceder los límites de aplicación (número de bloques anidados, número de funciones en un programa, el espacio de pila disponible ...)
  • valores numéricos del preprocesador que no pueden ser representados por un long int
  • directiva de preprocesamiento en el lado izquierdo de una definición de macro-función como
  • generar dinámicamente el token se define en una #if expresión

Para ser clasificado

  • Llamadas de salida durante la destrucción de un programa con una duración de almacenamiento estático

Otros consejos

El orden en que se evalúan los parámetros de función es no especificado Comportamiento . (Esto no hará que su caída del programa, explotar, o pedir una pizza ... a diferencia de Indefinido Comportamiento .)

El único requisito es que todos los parámetros deben ser evaluados por completo antes de llamar a la función.


Este:

// 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; le toca al compilador. El resultado puede importar, dependiendo de los efectos secundarios.

El compilador es libre para volver a ordenar las partes evaluación de una expresión (suponiendo que el significado es sin cambios).

A partir 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 comprobación de bloqueo. 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 "recursividad infinita en la creación de instancias de plantillas" porque creo que es el único en el que se produce un comportamiento no definido en tiempo de compilación.

Asignación a una constante después de quitar const Ness utilizando const_cast<>:

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

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

comportamiento indefinido se produce cuando un programa hace algo el resultado de que no se especifica en la norma.

comportamiento definido por la implementación es una acción por un programa el resultado de que no se define por la norma, pero que se requiere la implementación de documentar. Un ejemplo es "literales de caracteres de varios bytes", de la pregunta de desbordamiento de pila ¿Existe un compilador de C que falla al compilar esto? .

comportamiento definido por la implementación sólo le muerde cuando se inicia la portabilidad (pero la actualización a una nueva versión del compilador también está portando!)

Variables solamente pueden ser actualizados 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.

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

  • 127 parámetros en una función de definición
  • 127 argumentos en una llamada de función
  • 127 parámetros en una macro definición
  • 127 argumentos en una invocación macro
  • 4095 caracteres en una línea de fuente lógica
  • 4095 caracteres en una cadena de caracteres literal o una cadena literal de ancho (tras concatenación)
  • 65535 bytes en una objeto (en un entorno alojado solamente)
  • niveles 15nesting para #include fi les
  • 1023 caso de etiquetas para un conmutador declaración (excluyendo los de sentencias switch anynested)

En realidad estaba un poco sorprendido por el límite de 1023 etiquetas de caso de una sentencia switch, puedo forsee que se exceda de código / lex / analizadores generados bastante easially.

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

Derecho, sé que esto es de la especificación C, pero las acciones de C ++ estos soportes básicos.

Uso memcpy para copiar entre la superposición de las regiones de memoria. Por ejemplo:

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

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

7.21.2.1 La función memcpy

  

Sinopsis

     

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

     

Descripción

     

2 / La función memcpy   copias n caracteres del objeto apuntado por s2 en el objeto   apuntada por s1. Si la copia se realiza entre los objetos que se superponen,   el comportamiento no está definido. Devuelve 3 La función devuelve el establecimiento de memoria   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 copia memmove n caracteres del objeto apuntado   por s2 en el objeto apuntado por s1. La copia se lleva a cabo como si el   n caracteres desde el objeto apuntado por s2 se copian primero en una   matriz temporal de n caracteres que no se superpone los objetos   apuntada por S1 y S2, y luego los n caracteres desde el temporal   array se copian en el objeto apuntado por s1. Devuelve

     

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

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 otros tipos depende de la plataforma.

Los objetos de nivel de espacio de nombres en un diferentes unidades de compilación nunca deben dependen unos de otros para la inicialización, porque su orden de inicialización no está definido.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top