Fiquei com a impressão de que acessar um union
membro que não seja o último conjunto é UB, mas não consigo encontrar uma referência sólida (além de respostas afirmando que é UB, mas sem nenhum suporte do padrão).
Então, é um comportamento indefinido?
c++
undefined-behavior
language-lawyer
unions
Luchian Grigore
fonte
fonte
Respostas:
A confusão é que C permite explicitamente punções de tipo por meio de uma união, enquanto C ++ (c ++ 11) não tem essa permissão.
A situação com C ++:
O C ++ posteriormente possui uma linguagem que permite o uso de uniões contendo
struct
s com seqüências iniciais comuns; no entanto, isso não permite punição de tipo.Para determinar se a punção de tipo de união é permitida em C ++, precisamos procurar mais. Lembre-se quec99 é uma referência normativa para C ++ 11 (e C99 possui linguagem semelhante à C11, permitindo punção de tipo de união):
Fica particularmente interessante quando lemos
Portanto, para um tipo primitivo (cujo ipso facto possui inicialização trivial) contido em uma união, o tempo de vida do objeto abrange pelo menos o tempo de vida da própria união. Isso nos permite invocar
Supondo que a operação na qual estamos interessados seja punitiva, ou seja, assumindo o valor de um membro do sindicato não ativo, e dado acima, que temos uma referência válida ao objeto referido por esse membro, essa operação é lvalue-to -rvalue conversion:
A questão então é se um objeto que é um membro da união não ativo é inicializado por armazenamento no membro da união ativo. Tanto quanto posso dizer, este não é o caso e, portanto, se:
char
armazenamento e retorno da matriz (3.9: 2) ouo acesso a uma união por um membro não ativo é definido e segue a representação de objeto e valor; o acesso sem uma das interposições acima é um comportamento indefinido. Isso tem implicações para as otimizações permitidas a serem executadas em um programa desse tipo, pois a implementação pode, é claro, presumir que um comportamento indefinido não ocorra.
Ou seja, embora possamos legitimamente formar um valor mínimo para um membro do sindicato não ativo (é por isso que atribuir a um membro não ativo sem construção é aceitável), ele é considerado não inicializado.
fonte
memcpy
implementações personalizadas (acessar objetos usandounsigned char
lvalues), proibia acessos a*p
afterint *p = 0; const int *const *pp = &p;
(mesmo que a conversão implícita deint**
paraconst int*const*
fosse válida), proibia mesmo acessarc
depoisstruct S s; const S &c = s;
. Edição 616 do CWG . A nova redação permite isso? Há também [basic.lval].&
operador unário significa quando aplicado a um membro do sindicato. Eu acho que o ponteiro resultante deve ser utilizável para acessar o membro pelo menos até a próxima vez que o próximo uso direto ou indireto de qualquer outro valor de membro, mas no gcc o ponteiro não é utilizável por tanto tempo, o que levanta uma questão sobre o que o&
operador deve querer dizer.O padrão C ++ 11 diz desta maneira
Se apenas um valor é armazenado, como você pode ler outro? Simplesmente não está lá.
A documentação do gcc lista isso em Comportamento definido por implementação
indicando que isso não é exigido pelo padrão C.
05-01/2016: Através dos comentários, fui vinculado ao Relatório de Defeitos C99 # 283, que adiciona um texto semelhante a uma nota de rodapé ao documento padrão C:
Não tenho certeza se isso esclarece muito, considerando que uma nota de rodapé não é normativa para o padrão.
fonte
Eu acho que o mais próximo que o padrão chega de dizer que é um comportamento indefinido é onde ele define o comportamento de uma união que contém uma sequência inicial comum (C99, §6.5.2.3 / 5):
O C ++ 11 fornece requisitos / permissão semelhantes em §9.2 / 19:
Embora nenhum deles o exponha diretamente, ambos têm uma forte implicação de que "inspecionar" (ler) um membro é "permitido" apenas se 1) for (parte do) membro mais recentemente escrito ou 2) fizer parte de uma inicial comum seqüência.
Essa não é uma afirmação direta de que fazer o contrário é um comportamento indefinido, mas é o mais próximo do qual estou ciente.
fonte
union
s sendo indefinidos, já que um blog em particular me deu a impressão de que isso era bom e construí várias estruturas e projetos grandes em torno dele. Agora acho que posso estar bem, afinal, já que meusunion
s contêm classes com os mesmos tipos na frenteunion
contiver, por exemplo, auint8_t
e aclass Something { uint8_t myByte; [...] };
- eu assumiria que essa condição também se aplicaria aqui, mas é redigida com muita deliberação para permitir apenasstruct
s. Felizmente, eu já estou usando esses em vez de primitivos brutos: OAlgo que ainda não é mencionado pelas respostas disponíveis é a nota de rodapé 37 no parágrafo 21 da seção 6.2.5:
Esse requisito parece implicar claramente que você não deve escrever em um membro e ler em outro. Nesse caso, pode ser um comportamento indefinido por falta de especificação.
fonte
Eu bem explico isso com um exemplo.
suponha que temos a seguinte união:
Eu suponho que
sizeof(int)
dê 4 e quesizeof(short)
dê 2.Quando você escrever,
union A a = {10}
crie uma nova var do tipo A e coloque o valor 10.sua memória deve ficar assim: (lembre-se de que todos os membros do sindicato ficam no mesmo local)
como você pode ver, o valor de ax é 10, o valor de ay 1 é 10 e o valor de ay [0] é 0.
agora, o que acontecerá bem se eu fizer isso?
nossa memória ficará assim:
isso transformará o valor de ax para 2424842 (em decimal).
agora, se sua união tiver um valor flutuante ou duplo, seu mapa de memória ficará mais confuso, devido à maneira como você armazena números exatos. mais informações você pode entrar aqui .
fonte