¿Cuáles son todos los comportamientos indefinidos comunes que un programador de C ++ debe conocer?
Diga, como:
a[i] = i++;
¿Cuáles son todos los comportamientos indefinidos comunes que un programador de C ++ debe conocer?
Diga, como:
a[i] = i++;
Respuestas:
NULL
punteromemcpy
para copiar buffers superpuestos .int64_t i = 1; i <<= 72
no está definido)int i; i++; cout << i;
)volatile
osig_atomic_t
al recibir una señallong int
#if
expresiónEl orden en que se evalúan los parámetros de la función es el comportamiento no especificado . (Esto no hará que su programa se bloquee, explote u ordene 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; depende del compilador. El resultado puede importar, dependiendo de los efectos secundarios.
El compilador es libre de reordenar las partes de evaluación de una expresión (suponiendo que el significado no haya 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 comprobación 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.
Mi favorita es "La 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.
Además del comportamiento indefinido , también existe el comportamiento igualmente desagradable definido por la implementación .
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 puede compilar esto? .
El comportamiento definido por la implementación solo lo muerde cuando comienza a portar (¡pero actualizar a una nueva versión del compilador también es portar!)
Las variables solo pueden actualizarse 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;
En realidad, me sorprendió un poco el límite de 1023 etiquetas de caso para una declaración de cambio, puedo prever que se supere con bastante facilidad para el código / lex / analizador generado.
Si se exceden 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.
Utilizando 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.
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 señalado por s2 en el objeto señalado por s1. Si la copia se lleva a cabo entre objetos que se superponen, el comportamiento es indefinido. Devuelve 3 La función memcpy devuelve el valor de s1.
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 señalado por s1. La copia se realiza como si los n caracteres del objeto señalado por s2 se copiaran primero en una matriz temporal de n caracteres que no se superponen a los objetos señalados por s1 y s2, y luego los n caracteres de la matriz temporal se copian en El objeto señalado por s1. Devoluciones
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 demás tipos depende de la plataforma.