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
struct
um const
campo 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 const
campo _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_t
tipo.
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 const
campos não- const
struct
serem realmente colocados no armazenamento somente leitura. Mas essa redação do padrão proíbe o código que deliberadamente rejeita o const
qualificador 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 struct
seja somente leitura, basta declará-loconst
const S_t s3;
Mas, para que um todo struct
seja 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-
const
estruturas comconst
campos e emitir um diagnóstico nesse caso. Isso deixaria claro que osstruct
campos contendo somente leitura são somente leitura. - Definir o comportamento em caso de gravação em um
const
campo pertencente a uma não-const
estrutura, 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?
Respostas:
Apenas de uma perspectiva de tipo, não fazê-lo seria doentio (em outras palavras: terrivelmente quebrado e intencionalmente não confiável).
E isso é por causa do que "=" significa em uma estrutura: é uma atribuição recursiva. Segue-se que, eventualmente, você está
s1._a = <value>
acontecendo "dentro das regras de digitação". Se o padrão permitir isso paraconst
campos "aninhados" , isso adicionará uma inconsistência séria em sua definição de sistema de tipos como uma contradição explícita (pode muito bem jogar oconst
recurso fora, pois ele se tornou inútil e não confiável por sua própria definição).Sua solução (1), pelo que entendi, está forçando desnecessariamente toda a estrutura a estar
const
sempre que um de seus campos estiverconst
. Dessa forma,s1._b = b
seria ilegal para um._b
campo não const em um não const ques1
contenha aconst a
.fonte
C
mal tem um sistema de som (mais como um monte de caixas de canto amarradas umas às outras ao longo dos anos). Além disso, a outra maneira de atribuir a tarefa astruct
émemcpy(s_dest, s_src, sizeof(S_t))
. E tenho certeza de que é a maneira real como é implementada. E nesse caso, mesmo o "sistema de tipos" existente não o proíbe.O motivo é que os campos somente leitura são somente leitura. Não há grande surpresa lá.
Você está assumindo erroneamente que o único efeito é o posicionamento na ROM, o que é realmente impossível quando existem campos não-const adjacentes. Na realidade, os otimizadores podem assumir que as
const
expressões não foram gravadas e otimizar com base nisso. É claro que essa suposição não se aplica quando existem pseudônimos não constantes.Sua solução (1) quebra o código legal e razoável existente. Isso não vai acontecer. Sua solução (2) praticamente remove o significado de
const
membros. Enquanto isso não quebra o código existente, parece não ter uma justificativa.fonte
const
campos não sejam gravados, porque você sempre pode usarmemset
ormemcpy
, e isso seria compatível com o Padrão. (1) pode ser implementado como, no mínimo, aviso adicional, ativado por uma bandeira. (2) a lógica é que, bem, exatamente - não há como um componentestruct
ser considerado não gravável quando toda a estrutura é gravável.operator=
em termos de membros e, portanto, não define aoperator=
quando um membro éconst
. C e C ++ ainda são compatíveis aqui.memcpy
? Por outras razões - ok, é legado, mas por que foi feito dessa maneira em primeiro lugar?memcpy
está correto. AFACIT A citação de John Bode em sua outra pergunta está certa: seu código grava em um objeto qualificado como const e, portanto, NÃO é uma reclamação padrão, final da discussão.