scratch - Comportamiento indefinido en la lectura de un objeto que usa un tipo que no es de caracteres la última vez que se escribió usando un tipo de carácter
programacion android pdf 2018 (2)
Suponiendo que el unsigned int
no tiene representaciones de trampas, una o ambas afirmaciones marcadas con (A) y (B) a continuación provocan un comportamiento indefinido, por qué o por qué no, y (especialmente si piensa que una de ellas está bien definida pero la otra no lo está) ''t), ¿considera usted que un defecto en la norma? Me interesa principalmente la versión actual del estándar C (es decir, C2011), pero si esto es diferente en versiones anteriores del estándar, o en C ++, también me gustaría saberlo.
( _Alignas
se usa en este programa para eliminar cualquier pregunta de UB debido a una alineación inadecuada. Sin embargo, las reglas que discuto en mi interpretación no dicen nada sobre la alineación).
#include <stdlib.h>
#include <string.h>
int main(void)
{
unsigned int v1, v2;
unsigned char _Alignas(unsigned int) b1[sizeof(unsigned int)];
unsigned char *b2 = malloc(sizeof(unsigned int));
if (!b2) return 1;
memset(b1, 0x55, sizeof(unsigned int));
memset(b2, 0x55, sizeof(unsigned int));
v1 = *(unsigned int *)b1; /* (A) */
v2 = *(unsigned int *)b2; /* (B) */
return !(v1 == v2);
}
Mi interpretación de C2011 es que (A) provoca un comportamiento indefinido pero (B) está bien definido (para almacenar un valor no especificado en v2
), porque:
memset
se define (§7.24.6.1) para escribir su primer argumento as-if a través de un lvalue con tipo de carácter, que se permite tanto parab1
como parab2
según el caso especial al final de §6.5p7.El objeto
b1
tiene un tipo declarado,unsigned char[n]
. Por lo tanto, su tipo efectivo para accesos también esunsigned char[n]
por 6.5p6. La instrucción (A) leeb1
través de una expresión de valor l cuyo tipounsigned int
tieneunsigned int
, que no es el tipo efectivo deb1
ni ninguna de las otras excepciones en 6.5p7, por lo que el comportamiento no está definido.El objeto apuntado por
b2
no tiene un tipo declarado. El valor almacenado en él (pormemset
) fue (como-si) a través de un lvalue con tipo de carácter, por lo que el segundo caso de 6.5p6 no se aplica. El valor no se copió de ninguna parte, por lo que el tercer caso de 6.5p6 tampoco se aplica. Por lo tanto, el tipo efectivo del objeto es el tipo del lvalue utilizado para el acceso, que estáunsigned int
, y se cumplen las reglas de 6.5p7.Finalmente, según 6.2.6.1, suponiendo que el
unsigned int
no tiene representaciones de trampa, la operaciónmemset
ha creado la representación de algún valorunsigned int
especificar en cada uno deb1
yb2
. Por lo tanto, si ni (A) ni (B) provocan un comportamiento indefinido, entonces los valores reales env1
yv2
no están especificados, pero son iguales.
Comentario:
La asimetría de las reglas de "aliasing basadas en tipo" (es decir, 6.5p7), que permiten que un objeto con cualquier tipo efectivo sea accedido por un lvalue con tipo de carácter, pero no al revés, es una fuente continua de confusión. El segundo caso de 6.5p6 parece haber sido agregado específicamente para evitar que sea un comportamiento indefinido para leer un valor inicializado por memset
(o, para el caso, calloc
) pero, porque solo se aplica a objetos sin tipo declarado, es en sí mismo un Fuente adicional de confusión.
En un examen superficial, estoy de acuerdo con su evaluación (A es UB, B está bien) y puedo ofrecer una justificación concreta de por qué debería ser así (antes de la edición para incluir _Alignas()
): Alineación .
El char[]
en la pila puede comenzar en cualquier dirección, ya sea una alineación válida para un unsigned int
o no. Por el contrario, malloc()
debe devolver la memoria que cumpla con los requisitos de alineación más estrictos de cualquier tipo nativo en la plataforma en cuestión.
Obviamente, el estándar no quiere imponer requisitos de alineación en char[]
más allá de los de char
, por lo que tiene que dejar el tipo de acceso puntero al tipo como potencialmente indefinido.
Los autores de la Norma reconocen en el razonamiento que sería posible que una implementación sea conforme pero inútil. Debido a que esperaban que los implementadores se esforzaran por hacer que sus implementaciones fueran útiles, no creían que fuera necesario imponer todos los comportamientos que pudieran ser necesarios para hacer que una implementación fuera adecuada para cualquier propósito en particular.
El Estándar no impone requisitos sobre el comportamiento del código que accede a un objeto alineado de tipo de matriz de caracteres como algún otro tipo. Eso no significa que pretendan que las implementaciones deban hacer algo más que tratar la matriz como un almacenamiento sin tipo en los casos en que el código toma la dirección de la matriz una vez, pero nunca accede a ella directamente . La naturaleza fundamental del aliasing es que requiere que se acceda a un elemento de dos maneras diferentes; Si solo se accede a un objeto de una manera, no hay aliasing por definición. Cualquier implementación de calidad que se supone que es adecuada para la programación de bajo nivel debe comportarse de manera útil en los casos en que un char[]
se usa solo como almacenamiento sin tipo, ya sea que el Estándar lo requiera o no , y es difícil de imaginar algún propósito útil que se vería obstaculizado por dicho tratamiento. El único propósito que se cumpliría con el mandato estándar de tal comportamiento sería evitar que los escritores de compiladores consideren la falta de un mandato como una razón para no procesar dicho código de la manera más útil y obvia.