C++
Comportamiento no especificado
Buscar..
Observaciones
Si el comportamiento de una construcción no se especifica, entonces el estándar establece algunas restricciones en el comportamiento, pero deja cierta libertad a la implementación, que no es necesaria para documentar lo que sucede en una situación determinada. Contrasta con el comportamiento definido por la implementación, en el que se requiere que la implementación documente lo que sucede, y el comportamiento indefinido, en el que puede ocurrir cualquier cosa.
Orden de inicialización de globales a través de TU
Mientras que dentro de una unidad de traducción, se especifica el orden de inicialización de las variables globales, el orden de inicialización entre unidades de traducción no está especificado.
Así programa con los siguientes archivos
foo.cpp
#include <iostream> int dummyFoo = ((std::cout << "foo"), 0);
bar.cpp
#include <iostream> int dummyBar = ((std::cout << "bar"), 0);
main.cpp
int main() {}
podría producir como salida:
foobar
o
barfoo
Eso puede llevar a Fiasco Orden de Inicialización Estática .
Valor de una enumeración fuera de rango
Si una enumeración de ámbito se convierte en un tipo integral que es demasiado pequeño para mantener su valor, el valor resultante no se especifica. Ejemplo:
enum class E {
X = 1,
Y = 1000,
};
// assume 1000 does not fit into a char
char c1 = static_cast<char>(E::X); // c1 is 1
char c2 = static_cast<char>(E::Y); // c2 has an unspecified value
Además, si un entero se convierte en una enumeración y el valor del entero está fuera del rango de los valores de la enumeración, el valor resultante no se especifica. Ejemplo:
enum Color {
RED = 1,
GREEN = 2,
BLUE = 3,
};
Color c = static_cast<Color>(4);
Sin embargo, en el siguiente ejemplo, el comportamiento no está sin especificar, ya que el valor de origen está dentro del rango de la enumeración, aunque es desigual para todos los enumeradores:
enum Scale {
ONE = 1,
TWO = 2,
FOUR = 4,
};
Scale s = static_cast<Scale>(3);
Aquí s
tendrá el valor 3 y será igual a ONE
, TWO
y FOUR
.
Reparto estático a partir de un valor falso *
Si un valor void*
se convierte en un puntero al tipo de objeto, T*
, pero no se alinea correctamente para T
, el valor del puntero resultante no se especifica. Ejemplo:
// Suppose that alignof(int) is 4
int x = 42;
void* p1 = &x;
// Do some pointer arithmetic...
void* p2 = static_cast<char*>(p1) + 2;
int* p3 = static_cast<int*>(p2);
El valor de p3
no está especificado porque p2
no puede apuntar a un objeto de tipo int
; su valor no es una dirección correctamente alineada.
Resultado de algunas conversiones reinterpret_cast
El resultado de un reinterpret_cast
de un tipo de puntero de función a otro, o un tipo de referencia de función a otro, no está especificado. Ejemplo:
int f();
auto fp = reinterpret_cast<int(*)(int)>(&f); // fp has unspecified value
El resultado de un reinterpret_cast
de un tipo de puntero de objeto a otro, o un tipo de referencia de objeto a otro, no se especifica. Ejemplo:
int x = 42;
char* p = reinterpret_cast<char*>(&x); // p has unspecified value
Sin embargo, con la mayoría de los compiladores, esto era equivalente a static_cast<char*>(static_cast<void*>(&x))
por lo que el puntero resultante p
apuntaba al primer byte de x
. Esto se hizo el comportamiento estándar en C ++ 11. Ver tipo de conversión de puntos para más detalles.
Resultado de algunas comparaciones de punteros
Si se comparan dos punteros utilizando <
, >
, <=
o >=
, el resultado no se especifica en los siguientes casos:
Los punteros apuntan a diferentes matrices. (Un objeto sin matriz se considera una matriz de tamaño 1).
int x; int y; const bool b1 = &x < &y; // unspecified int a[10]; const bool b2 = &a[0] < &a[1]; // true const bool b3 = &a[0] < &x; // unspecified const bool b4 = (a + 9) < (a + 10); // true // note: a+10 points past the end of the array
Los punteros apuntan al mismo objeto, pero a los miembros con un control de acceso diferente.
class A { public: int x; int y; bool f1() { return &x < &y; } // true; x comes before y bool f2() { return &x < &z; } // unspecified private: int z; };
Espacio ocupado por una referencia.
Una referencia no es un objeto y, a diferencia de un objeto, no se garantiza que ocupe algunos bytes contiguos de memoria. El estándar deja sin especificar si una referencia requiere algún almacenamiento. Una serie de características del lenguaje conspiran para hacer que sea imposible examinar de manera portátil cualquier almacenamiento que la referencia pueda ocupar:
- Si se aplica
sizeof
a una referencia, devuelve el tamaño del tipo referenciado, por lo que no proporciona información sobre si la referencia ocupa algún almacenamiento. - Las matrices de referencias son ilegales, por lo que no es posible examinar las direcciones de dos elementos consecutivos de una referencia hipotética de matrices para determinar el tamaño de una referencia.
- Si se toma la dirección de una referencia, el resultado es la dirección del referente, por lo que no podemos obtener un puntero a la referencia en sí.
- Si una clase tiene un miembro de referencia, el intento de extraer la dirección de ese miembro usando
offsetof
produce un comportamiento indefinido ya que dicha clase no es una clase de diseño estándar. - Si una clase tiene un miembro de referencia, la clase ya no es un diseño estándar, por lo tanto, los intentos de acceder a los datos utilizados para almacenar los resultados de referencia en un comportamiento no definido o no especificado.
En la práctica, en algunos casos, una variable de referencia puede implementarse de manera similar a una variable de puntero y, por lo tanto, ocupar la misma cantidad de almacenamiento que un puntero, mientras que en otros casos una referencia puede no ocupar ningún espacio ya que puede optimizarse. Por ejemplo, en:
void f() {
int x;
int& r = x;
// do something with r
}
el compilador es libre de simplemente tratar r
como un alias para x
y reemplazar todas las apariciones de r
en el resto de la función f
con x
, y no asignar ningún almacenamiento para mantener r
.
Orden de evaluacion de argumentos de funcion.
Si una función tiene múltiples argumentos, no se especifica en qué orden se evalúan. El siguiente código podría imprimir x = 1, y = 2
o x = 2, y = 1
pero no se especifica cuál.
int f(int x, int y) {
printf("x = %d, y = %d\n", x, y);
}
int get_val() {
static int x = 0;
return ++x;
}
int main() {
f(get_val(), get_val());
}
En C ++ 17, el orden de evaluación de los argumentos de la función permanece sin especificar.
Sin embargo, cada argumento de función se evalúa por completo, y se garantiza la evaluación del objeto llamante antes de que lo sean los argumentos de función.
struct from_int {
from_int(int x) { std::cout << "from_int (" << x << ")\n"; }
};
int make_int(int x){ std::cout << "make_int (" << x << ")\n"; return x; }
void foo(from_int a, from_int b) {
}
void bar(from_int a, from_int b) {
}
auto which_func(bool b){
std::cout << b?"foo":"bar" << "\n";
return b?foo:bar;
}
int main(int argc, char const*const* argv) {
which_func( true )( make_int(1), make_int(2) );
}
esto debe imprimir:
bar
make_int(1)
from_int(1)
make_int(2)
from_int(2)
o
bar
make_int(2)
from_int(2)
make_int(1)
from_int(1)
que no se imprima bar
después de cualquiera de la make
o from
's, y que no se imprima:
bar
make_int(2)
make_int(1)
from_int(2)
from_int(1)
o similar. Antes de C ++ 17, la bar
impresión después de make_int
s era legal, al igual que hacer ambos make_int
s antes de hacer cualquier from_int
s.
Estado movido de la mayoría de las clases de biblioteca estándar
Todos los contenedores de biblioteca estándar se dejan en un estado válido pero no especificado después de ser movidos. Por ejemplo, en el siguiente código, v2
contendrá {1, 2, 3, 4}
después del movimiento, pero no se garantiza que v1
esté vacío.
int main() {
std::vector<int> v1{1, 2, 3, 4};
std::vector<int> v2 = std::move(v1);
}
Algunas clases tienen un estado movido desde exactamente definido. El caso más importante es el de std::unique_ptr<T>
, que se garantiza que será nulo después de ser movido.