porque - no puedo inicializar un disco externo
¿Es un comportamiento aa o a undefined si a no se inicializa? (3)
En C11:
- Está explícitamente indefinido de acuerdo con 6.3.2.1/2 si nunca se toma una dirección (se cita abajo)
- Podría ser una representación de trampa (que causa UB cuando se accede). 6.2.6.1/5:
Ciertas representaciones de objetos no necesitan representar un valor del tipo de objeto.
Las entradas sin firmar pueden tener representaciones de captura (por ejemplo, si tiene 15 bits de precisión y 1 de paridad, el acceso a
podría causar un error de paridad).
6.2.4 / 6 dice que el valor inicial es indeterminado y la definición de eso debajo de 3.19.2 es un valor no especificado o una representación de trampa .
Además: en C11 6.3.2.1/2, como lo señala Pascal Cuoq:
Si lvalue designa un objeto de duración de almacenamiento automático que podría haberse declarado con la clase de almacenamiento de registros (nunca se tomó su dirección), y ese objeto no se inicializó (no se declaró con un inicializador y no se realizó ninguna asignación antes del uso ), el comportamiento no está definido.
Esto no tiene la excepción para los tipos de caracteres, por lo que esta cláusula parece reemplazar la discusión anterior; el acceso a x
está definido de inmediato, incluso si no existen representaciones de trampas. Esta cláusula se agregó a C11 para admitir CPU Itanium que realmente tienen un estado de captura para registros.
Sistemas sin representaciones de trampa: ¿Pero qué pasa si lanzamos adentro &x;
para que la objeción de 6.3.2.1/2 ya no se aplique, y estamos en un sistema que se sabe que no tiene representaciones de trampas? Entonces el valor es un valor no especificado . La definición de valor no especificado en 3.19.3 es un poco vaga, sin embargo, está aclarada por DR 451 , que concluye:
- Un valor no inicializado en las condiciones descritas puede parecer que cambia su valor.
- Cualquier operación realizada en valores indeterminados tendrá como resultado un valor indeterminado.
- Las funciones de la biblioteca exhibirán un comportamiento indefinido cuando se usan en valores indeterminados.
- Estas respuestas son apropiadas para todos los tipos que no tienen representaciones de trampas.
Bajo esta resolución, int a; &a; int b = a - a;
int a; &a; int b = a - a;
da como resultado b
tener un valor indeterminado.
Tenga en cuenta que si el valor indeterminado no se pasa a una función de biblioteca, todavía estamos en el ámbito del comportamiento no especificado (comportamiento no indefinido). Los resultados pueden ser extraños, por ejemplo, if ( j != j ) foo();
podría llamar a foo, pero los demonios deben permanecer incrustados en la cavidad nasal.
Considera este programa:
#include <stdio.h>
int main(void)
{
unsigned int a;
printf("%u %u/n", a^a, a-a);
return 0;
}
¿Es un comportamiento indefinido?
A primera vista, a
es una variable no inicializada. Entonces eso apunta a un comportamiento indefinido. Pero a^a
y aa
son iguales a 0
para todos los valores de a
, al menos creo que ese es el caso. ¿Es posible que haya alguna forma de argumentar que el comportamiento está bien definido?
Sí, es un comportamiento indefinido.
En primer lugar, cualquier variable no inicializada puede tener una representación "rota" (también conocida como "trampa"). Incluso un solo intento de acceder a esa representación desencadena un comportamiento indefinido. Por otra parte, incluso objetos de tipos no atrapables (como unsigned char
) aún pueden adquirir estados especiales dependientes de la plataforma (como NaT - Not-A-Thing - en Itanium) que pueden aparecer como una manifestación de su "valor indeterminado".
En segundo lugar, no se garantiza que una variable no inicializada tenga un valor estable . Dos accesos secuenciales a la misma variable no inicializada pueden leer valores completamente diferentes , por lo que, incluso si ambos accesos en a - a
son "exitosos" (no atrapados), aún no se garantiza que a - a
evaluará a cero.
Si un objeto tiene una duración de almacenamiento automática y no se toma su dirección, intentar leerlo producirá un Comportamiento no definido. Tomando la dirección de dicho objeto y usando punteros del tipo "char sin signo" para leer los bytes del mismo, está garantizado por el Estándar para producir un valor del tipo "char sin signo", pero no todos los compiladores se adhieren al Estándar en ese sentido . ARM GCC 5.1, por ejemplo, cuando se da:
#include <stdint.h>
#include <string.h>
struct q { uint16_t x,y; };
volatile uint16_t zz;
int32_t foo(uint32_t x, uint32_t y)
{
struct q temp1,temp2;
temp1.x = 3;
if (y & 1)
temp1.y = zz;
memmove(&temp2,&temp1,sizeof temp1);
return temp2.y;
}
generará un código que devolverá x si y es cero, incluso si x está fuera del rango 0-65535. El estándar deja en claro que las lecturas de caracteres sin signo de valor indeterminado garantizan un valor dentro del rango de caracteres unsigned char
, y el comportamiento de memmove
se define como equivalente a una secuencia de lecturas y escrituras de caracteres. Por lo tanto, temp2 debe tener un valor que pueda almacenarse a través de una secuencia de escrituras de caracteres, pero gcc está decidiendo reemplazar el memmove con una asignación e ignorar el hecho de que el código tomó la dirección de temp1 y temp2.
Sería útil contar con un medio para forzar a un compilador a considerar que una variable tiene un valor arbitrario de su tipo, en los casos en que tal valor sea igualmente aceptable, pero el Estándar no especifica un medio limpio para hacerlo (guardar para almacenar algún valor particular que funcionaría, pero a menudo sería innecesariamente lento). Incluso las operaciones que lógicamente obligan a una variable a contener un valor que sería representable como una combinación de bits no se puede confiar para que funcionen en todos los compiladores. En consecuencia, no se puede garantizar nada útil sobre tales variables.