El estándar C99 dice en 6.5.16: 2:
Un operador de asignación tendrá un valor l modificable como su operando izquierdo.
y en 6.3.2.1:1:
Un lvalue modificable es un lvalue que no tiene un tipo de matriz, no tiene un tipo incompleto, no tiene un tipo const y, si es una estructura o unión, no tiene ningún miembro (incluido, recursivamente, ningún miembro o elemento de todos los agregados o uniones contenidos) con un tipo calificado const.
Ahora, consideremos un no const
struct
con un const
campo.
typedef struct S_s {
const int _a;
} S_t;
Por norma, el siguiente código es comportamiento indefinido (UB):
S_t s1;
S_t s2 = { ._a = 2 };
s1 = s2;
El problema semántico con esto es que la entidad que la encierra ( struct
) debe considerarse escribible (no solo de lectura), a juzgar por el tipo declarado de la entidad ( S_t s1
), pero no debe considerarse escribible por la redacción del estándar (las 2 cláusulas en la parte superior) debido al const
campo _a
. El estándar no deja claro para un programador que lee el código que la asignación es en realidad una UB, porque es imposible decir que sin la definición de struct S_s ... S_t
tipo.
Además, el acceso de solo lectura al campo solo se aplica sintácticamente de todos modos. No hay forma de que algunos const
campos que no const
struct
sean realmente se coloquen en almacenamiento de solo lectura. Pero tal redacción de los proscritos estándar del código que desecha deliberadamente el const
calificador de campos en los procedimientos de acceso de estos campos, así ( ¿Es una buena idea consolidar los campos de estructura en C? ):
(*)
#include <stdlib.h>
#include <stdio.h>
typedef struct S_s {
const int _a;
} S_t;
S_t *
create_S(void) {
return calloc(sizeof(S_t), 1);
}
void
destroy_S(S_t *s) {
free(s);
}
const int
get_S_a(const S_t *s) {
return s->_a;
}
void
set_S_a(S_t *s, const int a) {
int *a_p = (int *)&s->_a;
*a_p = a;
}
int
main(void) {
S_t s1;
// s1._a = 5; // Error
set_S_a(&s1, 5); // OK
S_t *s2 = create_S();
// s2->_a = 8; // Error
set_S_a(s2, 8); // OK
printf("s1.a == %d\n", get_S_a(&s1));
printf("s2->a == %d\n", get_S_a(s2));
destroy_S(s2);
}
Entonces, por alguna razón, para que un todo struct
sea de solo lectura es suficiente declararloconst
const S_t s3;
Pero para que un todo struct
no sea de solo lectura, no es suficiente declararlo sin él const
.
Lo que creo que sería mejor es:
- Para restringir la creación de no
const
estructuras conconst
campos, y emitir un diagnóstico en tal caso. Eso dejaría en claro que losstruct
campos que contienen solo lectura son solo de lectura. - Para definir el comportamiento en caso de escribir en un
const
campo perteneciente a unaconst
estructura no estructurada para que el código anterior (*) cumpla con el Estándar.
De lo contrario, el comportamiento no es consistente y difícil de entender.
Entonces, ¿cuál es la razón por la cual C Standard considera la const
recursividad recursivamente, como dice?
Respuestas:
Desde una perspectiva de tipo solo, no hacerlo sería poco sólido (en otras palabras: terriblemente roto e intencionalmente poco confiable).
Y eso se debe a lo que "=" significa en una estructura: es una asignación recursiva. De esto se deduce que eventualmente
s1._a = <value>
ocurre "dentro de las reglas de escritura". Si el estándar permite esto para losconst
campos "anidados" , está agregando una seria inconsistencia en su definición del sistema de tipos como una contradicción explícita (también podríaconst
descartar la característica, ya que se volvió inútil y poco confiable por su propia definición).Su solución (1), por lo que yo entiendo, está forzando innecesariamente a que toda la estructura sea
const
cada vez que uno de sus campos esconst
. De esta manera,s1._b = b
sería ilegal para un._b
campo no constante en un campo no constante ques1
contiene unconst a
.fuente
C
Apenas tiene un sistema de tipo de sonido (más como un montón de cajas de esquina atadas entre sí a lo largo de los años). Además, la otra forma de asignar la tarea a astruct
esmemcpy(s_dest, s_src, sizeof(S_t))
. Y estoy bastante seguro de que es la forma en que se implementa. Y en tal caso, incluso el "sistema de tipos" existente no le prohíbe hacerlo.La razón es que los campos de solo lectura son de solo lectura. No hay gran sorpresa allí.
Asume erróneamente que el único efecto está en la colocación en ROM, lo que de hecho es imposible cuando hay campos adyacentes no constantes. En realidad, los optimizadores pueden asumir que las
const
expresiones no están escritas y optimizar en función de eso. Por supuesto, esa suposición no se cumple cuando existen alias no constantes.Su solución (1) rompe el código legal y razonable existente. Eso no va a suceder. Su solución (2) prácticamente elimina el significado de
const
los miembros. Si bien esto no romperá el código existente, parece carecer de una justificación.fuente
const
campos no están escritos, porque siempre puedes usarmemset
omemcpy
, y eso incluso sería compatible con el Estándar. (1) puede implementarse como, como mínimo, advertencia adicional, habilitada por una bandera. La justificación de (2) es que, bueno, exactamente, no hay forma de que un componentestruct
pueda considerarse no escribible cuando toda la estructura es escribible.operator=
en términos de los miembros, y por lo tanto no defineoperator=
cuándo es un miembroconst
. C y C ++ siguen siendo compatibles aquí.memcpy
? En cuanto a otras razones, está bien, es legado, pero ¿por qué se hizo de esa manera en primer lugar?memcpy
es correcto. AFACITO La cita de John Bode en su otra pregunta es correcta: su código escribe en un objeto calificado const y, por lo tanto, NO es una queja estándar, fin de la discusión.