2012-01-12 9 views
17

A diferencia de C++, C no tiene noción de const_cast. Es decir, no hay manera válida para convertir un puntero const-calificado para un puntero sin salvedades:¿Es const-casting a través de un comportamiento indefinido de unión?

void const * p; 
void * q = p; // not good 

En primer lugar: ¿Es este elenco de hecho un comportamiento indefinido?

En cualquier caso, GCC advierte sobre esto. Para hacer un código "limpio" que requiera un molde constante (es decir, donde puedo garantizar que no mutaré el contenido, pero todo lo que tengo es un puntero mutable), he visto el siguiente truco de "conversión":

typedef union constcaster_ 
{ 
    void * mp; 
    void const * cp; 
} constcaster; 

Uso: u.cp = p; q = u.mp;.

¿Cuáles son las reglas del lenguaje C para eliminar la constness a través de dicha unión? Mi conocimiento de C es muy desigual, pero he escuchado que C es mucho más indulgente con el acceso a la unión que C++, así que aunque tengo un mal presentimiento sobre esta construcción, me gustaría un argumento del estándar (C99 supongo, aunque si esto ha cambiado en C11 será bueno saberlo).

+1

¿Quieres decir '* q = (*) p 'en el primer bloque de código vacío vacío? – kennytm

+0

@KennyTM: Sí, ¿necesito el lanzamiento explícito, y/o hace la diferencia? –

+0

Sin el lanzamiento implícito, gcc advierte sobre el descarte de los calificadores. Pero gcc aún informa un error ("el elemento inicializador no es constante") incluso con el molde. –

Respuesta

10

Se define la aplicación, ver 6.5.2.3/5 C99:

si el valor de un miembro de un objeto de unión se utiliza cuando la tienda más reciente que el objetivo era un miembro diferente, el comportamiento es implementación definida.

Actualización: @AaronMcDaid comentó que esto podría estar bien definido, después de todo.

la norma especificada la siguiente 6.2.5/27:

Del mismo modo, los punteros a las versiones calificado o no calificado de compatibles tipos tendrán la misma representación y alineación requirements.27)

27 Los mismos requisitos de representación y alineación están destinados a implican intercambiabilidad como argumentos para funciones, valores de retorno de las funciones y miembros de uniones.

Y (6.7.2.1/14):

Un puntero a un objeto de unión, adecuadamente convertidos, los puntos a cada uno de sus miembros (o si un miembro es un campo de bits, a continuación, a la unidad en la que reside ), y viceversa.

Una poder concluir que, en este caso particular,, sólo hay espacio para exactamente una forma de acceder a los elementos de la unión.

+2

+1 para la respuesta directa a la pregunta del OP :) – Kos

+1

¡Agradable! Entonces, ¿no estoy seguro de que esto realmente hará lo que pienso? –

+1

Bueno, "implementación definida" significa que el proveedor del compilador debe especificar qué sucede. Sin embargo, no puede confiar en que funcione para * todos los compiladores. – Lindydancer

3

Entiendo que el UB puede surgir solo si intenta modificar un objeto const-declared.

Así que el código siguiente no es UB:

int x = 0; 
const int *cp = &x; 
int *p = (int*)cp; 
*p = 1; /* OK: x is not a const object */ 

Pero esto es UB:

const int cx = 0; 
const int *cp = &cx; 
int *p = (int*)cp; 
*p = 1; /* UB: cx is const */ 

El uso de una unión en lugar de un reparto no debe hacer ninguna diferencia en este caso.

De las especificaciones C99 (fase de clasificación 6.7.3 Tipo):

Si se hace un intento de modificar un objeto definido con un tipo const cualificado a través del uso de un lvalue con no constante cualificado tipo, el comportamiento no está definido.

+1

Puedes asumir que nunca mudo nada. Es decir, tengo una operación sin mutación que necesita tratar con un puntero mutable heredado. Entiendo que violar la constness es UB - mi pregunta es principalmente sobre el malabares del puntero. En C++ solo diría 'void * q = const_cast (p);' ... –

+0

@KerrekSB No es 'const_cast ' solo azúcar sintáctica para '(void *)' (quizás con algunas verificaciones adicionales pero iguales) comportamiento)? –

+2

@ChristianRau: No, es parte del lenguaje y del sistema de tipos. Si lo hace, todo el sistema de tipos es solo azúcar sintáctico sobre el ensamblador, por lo que la distinción es importante aquí ... –

