Acredito que me deparei com um caso ambíguo no padrão C em relação ao escopo de objetos declarados na primeira cláusula de um for
loop em comparação com objetos declarados no corpo do loop.
Dado o seguinte código:
for (int i = 1; i < 5; i++) {
int i = 2;
printf("%d ", i);
}
À primeira vista, parece que a declaração i
dentro do corpo do loop está em um escopo interno em comparação ao i
declarado na primeira cláusula do for
loop.
A seção 6.2.1p4 do padrão C23 afirma o seguinte em relação aos identificadores de escopo de bloco :
Se o declarador ou especificador de tipo que declara o identificador aparecer dentro de um bloco ou dentro da lista de declarações de parâmetros em uma definição de função, o identificador tem escopo de bloco , que termina no final do bloco associado
E 6.2.2p6 sobre ligação :
Os seguintes identificadores não têm vinculação: um identificador declarado como algo diferente de um objeto ou uma função; um identificador declarado como um parâmetro de função; um identificador de escopo de bloco para um objeto declarado sem o especificador de classe de armazenamento
extern
.
Portanto, ambos têm escopo de bloco e nenhuma vinculação.
Entretanto, olhando para 6.2.1p6 que diz o seguinte sobre a comparação de escopos:
Dois identificadores têm o mesmo escopo se e somente se seus escopos terminam no mesmo ponto.
E 6.8.5.3p1 sobre a for
declaração diz o seguinte sobre o escopo dos identificadores que declara:
Se a cláusula 1 for uma declaração, o escopo de quaisquer identificadores que ela declara é o restante da declaração e o loop inteiro, incluindo as outras duas expressões; ele é alcançado na ordem de execução antes da primeira avaliação da expressão de controle
Juntando tudo isso, parece que ambos os objetos declarados como i
têm escopos que terminam no mesmo ponto, ou seja, a chave de fechamento do for
loop (ou seja, o fim de "todo o loop") e, portanto, têm o mesmo escopo e, além disso, ambos não têm vinculação.
Isso seria então uma violação de restrição conforme 6.7p3:
Se um identificador não tiver vinculação, não deverá haver mais de uma declaração do identificador (em um declarador ou especificador de tipo) com o mesmo escopo e no mesmo espaço de nomes.
Mas nenhum compilador que testei exibe qualquer diagnóstico.
Intuitivamente, parece que isso deveria ser permitido.
Isso é um defeito no padrão ou estou esquecendo de algo? Existe uma razão específica para que a correspondência de escopo verifique apenas o fim, mas não o começo? A linguagem é a mesma desde C99, quando declarações em for
loops foram introduzidas.
OBSERVAÇÃO: esta é uma grande reescrita da versão inicial desta resposta.
E é mesmo.
Você citou as seções relevantes da especificação. Não acho que elas deixem espaço para dúvidas de que ambos
i
os s têm escopo de bloco e nenhuma ligação.Mais ou menos. É importante reconhecer que na linguagem da especificação, "block" não significa apenas uma declaração composta entre chaves . Conforme C23 6.8.1p3,
Blocos primários incluem não apenas instruções compostas, mas também instruções de seleção (
if
andswitch
) e instruções de iteração (for
,while
,do
...while
). Blocos secundários incluem todas as instruções rotuladas e não rotuladas — não apenas as acima, mas também instruções de expressão e instruções de salto (break
,continue
,goto
,return
). (6.8.1p1) No geral, "bloco" é uma categoria sintática bastante ampla.6.8.5.3p1 diz que o escopo do outer
i
inclui "o loop inteiro", o que é razoavelmente interpretado como "afor
declaração inteira". Esse entendimento é útil quando consideramos o próximo bit de 6.8.1p3:Isso fala diretamente à questão. No código de exemplo, a
for
declaração geral tem o papel de A , e a declaração composta que serve como corpo do loop tem o papel de B . O escopo do inneri
termina no final de B , o corpo do loop, enquanto o escopo do outeri
continua além disso até o final de A , afor
declaração geral. O ponto complicado aqui é que essa é principalmente uma distinção semântica no caso de exemplo, não sintática.E é a distinção semântica que é mais importante. Em particular, o fato de que o outer
i
retém seu valor através de iterações de loop reflete que ele não sai do escopo para toda a execução do loop, enquanto o fato de que o inneri
não retém seu valor, ou mesmo vive, de uma iteração para a próxima reflete seu escopo mais estreito.Se você quiser sobrepor uma distinção sintática nisso, no entanto, uma maneira de fazer isso seria interpretar o escopo do inner
i
para excluir a chave de fechamento do corpo do loop, e o escopo do outeri
para incluí-la. Isso lhe dá uma diferença no escopo que pode ser expressa em termos de regiões distinguíveis do código-fonte.Considere também este fragmento de código, que tem estrutura semelhante, mas é menos controverso:
Note que não há instruções entre o fim do escopo do inner
i
e o fim do escopo do outeri
, mas eles ainda são escopos diferentes. Isso dá suporte à ideia de que o escopo é sobre a estrutura lógica do programa, não necessariamente mensurável em termos de etapas de execução.Suspeito que você esteja esquecendo a mesma coisa que eu inicialmente, que o termo "bloco" deve ser entendido de forma bem expansiva. Você também pode estar pensando em termos da estrutura lexical do código, enquanto em C, o escopo deve ser entendido em um nível gramatical/semântico.
A justificativa do C99 gasta algum tempo no escopo, mas não aborda essa questão em particular. Embora a linguagem que define "mesmo escopo" fosse nova no C99, o conceito geral foi transportado do C90, onde o problema que você pergunta não surge. O C90 focou onde o escopo termina, não onde ele começa.
A redação do C99 foi transportada substancialmente inalterada para o C23. Sempre pensei que era um pouco carregada, mas estou razoavelmente confiante de que, mesmo antes do C90, a ideia era incluir todos os identificadores, incluindo aqueles (com ligação interna ou externa) que são declarados várias vezes no mesmo escopo. Algumas declarações iniciam o escopo do identificador declarado, enquanto outras não, mas o ponto em que o escopo de um identificador termina é claro. E esse ponto final captura a ideia desejada.