Al preguntar sobre el comportamiento indefinido común en C , las personas a veces se refieren a la estricta regla de alias.
De qué están hablando?
805
Al preguntar sobre el comportamiento indefinido común en C , las personas a veces se refieren a la estricta regla de alias.
De qué están hablando?
c
yc++faq
.Respuestas:
Una situación típica en la que encuentra problemas de alias estrictos es cuando superpone una estructura (como un mensaje de dispositivo / red) en un búfer del tamaño de palabra de su sistema (como un puntero a
uint32_t
souint16_t
s). Cuando superpone una estructura en dicho búfer, o un búfer en dicha estructura a través de la conversión del puntero, puede violar fácilmente las estrictas reglas de alias.Entonces, en este tipo de configuración, si quiero enviar un mensaje a algo, tendría que tener dos punteros incompatibles apuntando a la misma porción de memoria. Entonces podría codificar ingenuamente algo como esto (en un sistema con
sizeof(int) == 2
):La estricta regla de alias hace que esta configuración sea ilegal: desreferenciar un puntero que alias un objeto que no es de un tipo compatible o uno de los otros tipos permitidos por C 2011 6.5 párrafo 7 1 es un comportamiento indefinido. Desafortunadamente, todavía puede codificar de esta manera, tal vez obtener algunas advertencias, compilarlo bien, solo para tener un comportamiento extraño e inesperado cuando ejecuta el código.
(GCC parece algo inconsistente en su capacidad de dar advertencias de aliasing, a veces dándonos una advertencia amistosa y otras no).
Para ver por qué este comportamiento es indefinido, tenemos que pensar en lo que la regla de alias estricto compra al compilador. Básicamente, con esta regla, no tiene que pensar en insertar instrucciones para actualizar el contenido de
buff
cada ejecución del ciclo. En cambio, cuando se optimiza, con algunas suposiciones molestas sobre el alias, puede omitir esas instrucciones, cargarbuff[0]
ybuff[1
] en los registros de la CPU una vez antes de que se ejecute el bucle, y acelerar el cuerpo del bucle. Antes de que se introdujera un alias estricto, el compilador tenía que vivir en un estado de paranoia que el contenido debuff
podía cambiar en cualquier momento y en cualquier lugar. Entonces, para obtener una ventaja de rendimiento adicional, y suponiendo que la mayoría de las personas no escriben punteros, se introdujo la estricta regla de alias.Tenga en cuenta que si cree que el ejemplo está ideado, esto podría suceder incluso si pasa un búfer a otra función que realiza el envío por usted, si es que lo ha hecho.
Y reescribió nuestro bucle anterior para aprovechar esta conveniente función
El compilador puede o no ser capaz o lo suficientemente inteligente como para intentar enviar SendMessage en línea y puede o no decidir cargar o no cargar buff nuevamente. Si
SendMessage
es parte de otra API que se compila por separado, probablemente tenga instrucciones para cargar el contenido de buff. Por otra parte, tal vez estás en C ++ y esta es una implementación de solo encabezado con plantilla que el compilador cree que puede en línea. O tal vez es algo que escribió en su archivo .c para su propia conveniencia. De todos modos, todavía podría seguir un comportamiento indefinido. Incluso cuando sabemos algo de lo que sucede debajo del capó, sigue siendo una violación de la regla, por lo que no se garantiza un comportamiento bien definido. Entonces, simplemente envolviendo una función que toma nuestro búfer delimitado por palabras no necesariamente ayuda.Entonces, ¿cómo puedo evitar esto?
Usa una unión. La mayoría de los compiladores admiten esto sin quejarse de un alias estricto. Esto está permitido en C99 y explícitamente en C11.
Puede desactivar el alias estricto en su compilador ( f [no-] alias estricto en gcc))
Puede usar
char*
para crear alias en lugar de la palabra de su sistema. Las reglas permiten una excepción parachar*
(incluyendosigned char
yunsigned char
). Siempre se supone quechar*
alias otros tipos. Sin embargo, esto no funcionará de otra manera: no se supone que su estructura alias un búfer de caracteres.Principiante ten cuidado
Este es solo un campo minado potencial cuando se superponen dos tipos entre sí. También debe aprender sobre endianness , alineación de palabras y cómo lidiar con problemas de alineación a través de estructuras de empaque correctamente.
Nota
1 Los tipos a los que C 2011 6.5 7 le permite acceder a un valor son:
fuente
unsigned char*
puede usar lejoschar*
? Tiendo a usar enunsigned char
lugar dechar
como el tipo subyacentebyte
porque mis bytes no están firmados y no quiero que la rareza del comportamiento firmado (especialmente wrt se desborde)unsigned char *
está bien.uint32_t* buff = malloc(sizeof(Msg));
y las siguientesunsigned int asBuffer[sizeof(Msg)];
declaraciones de búfer de unión tendrán diferentes tamaños y ninguna de ellas es correcta. Lamalloc
llamada se basa en la alineación de 4 bytes debajo del capó (no lo hagas) y la unión será 4 veces más grande de lo necesario ... Entiendo que es por claridad, pero no me molesta. menos ...La mejor explicación que he encontrado es Mike Acton, Understanding Strict Aliasing . Se centra un poco en el desarrollo de PS3, pero eso es básicamente solo GCC.
Del artículo:
Así que, básicamente, si tiene una
int*
señal que apunta a alguna memoria que contiene unaint
y luego señala unafloat*
a esa memoria y la usa como unafloat
infracción de la regla. Si su código no respeta esto, entonces el optimizador del compilador probablemente romperá su código.La excepción a la regla es un
char*
, que puede apuntar a cualquier tipo.fuente
Esta es la estricta regla de alias, que se encuentra en la sección 3.10 del estándar C ++ 03 (otras respuestas proporcionan una buena explicación, pero ninguna proporcionó la regla en sí):
Texto de C ++ 11 y C ++ 14 (cambios enfatizados):
Dos cambios fueron pequeños: glvalue en lugar de lvalue , y aclaración del caso agregado / unión.
El tercer cambio ofrece una garantía más sólida (relaja la fuerte regla de alias): el nuevo concepto de tipos similares que ahora son seguros para el alias.
También la redacción C (C99; ISO / IEC 9899: 1999 6.5 / 7; exactamente la misma redacción se utiliza en ISO / IEC 9899: 2011 §6.5 ¶7):
fuente
wow(&u->s1,&u->s2)
debería ser legal incluso cuando se usa un puntero para modificaru
, y eso negaría la mayoría de las optimizaciones la regla de alias fue diseñada para facilitar.Nota
Esto se extrae de mi "¿Cuál es la regla de alias estricto y por qué nos importa?" redacción
¿Qué es el alias estricto?
En C y C ++, el alias tiene que ver con qué tipos de expresión se nos permite acceder a los valores almacenados. Tanto en C como en C ++, el estándar especifica qué tipos de expresión tienen permiso para alias de qué tipos. El compilador y el optimizador pueden asumir que seguimos estrictamente las reglas de alias, de ahí el término regla de alias estricto . Si intentamos acceder a un valor utilizando un tipo no permitido, se clasifica como comportamiento indefinido ( UB ). Una vez que tenemos un comportamiento indefinido, todas las apuestas se cancelan, los resultados de nuestro programa ya no son confiables.
Desafortunadamente, con violaciones de alias estrictas, a menudo obtendremos los resultados que esperamos, dejando la posibilidad de que una versión futura de un compilador con una nueva optimización rompa el código que pensamos que era válido. Esto no es deseable y es un objetivo que vale la pena comprender las estrictas reglas de alias y cómo evitar violarlas.
Para entender más acerca de por qué nos importa, discutiremos los problemas que surgen cuando se violan las estrictas reglas de alias, el tipo de punteo, ya que las técnicas comunes utilizadas en el tipo de punteo a menudo violan las estrictas reglas de alias y cómo escribir el juego de palabras correctamente.
Ejemplos preliminares
Veamos algunos ejemplos, luego podemos hablar sobre exactamente lo que dicen los estándares, examinar algunos ejemplos adicionales y luego ver cómo evitar el alias estricto y detectar las violaciones que pasamos por alto. Aquí hay un ejemplo que no debería sorprender ( ejemplo en vivo ):
Tenemos un int * que apunta a la memoria ocupada por un int y este es un alias válido. El optimizador debe asumir que las asignaciones a través de ip podrían actualizar el valor ocupado por x .
El siguiente ejemplo muestra alias que conduce a un comportamiento indefinido ( ejemplo en vivo ):
En la función foo tomamos un int * y un float * , en este ejemplo llamamos a foo y establecemos ambos parámetros para que apunten a la misma ubicación de memoria que en este ejemplo contiene un int . Tenga en cuenta que reinterpret_cast le dice al compilador que trate la expresión como si tuviera el tipo especificado por su parámetro de plantilla. En este caso, le estamos diciendo que trate la expresión & x como si tuviera el tipo float * . Podemos esperar ingenuamente que el resultado del segundo cout sea 0 pero con la optimización habilitada usando -O2, tanto gcc como clang producen el siguiente resultado:
Lo cual puede no esperarse pero es perfectamente válido ya que hemos invocado un comportamiento indefinido. Un flotante no puede alias válidamente un objeto int . Por lo tanto, el optimizador puede asumir que la constante 1 almacenada al desreferenciar i será el valor de retorno ya que una tienda a través de f no podría afectar válidamente un objeto int . Conectar el código en el Explorador de compiladores muestra que esto es exactamente lo que está sucediendo ( ejemplo en vivo ):
El optimizador usando Análisis Alias Tipo-Based (TBAA) asume 1 serán devueltos y se mueve directamente el valor constante en el registro EAX que lleva el valor de retorno. TBAA usa las reglas de idiomas sobre qué tipos están permitidos para alias para optimizar cargas y tiendas. En este caso, TBAA sabe que un flotador no puede alias e int y optimiza la carga de i .
Ahora, al Libro de Reglas
¿Qué dice exactamente la norma que se nos permite y no se nos permite hacer? El lenguaje estándar no es sencillo, por lo que para cada elemento intentaré proporcionar ejemplos de código que demuestren el significado.
¿Qué dice el estándar C11?
El estándar C11 dice lo siguiente en la sección 6.5 Expresiones párrafo 7 :
gcc / clang tiene una extensión y también que permite asignar int * a int * sin firmar aunque no sean tipos compatibles.
Lo que dice el borrador del estándar C ++ 17
El borrador del estándar C ++ 17 en la sección [basic.lval] párrafo 11 dice:
Vale la pena señalar que el carácter firmado no está incluido en la lista anterior, esta es una diferencia notable de C que dice un tipo de carácter .
¿Qué es el tipo Punning?
Hemos llegado a este punto y podemos preguntarnos, ¿por qué querríamos alias? La respuesta generalmente es escribir un juego de palabras , a menudo los métodos utilizados violan estrictas reglas de alias.
A veces queremos evitar el sistema de tipos e interpretar un objeto como un tipo diferente. Esto se denomina punteo de tipo , para reinterpretar un segmento de memoria como otro tipo. La escritura de tipos es útil para tareas que desean acceder a la representación subyacente de un objeto para ver, transportar o manipular. Las áreas típicas que encontramos que se utilizan son los compiladores, la serialización, el código de red, etc.
Tradicionalmente, esto se ha logrado tomando la dirección del objeto, convirtiéndolo en un puntero del tipo con el que queremos reinterpretarlo y luego accediendo al valor, o en otras palabras, aliasing. Por ejemplo:
Como hemos visto anteriormente, este no es un alias válido, por lo que estamos invocando un comportamiento indefinido. Pero tradicionalmente los compiladores no aprovechaban las estrictas reglas de alias y este tipo de código generalmente funcionaba, desafortunadamente los desarrolladores se han acostumbrado a hacer las cosas de esta manera. Un método alternativo común para el tipo de punteo es a través de uniones, que es válido en C pero comportamiento indefinido en C ++ ( ver ejemplo en vivo ):
Esto no es válido en C ++ y algunos consideran que el propósito de las uniones es únicamente para implementar tipos de variantes y sienten que el uso de uniones para el castigo de tipo es un abuso.
¿Cómo escribimos Pun correctamente?
El método estándar para la escritura de tipos en C y C ++ es memcpy . Esto puede parecer un poco pesado, pero el optimizador debe reconocer el uso de memcpy para la escritura de tipo y optimizarlo y generar un registro para registrar el movimiento. Por ejemplo, si sabemos que int64_t tiene el mismo tamaño que el doble :
podemos usar memcpy :
En un nivel de optimización suficiente, cualquier compilador moderno decente genera un código idéntico al método reinterpret_cast mencionado anteriormente o al método de unión para el tipo punning . Examinando el código generado, vemos que usa solo registrar mov ( ejemplo de Live Compiler Explorer ).
C ++ 20 y bit_cast
En C ++ 20 podemos obtener bit_cast ( implementación disponible en el enlace de la propuesta ) que proporciona una forma simple y segura de escribir juegos de palabras, además de ser utilizable en un contexto constexpr.
El siguiente es un ejemplo de cómo usar bit_cast para escribir pun un int no firmado para flotar , ( verlo en vivo ):
En el caso de que los tipos To y From no tengan el mismo tamaño, requiere que usemos una estructura intermedia15. Usaremos una estructura que contenga una matriz de caracteres sizeof (unsigned int) (se supone que 4 bytes unsigned int ) es el tipo From y unsigned int como el tipo To . :
Es lamentable que necesitemos este tipo intermedio, pero esa es la restricción actual de bit_cast .
Capturando violaciones de alias estrictas
No tenemos muchas herramientas buenas para detectar el alias estricto en C ++, las herramientas que tenemos detectarán algunos casos de violaciones de alias estricto y algunos casos de cargas y tiendas desalineadas.
gcc usando la bandera -fstrict-aliasing y -Wstrict-aliasing puede detectar algunos casos, aunque no sin falsos positivos / negativos. Por ejemplo, los siguientes casos generarán una advertencia en gcc ( verlo en vivo ):
aunque no captará este caso adicional ( verlo en vivo ):
Aunque el sonido metálico permite estas banderas, aparentemente no implementa las advertencias.
Otra herramienta que tenemos disponible es ASan, que puede detectar cargas y tiendas desalineadas. Aunque estas no son violaciones de alias estrictamente directas, son un resultado común de violaciones de alias estrictas. Por ejemplo, los siguientes casos generarán errores de tiempo de ejecución cuando se crean con clang usando -fsanitize = address
La última herramienta que recomendaré es específica de C ++ y no estrictamente una herramienta, sino una práctica de codificación, no permita conversiones de estilo C. Tanto gcc como clang producirán un diagnóstico para los lanzamientos de estilo C usando -Wold-style-cast . Esto forzará a cualquier juego de palabras de tipo indefinido a usar reinterpret_cast, en general reinterpret_cast debería ser una bandera para una revisión más detallada del código. También es más fácil buscar en su base de código reinterpret_cast para realizar una auditoría.
Para C tenemos todas las herramientas ya cubiertas y también tenemos tis-interpreter, un analizador estático que analiza exhaustivamente un programa para un gran subconjunto del lenguaje C. Dada una versión en C del ejemplo anterior donde el uso de -fstrict-aliasing pierde un caso ( verlo en vivo )
tis-interpeter es capaz de atrapar a los tres, el siguiente ejemplo invoca tis-kernal como tis-interpreter (la salida se edita por brevedad):
Finalmente está TySan, que actualmente está en desarrollo. Este desinfectante agrega información de verificación de tipos en un segmento de memoria secundaria y verifica los accesos para ver si violan las reglas de alias. La herramienta debería poder detectar todas las infracciones de alias pero puede tener una gran sobrecarga de tiempo de ejecución.
fuente
reinterpret_cast
podría hacer o lo quecout
podría significar. (Está bien mencionar C ++, pero la pregunta original era sobre C y IIUC, estos ejemplos podrían escribirse igualmente en C.)El alias estricto no se refiere solo a los punteros, sino que también afecta a las referencias, escribí un artículo al respecto para la wiki de desarrollador de impulso y fue tan bien recibido que lo convertí en una página en mi sitio web de consultoría. Explica completamente qué es, por qué confunde tanto a las personas y qué hacer al respecto. Libro blanco de alias estricto . En particular, explica por qué las uniones son un comportamiento arriesgado para C ++, y por qué usar memcpy es la única solución portátil en C y C ++. Espero que esto sea útil.
fuente
Como anexo a lo que Doug T. ya escribió, aquí hay un caso de prueba simple que probablemente lo desencadena con gcc:
check.c
Compilar con
gcc -O2 -o check check.c
. Usualmente (con la mayoría de las versiones de gcc que probé) esto genera un "problema de alias estricto", porque el compilador supone que "h" no puede ser la misma dirección que "k" en la función "verificar". Por eso, el compilador optimiza elif (*h == 5)
ausente y siempre llama a printf.Para aquellos que estén interesados aquí está el código de ensamblador x64, producido por gcc 4.6.3, que se ejecuta en ubuntu 12.04.2 para x64:
Entonces, la condición if desapareció por completo del código del ensamblador.
fuente
long long*
yint64_t
*). Uno podría esperar que un compilador sensata debería reconocer que unalong long*
yint64_t*
podría tener acceso a la misma de almacenamiento si están almacenados de forma idéntica, pero dicho tratamiento ya no está de moda.La escritura de tipos mediante el uso de punteros (en lugar de usar una unión) es un ejemplo importante de romper el alias estricto.
fuente
fpsync()
directiva entre escribir como fp y leer como int o viceversa [en implementaciones con tuberías y cachés de enteros y FPU por separado , tal directiva podría ser costosa, pero no tan costosa como hacer que el compilador realice dicha sincronización en cada acceso de unión]. O una implementación podría especificar que el valor resultante nunca será utilizable, excepto en circunstancias que utilizan secuencias iniciales comunes.Según la justificación de C89, los autores de la Norma no querían exigir que los compiladores dieran código como:
se debe exigir que vuelva a cargar el valor de
x
entre la asignación y la declaración de retorno para permitir la posibilidad de quep
pueda apuntarx
, y la asignación de*p
puede alterar en consecuencia el valor dex
. La noción de que un compilador debería tener derecho a suponer que no habrá alias en situaciones como las anteriores no fue controvertida.Desafortunadamente, los autores del C89 escribieron su regla de una manera que, si se lee literalmente, haría que incluso la siguiente función invoque Comportamiento indefinido:
porque usa un valor de tipo l
int
para acceder a un objeto de tipostruct S
, yint
no está entre los tipos que se pueden usar para acceder a unstruct S
. Debido a que sería absurdo tratar todo uso de miembros de estructuras y uniones que no sean del tipo de caracteres como Comportamiento indefinido, casi todos reconocen que hay al menos algunas circunstancias en las que se puede usar un valor de un tipo para acceder a un objeto de otro tipo . Lamentablemente, el Comité de Normas C no ha podido definir cuáles son esas circunstancias.Gran parte del problema es el resultado del Informe de defectos # 028, que preguntó sobre el comportamiento de un programa como:
El Informe de defectos n. ° 28 establece que el programa invoca Comportamiento indefinido porque la acción de escribir un miembro de unión de tipo "double" y leer uno de tipo "int" invoca un comportamiento definido por la implementación. Tal razonamiento no tiene sentido, pero forma la base de las reglas de Tipo efectivo que complican innecesariamente el lenguaje sin hacer nada para abordar el problema original.
La mejor manera de resolver el problema original probablemente sería tratar la nota al pie de página sobre el propósito de la regla como si fuera normativa, y hacer que la regla no se pueda hacer cumplir, excepto en los casos que realmente involucran accesos conflictivos usando alias. Dado algo como:
No hay conflicto en el interior
inc_int
porque todos los accesos al almacenamiento al que se accede*p
se realizan con un valor de tipo lint
, y no hay conflictotest
porquep
se deriva visiblemente de unstruct S
, y para la próxima vez ques
se use, todos los accesos a ese almacenamiento se realizarán alguna vez a travésp
ya habrá sucedido.Si el código fuera cambiado ligeramente ...
Aquí, existe un conflicto de alias
p
y el acceso as.x
en la línea marcada porque en ese punto de ejecución existe otra referencia que se utilizará para acceder al mismo almacenamiento .Si el Informe de defectos 028 decía que el ejemplo original invocaba a UB debido a la superposición entre la creación y el uso de los dos punteros, eso habría aclarado mucho las cosas sin tener que agregar "Tipos efectivos" u otra complejidad similar.
fuente
Después de leer muchas de las respuestas, siento la necesidad de agregar algo:
El alias estricto (que describiré en un momento) es importante porque :
El acceso a la memoria puede ser costoso (en cuanto al rendimiento), por lo que los datos se manipulan en los registros de la CPU antes de volver a escribirse en la memoria física.
Si los datos en dos registros de CPU diferentes se escribirán en el mismo espacio de memoria, no podemos predecir qué datos "sobrevivirán" cuando codifiquemos en C.
En el ensamblaje, donde codificamos la carga y descarga de los registros de la CPU manualmente, sabremos qué datos permanecen intactos. Pero C (afortunadamente) abstrae este detalle.
Dado que dos punteros pueden apuntar a la misma ubicación en la memoria, esto podría resultar en un código complejo que maneja posibles colisiones .
Este código adicional es lento y perjudica el rendimiento ya que realiza operaciones adicionales de lectura / escritura de memoria que son más lentas y (posiblemente) innecesarias.
La regla de alias estricto nos permite evitar el código de máquina redundante en los casos en que debería ser seguro asumir que dos punteros no apuntan al mismo bloque de memoria (ver también la
restrict
palabra clave).El alias estricto indica que es seguro asumir que los punteros a diferentes tipos apuntan a diferentes ubicaciones en la memoria.
Si un compilador nota que dos punteros apuntan a diferentes tipos (por ejemplo, an
int *
y afloat *
), asumirá que la dirección de memoria es diferente y no protegerá contra colisiones de direcciones de memoria, lo que resulta en un código de máquina más rápido.Por ejemplo :
Asumamos la siguiente función:
Para manejar el caso en el que
a == b
(ambos punteros apuntan a la misma memoria), necesitamos ordenar y probar la forma en que cargamos datos de la memoria a los registros de la CPU, por lo que el código podría terminar así:carga
a
yb
de memoria.añadir
a
ab
.guardar
b
y recargara
.(guardar desde el registro de la CPU en la memoria y cargar desde la memoria en el registro de la CPU).
añadir
b
aa
.guardar
a
(desde el registro de la CPU) en la memoria.El paso 3 es muy lento porque necesita acceder a la memoria física. Sin embargo, se requiere para proteger contra instancias donde
a
yb
apuntar a la misma dirección de memoria.El alias estricto nos permitiría evitar esto al decirle al compilador que estas direcciones de memoria son claramente diferentes (lo que, en este caso, permitirá una optimización aún mayor que no se puede realizar si los punteros comparten una dirección de memoria).
Esto se puede decir al compilador de dos maneras, utilizando diferentes tipos para señalar. es decir:
Usando la
restrict
palabra clave. es decir:Ahora, al cumplir la regla de Alias estricto, se puede evitar el paso 3 y el código se ejecutará significativamente más rápido.
De hecho, al agregar la
restrict
palabra clave, toda la función podría optimizarse para:carga
a
yb
de memoria.añadir
a
ab
.guardar resultado tanto para
a
como parab
.Esta optimización no podría haberse hecho antes, debido a la posible colisión (dónde
a
yb
se triplicaría en lugar de duplicarse).fuente
b
(no volviendo a cargarlo) y volviendo a cargara
. Espero que sea más claro ahora.restrict
, pero creo que este último en la mayoría de las circunstancias sería más efectivo, y aflojar algunas restriccionesregister
le permitiría completar algunos de los casos en losrestrict
que no ayudaría. No estoy seguro de que alguna vez fue "importante" tratar el Estándar como una descripción completa de todos los casos en que los programadores deben esperar que los compiladores reconozcan la evidencia de alias, en lugar de simplemente describir los lugares donde los compiladores deben suponer alias, incluso cuando no existe evidencia particular de ello .restrict
palabra clave minimiza no solo la velocidad de las operaciones, sino también su número, lo que podría ser significativo ... Quiero decir, después de todo, la operación más rápida es ninguna operación :)El alias estricto no permite diferentes tipos de puntero a los mismos datos.
Este artículo debería ayudarlo a comprender el problema con todo detalle.
fuente
int
una estructura que contiene unint
).Técnicamente en C ++, la estricta regla de alias probablemente nunca sea aplicable.
Tenga en cuenta la definición de indirección ( * operador ):
También de la definición de glvalue
Entonces, en cualquier rastreo de programa bien definido, un valor de gl se refiere a un objeto. Por lo tanto, la llamada regla de alias estricto no se aplica, nunca. Esto puede no ser lo que los diseñadores querían.
fuente
int foo;
, ¿a qué accede la expresión lvalue*(char*)&foo
? ¿Es eso un objeto de tipochar
? ¿Ese objeto llega a existir al mismo tiempo quefoo
? ¿Escribiría parafoo
cambiar el valor almacenado de ese objeto de tipo mencionado anteriormentechar
? Si es así, ¿hay alguna regla que permitachar
acceder al valor almacenado de un objeto de tipo utilizando un valor de tipo lint
?int i;
crea cuatro objetos de cada tipo de carácterin addition to one of type
int? I see no way to apply a consistent definition of "object" which would allow for operations on both
* (char *) & i` yi
. Finalmente, no hay nada en el Estándar que permita que incluso unvolatile
puntero calificado acceda a registros de hardware que no cumplan con la definición de "objeto".