0

No lo moldee para nada. Es un puntero a const, lo que significa que no se permite el intento de modificar los datos y en muchas implementaciones hará que el programa falle si el puntero apunta a una memoria no modificable. Incluso si sabe que la memoria puede modificarse, puede haber otros indicadores que no esperen que cambie, p. si es parte del almacenamiento de una cadena lógicamente inmutable.

La advertencia está ahí por una buena razón.

Si necesita modificar el contenido de un puntero de const, la manera segura de hacerlo primero es copiar la memoria a la que apunta y luego modificarla.

+0

De sus comentarios, creo que es consciente de estas implicaciones y quiere pasarlo a una función heredada, aceptando un puntero no const y no modificando los datos (cualesquiera que sean las razones para que esto tome una no-const en primer lugar) Y, por cierto, ¿cuál es la respuesta a la pregunta real? –

+0

@Christian Rau: si sabes que la función de la herencia no modifica los datos, entonces un molde explícito estaría bien como en el comentario de KennyTM sobre la pregunta original. – JeremyP

+1

@Kerrek SB C le permite pasar un puntero no const a un parámetro const. 'const char * p = q;' donde q es char * está bien. – JeremyP

3

La inicialización ciertamente no causará UB. La conversión entre tipos de punteros calificados está explícitamente permitida en §6.3.2.3/2 (n1570 (C11)). Es el uso de contenido en ese puntero después que causa UB (vea la respuesta de @rodrigo).

Sin embargo, necesita un molde explícito para convertir un void* en un const void*, porque la restricción de asignación simple aún requiere que todos los calificadores en el LHS aparezcan en el RHS.

§6.7.9/11: ... El valor inicial del objeto es el de la expresión (después de la conversión); se aplican las mismas restricciones de tipo y conversiones que para la asignación simple, tomando el tipo de escalar para ser la versión no calificada de su tipo declarado.

§6.5.16.1/1: (Asignación simple/Contraints)

  • ... ambos operandos son punteros a versiones cualificados o no de tipos compatibles, y el tipo apuntaban a por el left tiene todos los calificadores del tipo señalado por la derecha;
  • ... un operando es un puntero a un tipo de objeto, y el otro es un puntero a una versión calificada o no calificada de void, y el tipo apuntado a la izquierda tiene todos los calificadores del tipo apuntado a por la derecha;

No sé por qué gcc acaba de dar una advertencia.


Y para el truco de la unión, sí, no es UB, pero aún así el resultado es probablemente no especificado.

§6.5.2.3/3 fn 95: Si el miembro usa para leer el contenido de un objeto de unión no es el mismo que el último miembro utiliza para almacenar un valor en el objeto, la parte apropiada de la representación del objeto del valor se reinterpreta como una representación del objeto en el nuevo tipo, como se describe en 6.2.6 (un proceso que a veces se denomina "tipo de juego de palabras"). Esto podría ser una representación de trampa.

§6.2.6.1/7: Cuando un valor se almacena en un miembro de un objeto de tipo unión, los bytes de la representación del objeto que no corresponden a ese miembro pero corresponden a otros miembros toman valores no especificados . (* Nota: véase también §6.5.2.3/6 para una excepción, pero no se aplica aquí)


Las secciones correspondientes en n1124 (C99) son

  • C11 § 6.3.2.3/2 = C99 §6.3.2.3/2
  • C11 §6.7.9/11 = C99 §6.7.8/11
  • C11 §6.5.16.1/1 = C99 §6.5.16.1/1
  • C11 §6.5.2.3/3 fn 95 = faltante ("tipo de juego de palabras" no aparece n C99)
  • C11 §6.2.6.1/7 = C99 §6.2.6.1/7
+0

Genial, gracias! Entonces, ¿crees que la mejor manera de escribir el código C es tener un reparto explícito cuando sea necesario y pasar '-Wno-cast-qual' a GCC? –

+0

@KerrekSB: Ciertamente estoy a favor de advertencias más estrictas sobre moldes implícitos que pueden causar problemas en el futuro ☺, y probablemente sea mejor utilizar '#pragma GCC diagnostic' para minimizar la región donde la advertencia está deshabilitada. – kennytm

+0

Aunque esta es la mejor respuesta, ¿me respaldaría si aceptara una respuesta de representante inferior? Se trata de la reputación-motivación-utilidad, ves :-) –

Cuestiones relacionadas