Considere a seguinte estrutura:
struct s {
int a, b;
};
Normalmente 1 , essa estrutura terá tamanho 8 e alinhamento 4.
E se criarmos dois struct s
objetos (mais precisamente, gravamos no armazenamento alocado dois desses objetos), com o segundo objeto sobreposto ao primeiro?
char *storage = malloc(3 * sizeof(struct s));
struct s *o1 = (struct s *)storage; // offset 0
struct s *o2 = (struct s *)(storage + alignof(struct s)); // offset 4
// now, o2 points half way into o1
*o1 = (struct s){1, 2};
*o2 = (struct s){3, 4};
printf("o2.a=%d\n", o2->a);
printf("o2.b=%d\n", o2->b);
printf("o1.a=%d\n", o1->a);
printf("o1.b=%d\n", o1->b);
Existe algo sobre esse comportamento indefinido do programa? Se sim, onde fica indefinido? Se não for UB, é garantido que você sempre imprima o seguinte:
o2.a=3
o2.b=4
o1.a=1
o1.b=3
Em particular, quero saber o que acontece com o objeto apontado por o1
quando o2
, que se sobrepõe, é gravado. Ainda é permitido acessar a parte desobstruída ( o1->a
)? O acesso à parte estragada o1->b
é o mesmo que o acesso o2->a
?
Como o tipo eficaz se aplica aqui? As regras são claras o suficiente quando você está falando sobre objetos e ponteiros que não se sobrepõem e apontam para o mesmo local da última loja, mas quando você começa a falar sobre o tipo eficaz de partes de objetos ou objetos sobrepostos, fica menos claro.
Alguma coisa mudaria se a segunda gravação fosse de um tipo diferente? Se os membros fossem dizer int
e em short
vez de dois int
s?
Aqui está um godbolt, se você quiser brincar com ele lá.
1 Esta resposta se aplica a plataformas onde também não é o caso: por exemplo, algumas podem ter tamanho 4 e alinhamento 2. Em uma plataforma em que o tamanho e o alinhamento eram iguais, essa pergunta não se aplicaria, pois objetos alinhados e sobrepostos ser impossível, mas não tenho certeza se existe alguma plataforma como essa.
Respostas:
Basicamente, essa é toda a área cinza do padrão; a regra de aliasing estrita especifica casos básicos e deixa o leitor (e os fornecedores do compilador) preencher os detalhes.
Houve um esforço para escrever uma regra melhor, mas até agora eles não resultaram em nenhum texto normativo e não tenho certeza qual é o status disso para o C2x.
Como mencionado na minha resposta à sua pergunta anterior, a interpretação mais comum é que isso
p->q
significa(*p).q
e o tipo efetivo se aplica a todos*p
, mesmo depois de aplicarmos.q
.Sob essa interpretação,
printf("o1.a=%d\n", o1->a);
causaria um comportamento indefinido, pois o tipo efetivo do local*o1
não és
(uma vez que parte dele foi substituída).A justificativa para essa interpretação pode ser vista em uma função como:
Com essa interpretação, a última linha poderia ser otimizada
puts("5");
, mas sem ela, o compilador precisaria considerar que a chamada de função pode ter sidof(o1, o2);
e, portanto, perderá todos os benefícios que são supostamente fornecidos pela regra de aliasing estrita.Um argumento semelhante se aplica a dois tipos de estrutura não relacionados que possuem um
int
membro em deslocamento diferente.fonte
f(s* s1, s* s2)
, semrestrict
, o compilador não pode assumirs1
es2
são indicadores diferentes. Eu acho que , novamente semrestrict
, não pode nem assumir que eles não se sobrepõem parcialmente. IAC, não vejo que a preocupação do OP seja bem demonstrada pelaf()
analogia. Boa sorte invencível. UV para o primeiro semestre.s1 == s2
seria permitido, mas não se sobrepõe parcialmente. (A otimização no meu exemplo de código ainda pode ser executada ses1 == s2
)int
vez de estruturas (e um sistema com_Alignof(int) < sizeof(int)
).p->q
e(*p).q
. Isso pode ser verdade para a interpretação do tipo, como você declara, mas não é verdade do ponto de vista operacional. É importante para acessos simultâneos à mesma estrutura que o acesso de um membro não implica o acesso de nenhum outro membro.E1.E2
expressão não realiza acesso (quero dizer aE1
expressão inteira . Algumas de suas subexpressões podem realizar acesso. Ou seja, seE1
for(*p)
, então, ler o valor do ponteiro ao avaliar op
acesso, mas a avaliação*p
ou(*p)
não realiza nenhum Acesso). A regra estrita de alias não se aplica quando não há acesso.