Uma pergunta sobre a união em C - loja como um tipo e ler como um outro - é definido pela implementação?

StackOverflow https://stackoverflow.com/questions/1812348

Pergunta

Eu estava lendo sobre união em C de K & R, tanto quanto eu entendi, a única variável em união pode conter qualquer um dos vários tipos e se algo é armazenado como um tipo e extraído como outro o resultado é puramente implementação definida .

Agora, por favor, verifique este trecho de código:

#include<stdio.h>

int main(void)
{
  union a
  {
     int i;
     char ch[2];
  };

  union a u;
  u.ch[0] = 3;
  u.ch[1] = 2;

  printf("%d %d %d\n", u.ch[0], u.ch[1], u.i);

  return 0;
}

Output:

3 2 515

Aqui eu estou atribuindo valores no u.ch mas a recuperação de ambos u.ch e u.i. Trata-se de implementação definido? Ou estou fazendo algo realmente bobo?

Eu sei que pode parecer muito iniciante a maioria de outras pessoas, mas eu sou incapaz de descobrir a razão por trás disso saída.

Graças.

Foi útil?

Solução

Este é um comportamento indefinido. u.i e u.ch estão localizados no mesmo endereço de memória. Assim, o resultado de escrever em um e lê a partir do outro depende do compilador, plataforma, arquitetura, e por vezes mesmo nível de otimização do compilador. Portanto, a saída para u.i pode não ser sempre 515.

Exemplo

Por exemplo gcc na minha máquina produz duas respostas diferentes para -O0 e -O2.

  1. Porque a minha máquina tem uma arquitectura pouco-endian de 32 bits, com -O0 acabo com dois bytes menos significativos inicializadas para 2 e 3, dois bytes mais significativos são inicializado. Então olhares memória do sindicato como este: {3, 2, garbage, garbage}

    Por isso fico com a saída semelhante ao 3 2 -1216937469.

  2. Com -O2, recebo a saída do 3 2 515 como você faz, o que torna {3, 2, 0, 0} memória união. O que acontece é que gcc otimiza a chamada para printf com valores reais, então a aparência de saída de montagem como um equivalente de:

    #include <stdio.h>
    int main() {
        printf("%d %d %d\n", 3, 2, 515);
        return 0;
    }
    

    O valor 515 pode ser obtido como outro explicou em outras respostas para essa pergunta. Em essência, isso significa que quando gcc otimizado a chamada que escolheu zeros como o valor aleatório de um aspirante a união não inicializado.

escrita a um membro de união e leitura de outro normalmente não faz muito sentido, mas, por vezes, pode ser útil para programas compilados com estrita aliasing .

Outras dicas

A resposta a esta pergunta depende do contexto histórico, desde a especificação da linguagem mudou com o tempo. E essa questão passa a ser o único afetado pelas mudanças.

Você disse que você estava lendo K & R. A última edição do livro (a partir de agora), descreve a primeira versão padronizada da linguagem C - C89 / 90. Nessa versão da linguagem C escrevendo um membro da união e ler outro membro é comportamento indefinido . Não implementação definido (que é uma coisa diferente), mas indefinido comportamento. A parte relevante do padrão de linguagem, neste caso, é de 6,5 / 7.

Agora, em algum momento mais tarde na evolução da C (versão C99 da especificação da linguagem com Rectificação Técnico 3 aplicada), de repente, tornou-se legal para usar a união para o tipo de trocadilhos, ou seja, para escrever um membro da união e, em seguida, ler outra.

Note que a tentativa de fazer isso ainda pode levar a um comportamento indefinido. Se o valor que você lê passa a ser inválido (chamada "representação armadilha") para o tipo que você lê-lo completamente, então o comportamento ainda é indefinido. Caso contrário, o valor que você lê é definido pela implementação.

O específica exemplo é relativamente seguro para o tipo de trocadilhos de int a variedade char[2]. É sempre legal em linguagem C a reinterpret o conteúdo de qualquer objecto como uma matriz de char (novamente, 6.5 / 7).

No entanto, o inverso não é verdadeiro. Escrever dados no membro da matriz char[2] de sua união e, em seguida, lê-lo como um int pode potencialmente criar uma representação armadilha e levar a comportamento indefinido . O perigo potencial existe mesmo se a sua matriz de char tem comprimento suficiente para cobrir toda a int.

Mas no seu caso específico, se int passa a ser maior do que char[2], o int você ler vai cobrir a área não inicializado para além do fim da matriz, que por sua vez leva a um comportamento indefinido.

A razão por trás da saída é que em seus inteiros da máquina são armazenados em little-endian formato: os bytes menos significativos são armazenados em primeiro lugar. Por isso, a sequência de bytes [3,2,0,0] representa o número inteiro 3 + 2 * 256 = 515.

Este resultado depende da implementação específica ea plataforma.

A saída de tal código será dependente de sua plataforma e implementação do compilador C. Sua saída faz-me pensar que você está executando este código em um sistema litte-endian (provavelmente x86). Se você fosse para colocar 515 em i e olhar para ele em um depurador, você veria que o byte de ordem mais baixa seria um 3 e o próximo byte na memória seria a 2, que mapeia exatamente ao que você colocou no ch.

Se você fez isso em um sistema big-endian, você teria que (provavelmente) obtido 770 (assumindo ints de 16 bits) ou 50462720 (assumindo ints de 32 bits).

É dependente de implementação e os resultados podem variar em uma plataforma diferente / compilador mas parece que isso é o que está acontecendo:

515 no binário é

1000000011

zeros preenchimento para tornar mais dois bytes (assumindo int 16 bits):

0000001000000011

Os dois bytes são:

00000010 and 00000011

Qual é 2 e 3

Hope alguém explica por que eles são invertidos -. Meu palpite é que caracteres não são revertidas mas o int é pouco endian

A quantidade de memória alocada para uma união é igual à memória necessária para armazenar o maior membro. Neste caso, você tem um int e uma matriz de char de comprimento 2. Assumindo int é de 16 bits e char é de 8 bits, ambos exigem mesmo espaço e, portanto, a união é alocado dois bytes.

Quando você atribuir três (00000011) e dois (00000010) para a matriz char, o estado de união é 0000001100000010. Quando você lê a int desta união, ele converte a coisa toda em e inteiro. Assumindo representação little-endian onde LSB é armazenado no endereço mais baixo, a leitura int a partir da união seria 0000001000000011 que é o binário para 515.

NOTA: Isto é verdadeiro mesmo se o int foi de 32 bit - Verifique A resposta de Amnon

Se você estiver em um sistema de 32 bits, em seguida, um int é de 4 bytes, mas você só inicializar apenas 2 bytes. Acessar dados uninitialised é um comportamento indefinido.

Assumindo que você está em um sistema com ints de 16 bits, então o que você está fazendo ainda é implementação definida. Se o seu sistema é pouco endian, então u.ch [0] irá corresponder com o byte menos significativo de ui e u.ch 1 será o byte mais significativo. Em um sistema endian grande, é o contrário. Além disso, o padrão C não forçar a implementação de usar complemento de dois para representar inteiro assinado valores, apesar de complemento de dois é o mais comum. Obviamente, o tamanho de um inteiro também é definido pela implementação.

Dica: é mais fácil ver o que está acontecendo se você usar valores hexadecimais. Em um pequeno sistema endian, o resultado em hexadecimal seria 0x0203.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top