Na linguagem C, se inicializar uma matriz como esta:
int a[5] = {1,2};
então, todos os elementos da matriz que não foram inicializados explicitamente serão inicializados implicitamente com zeros.
Mas, se eu inicializar uma matriz como esta:
int a[5]={a[2]=1};
printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);
resultado:
1 0 1 0 0
Não entendo, por que a[0]
imprime em 1
vez de 0
? É um comportamento indefinido?
Nota: Esta pergunta foi feita em uma entrevista.
a[2]=1
avalia para1
.a[2] = 1
é1
, mas não tenho certeza se você tem permissão para considerar o resultado de uma expressão inicializadora designada como o valor do primeiro elemento. O fato de você ter adicionado a etiqueta de advogado significa que precisamos de uma resposta citando o padrão.Respostas:
TL; DR: Não acho que o comportamento de
int a[5]={a[2]=1};
está bem definido, pelo menos em C99.A parte engraçada é que a única parte que faz sentido para mim é a parte sobre a qual você está perguntando:
a[0]
está definida como1
porque o operador de atribuição retorna o valor que foi atribuído. É tudo o mais que não está claro.Se o código fosse
int a[5] = { [2] = 1 }
, tudo teria sido fácil: essa é uma configuração de inicializador designadaa[2]
para1
e todo o resto para0
. Mas com{ a[2] = 1 }
temos um inicializador não designado que contém uma expressão de atribuição e caímos em uma toca de coelho.Aqui está o que descobri até agora:
a
deve ser uma variável local.a[2] = 1
não é uma expressão constante, portanto,a
deve haver armazenamento automático.a
está no escopo em sua própria inicialização.O declarador é
a[5]
, portanto, as variáveis estão no escopo em sua própria inicialização.a
está vivo em sua própria inicialização.Existe um ponto de sequência depois
a[2]=1
.Note-se que por exemplo
int foo[] = { 1, 2, 3 }
no{ 1, 2, 3 }
parte é uma lista anexa-cinta de inicializadores, cada um dos quais tem um ponto sequência após ele.A inicialização é realizada na ordem da lista de inicializadores.
No entanto, as expressões do inicializador não são necessariamente avaliadas em ordem.
No entanto, isso ainda deixa algumas perguntas sem resposta:
Os pontos de sequência são relevantes? A regra básica é:
a[2] = 1
é uma expressão, mas a inicialização não é.Isso é ligeiramente contradito pelo Anexo J:
O Anexo J diz que qualquer modificação conta, não apenas modificações por expressões. Mas, dado que os anexos não são normativos, provavelmente podemos ignorar isso.
Como as inicializações de subobjeto são sequenciadas em relação às expressões do inicializador? Todos os inicializadores são avaliados primeiro (em alguma ordem) e, em seguida, os subobjetos são inicializados com os resultados (na ordem da lista de inicializadores)? Ou eles podem ser intercalados?
Acho que
int a[5] = { a[2] = 1 }
é executado da seguinte forma:a
é alocado quando o bloco que o contém é inserido. O conteúdo é indeterminado neste momento.a[2] = 1
), seguido por um ponto de sequência. Isso armazena1
ema[2]
e retornos1
.1
é usado para inicializara[0]
(o primeiro inicializador inicializa o primeiro subobjeto).Mas aqui as coisas ficam confuso porque os elementos restantes (
a[1]
,a[2]
,a[3]
,a[4]
) são supostamente para ser inicializado para0
, mas não está claro quando: isso acontece antesa[2] = 1
é avaliado? Se sim,a[2] = 1
"ganharia" e substituiriaa[2]
, mas essa atribuição teria comportamento indefinido porque não há ponto de sequência entre a inicialização zero e a expressão de atribuição? Os pontos de sequência são relevantes (veja acima)? Ou a inicialização zero ocorre depois que todos os inicializadores são avaliados? Se for assim,a[2]
deve acabar sendo0
.Como o padrão C não define claramente o que acontece aqui, acredito que o comportamento é indefinido (por omissão).
fonte
a[0]
subobjeto antes de avaliar seu inicializador, e avaliar qualquer inicializador inclui um ponto de sequência (porque é uma "expressão completa"). Portanto, acredito que modificar o subobjeto que estamos inicializando é um jogo justo.Provavelmente
a[2]=1
inicializaa[2]
primeiro, e o resultado da expressão é usado para inicializara[0]
.De N2176 (rascunho C17):
Então, parece que a saída
1 0 0 0 0
também teria sido possível.Conclusão: não escreva inicializadores que modifiquem a variável inicializada em tempo real.
fonte
{...}
expressão que inicializaa[2]
com0
, ea[2]=1
a subexpressão que inicializaa[2]
com1
.{...}
é uma lista de inicializadores com chaves. Não é uma expressão.Acho que o padrão C11 cobre esse comportamento e diz que o resultado não é especificado , e não acho que o C18 tenha feito alterações relevantes nesta área.
A linguagem padrão não é fácil de analisar. A seção relevante do padrão é §6.7.9 Inicialização . A sintaxe é documentada como:
Observe que um dos termos é expressão de atribuição e , como
a[2] = 1
é indubitavelmente uma expressão de atribuição, é permitido dentro de inicializadores para matrizes com duração não estática:Um dos parágrafos principais é:
E outro parágrafo importante é:
Tenho quase certeza de que o parágrafo §23 indica que a notação na questão:
leva a um comportamento não especificado. A atribuição a
a[2]
é um efeito colateral, e a ordem de avaliação das expressões são sequenciadas indeterminadamente uma em relação à outra. Consequentemente, não acho que haja uma maneira de apelar para o padrão e alegar que um compilador específico está lidando com isso correta ou incorretamente.fonte
Meu entendimento é
a[2]=1
retorna o valor 1, então o código se tornaint a[5]={1}
atribuir valor para um [0] = 1Portanto, imprime 1 para um [0]
Por exemplo
fonte
Tento dar uma resposta curta e simples para o quebra-cabeça:
int a[5] = { a[2] = 1 };
a[2] = 1
é definido. Isso significa que a matriz diz:0 0 1 0 0
{ }
colchetes, que são usados para inicializar o array em ordem, ele pega o primeiro valor (que é1
) e o define comoa[0]
. É como seint a[5] = { a[2] };
fosse ficar, onde já chegamosa[2] = 1
. A matriz resultante é agora:1 0 1 0 0
Outro exemplo:
int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 };
- Embora a ordem seja um tanto arbitrária, supondo que vá da esquerda para a direita, ela seguiria estas 6 etapas:fonte
A = B = C = 5
não é uma declaração (ou inicialização). É uma expressão normal que analisa comoA = (B = (C = 5))
porque o=
operador é associativo à direita. Isso realmente não ajuda a explicar como funciona a inicialização. A matriz realmente começa a existir quando o bloco em que está definido é inserido, o que pode demorar muito antes de a definição real ser executada.a[2] = 1
expressão do inicializador seja aplicado? O resultado observado é como se fosse, mas o padrão não parece especificar que esse seja o caso. Esse é o centro da controvérsia, e essa resposta o ignora completamente.A atribuição
a[2]= 1
é uma expressão que possui o valor1
e você essencialmente escreveuint a[5]= { 1 };
(com o efeito colaterala[2]
atribuído1
também).fonte
Acredito que
int a[5]={ a[2]=1 };
seja um bom exemplo para um programador atirando em si mesmo no próprio pé.Posso ficar tentado a pensar que o que você quis dizer foi
int a[5]={ [2]=1 };
qual seria um inicializador designado C99 definindo o elemento 2 para 1 e o resto para zero.No caso raro em que você realmente quis dizer
int a[5]={ 1 }; a[2]=1;
, essa seria uma maneira engraçada de escrever. De qualquer forma, seu código se resume a isso, embora alguns aqui tenham apontado que ele não está bem definido quando a gravaçãoa[2]
é realmente executada. A armadilha aqui é quea[2]=1
não é um inicializador designado, mas uma atribuição simples que tem o valor 1.fonte