O padrão C99 diz em 6.5.16: 2:
Um operador de atribuição deve ter um valor modificável como seu operando esquerdo.
e em 6.3.2.1:1:
Um lvalue modificável é um lvalue que não possui um tipo de matriz, não possui um tipo incompleto, não possui um tipo qualificado de const e, se for uma estrutura ou união, não possui nenhum membro (incluindo, recursivamente, qualquer membro ou elemento de todos os agregados ou uniões contidos) com um tipo qualificado de const.
Agora, vamos considerar const structum constcampo não- com .
typedef struct S_s {
const int _a;
} S_t;
Por padrão, o código a seguir é um comportamento indefinido (UB):
S_t s1;
S_t s2 = { ._a = 2 };
s1 = s2;
O problema semântico disso é que a entidade envolvente ( struct) deve ser considerada gravável (não somente leitura), a julgar pelo tipo declarado da entidade ( S_t s1), mas não deve ser considerada gravável pela redação do padrão (as 2 cláusulas no topo) por causa do constcampo _a. O Padrão deixa claro para um programador que lê o código que a atribuição é realmente um UB, porque é impossível dizer que sem a definição de struct S_s ... S_ttipo.
Além disso, o acesso somente leitura ao campo é imposto de forma sintática apenas de qualquer maneira. Não há como, de fato, alguns constcampos não- const structserem realmente colocados no armazenamento somente leitura. Mas essa redação do padrão proíbe o código que deliberadamente rejeita o constqualificador de campos nos procedimentos de acessador desses campos, assim ( é uma boa idéia qualificar os campos de estrutura em 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);
}
Portanto, por algum motivo, para que um todo structseja somente leitura, basta declará-loconst
const S_t s3;
Mas, para que um todo structseja não somente leitura, não é suficiente declará-lo sem const.
O que eu acho que seria melhor, é:
- Restringir a criação de não-
constestruturas comconstcampos e emitir um diagnóstico nesse caso. Isso deixaria claro que osstructcampos contendo somente leitura são somente leitura. - Definir o comportamento em caso de gravação em um
constcampo pertencente a uma não-constestrutura, a fim de tornar o código acima (*) compatível com a Norma.
Caso contrário, o comportamento não é consistente e difícil de entender.
Então, qual é a razão para o Padrão C considerar o const-ness recursivamente, como ele diz?