Eu pensava há muito tempo que em C todas as variáveis tinham que ser declaradas no início da função. Eu sei que em C99, as regras são as mesmas que em C ++, mas quais são as regras de posicionamento de declaração variável para C89 / ANSI C?
O código a seguir é compilado com êxito com gcc -std=c89
e gcc -ansi
:
#include <stdio.h>
int main() {
int i;
for (i = 0; i < 10; i++) {
char c = (i % 95) + 32;
printf("%i: %c\n", i, c);
char *s;
s = "some string";
puts(s);
}
return 0;
}
As declarações c
e não devem s
causar um erro no modo C89 / ANSI?
c
declaration
c89
mcjabberz
fonte
fonte
Respostas:
Ele é compilado com êxito porque o GCC permite a declaração
s
como uma extensão GNU, mesmo que não faça parte do padrão C89 ou ANSI. Se você deseja aderir estritamente a esses padrões, deve passar a-pedantic
bandeira.A declaração de
c
no início de um{ }
bloco faz parte do padrão C89; o bloco não precisa ser uma função.fonte
s
é uma extensão (do ponto de vista do C89). A declaração dec
é perfeitamente legal no C89, sem extensões necessárias.Para C89, você deve declarar todas as suas variáveis no início de um bloco de escopo .
Portanto, sua
char c
declaração é válida, pois está no topo do bloco de escopo do loop for. Mas, achar *s
declaração deve ser um erro.fonte
gcc
. Ou seja, não confie que um programa possa ser compilado para significar que é compatível.O agrupamento de declarações de variáveis na parte superior do bloco é provavelmente um legado devido a limitações de compiladores C primitivos antigos. Todos os idiomas modernos recomendam e às vezes até reforçam a declaração de variáveis locais no ponto mais recente: onde são inicializados pela primeira vez. Porque isso elimina o risco de usar um valor aleatório por engano. Separar declaração e inicialização também impede que você use "const" (ou "final") quando puder.
Infelizmente, o C ++ continua aceitando o caminho antigo de declaração superior para compatibilidade com versões anteriores com C (uma compatibilidade C é arrastada para fora de muitas outras ...) Mas o C ++ tenta se afastar dela:
C99 começa a mover C nessa mesma direção.
Se você está preocupado em não encontrar onde as variáveis locais são declaradas, isso significa que você tem um problema muito maior: o bloco anexo é muito longo e deve ser dividido.
https://wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+Minimize+the+scope+of+variables+and+functions
fonte
int y; ... if (x) { printf("X was true"); y=23;} return y;
...null
, all-bits-zero geralmente é um valor de interceptação útil. Além disso, em linguagens que especificam explicitamente que as variáveis assumem o padrão de todos os bits zero, confiar nesse valor não é um erro . Os compiladores ainda não tendem a ficar muito malucos com suas "otimizações", mas os escritores do compilador continuam tentando se tornar cada vez mais inteligentes. Uma opção do compilador para inicializar variáveis com variáveis pseudo-aleatórias deliberadas pode ser útil para identificar falhas, mas apenas deixar o armazenamento mantendo seu último valor às vezes pode mascarar falhas.Do ponto de vista de manutenção, em vez de sintático, existem pelo menos três linhas de pensamento:
Declare todas as variáveis no início da função para que elas estejam em um só lugar e você poderá ver a lista abrangente rapidamente.
Declare todas as variáveis o mais próximo possível do local em que são usadas pela primeira vez, para que você saiba por que cada uma é necessária.
Declare todas as variáveis no início do bloco de escopo mais interno, para que fiquem fora do escopo o mais rápido possível e permitam ao compilador otimizar a memória e informar se você acidentalmente as usará onde não pretendia.
Geralmente, prefiro a primeira opção, pois acho que as outras geralmente me forçam a procurar o código das declarações. Definir todas as variáveis antecipadamente também facilita a inicialização e a visualização de um depurador.
Às vezes, declararei variáveis dentro de um bloco de escopo menor, mas apenas por uma boa razão, da qual tenho muito poucas. Um exemplo pode ser após a
fork()
, para declarar variáveis necessárias apenas pelo processo filho. Para mim, esse indicador visual é um lembrete útil de seu objetivo.fonte
Como observado por outros, o GCC é permissivo nesse sentido (e possivelmente outros compiladores, dependendo dos argumentos com os quais são chamados), mesmo quando no modo 'C89', a menos que você use a verificação 'pedante'. Para ser honesto, não há muitas boas razões para não ter pedante; um código moderno de qualidade sempre deve ser compilado sem avisos (ou muito poucos em que você sabe que está fazendo algo específico que é suspeito do compilador como um possível erro); portanto, se você não pode compilar seu código com uma configuração pedante, provavelmente precisará de alguma atenção.
O C89 exige que as variáveis sejam declaradas antes de qualquer outra instrução dentro de cada escopo; os padrões posteriores permitem uma declaração mais próxima do uso (que pode ser mais intuitiva e mais eficiente), especialmente a declaração e a inicialização simultâneas de uma variável de controle de loop nos loops 'for'.
fonte
Como foi observado, existem duas escolas de pensamento sobre isso.
1) Declare tudo no topo das funções porque o ano é 1987.
2) Declare o mais próximo do primeiro uso e no menor escopo possível.
Minha resposta para isso é FAÇA AMBOS! Deixe-me explicar:
Para funções longas, 1) torna a refatoração muito difícil. Se você trabalha em uma base de código em que os desenvolvedores são contrários à ideia de sub-rotinas, você terá 50 declarações de variáveis no início da função e algumas delas podem ser apenas um "i" para um loop for muito parte inferior da função.
Portanto, desenvolvi a declaração no topo do TEPT e tentei fazer a opção 2) religiosamente.
Voltei à opção um por causa de uma coisa: funções curtas. Se suas funções forem curtas o suficiente, você terá poucas variáveis locais e, como a função é curta, se você as colocar no topo da função, elas ainda estarão próximas ao primeiro uso.
Além disso, o antipadrão de "declarar e definir como NULL" quando você deseja declarar na parte superior, mas ainda não fez alguns cálculos necessários para a inicialização, é resolvido porque as coisas que você precisa inicializar provavelmente serão recebidas como argumentos.
Então, agora, meu pensamento é que você deve declarar no topo das funções e o mais próximo possível do primeiro uso. Então AMBOS! E a maneira de fazer isso é com sub-rotinas bem divididas.
Mas se você estiver trabalhando em uma função longa, coloque as coisas mais próximas do primeiro uso, pois assim será mais fácil extrair métodos.
Minha receita é essa. Para todas as variáveis locais, pegue a variável e mova sua declaração para o final, compile e mova a declaração imediatamente antes do erro de compilação. Esse é o primeiro uso. Faça isso para todas as variáveis locais.
Agora, defina um bloco de escopo que seja iniciado antes da declaração e mova o final até o programa compilar
Isso não é compilado porque há mais código que usa foo. Podemos notar que o compilador foi capaz de passar pelo código que usa bar porque não usa foo. Neste ponto, existem duas opções. O mecânico é apenas mover o "}" para baixo até compilar, e a outra opção é inspecionar o código e determinar se a ordem pode ser alterada para:
Se a ordem puder ser alterada, provavelmente é isso que você deseja, pois reduz a vida útil dos valores temporários.
Outra coisa a ser observada: o valor de foo precisa ser preservado entre os blocos de código que o utilizam, ou poderia ser apenas um foo diferente em ambos. Por exemplo
Essas situações precisam mais do que meu procedimento. O desenvolvedor precisará analisar o código para determinar o que fazer.
Mas o primeiro passo é encontrar o primeiro uso. Você pode fazer isso visualmente, mas às vezes é mais fácil excluir a declaração, tentar compilar e colocá-la novamente acima do primeiro uso. Se esse primeiro uso estiver dentro de uma instrução if, coloque-o lá e verifique se ele compila. O compilador identificará outros usos. Tente criar um bloco de escopo que inclua os dois usos.
Depois que essa parte mecânica é concluída, fica mais fácil analisar onde estão os dados. Se uma variável for usada em um grande bloco de escopo, analise a situação e veja se você está apenas usando a mesma variável para duas coisas diferentes (como um "i" que é usado por duas para loops). Se os usos não estiverem relacionados, crie novas variáveis para cada um desses usos não relacionados.
fonte
Você deve declarar todas as variáveis na parte superior ou "localmente" na função A resposta é:
Depende de que tipo de sistema você está usando:
1 / Sistema Embutido (especialmente relacionado a vidas como Avião ou Carro): Permite o uso de memória dinâmica (por exemplo: calloc, malloc, new ...). Imagine que você está trabalhando em um projeto muito grande, com 1000 engenheiros. E se eles alocarem nova memória dinâmica e se esquecerem de removê-la (quando não usar mais)? Se o sistema incorporado for executado por um longo período, isso causará excesso de pilha e o software será corrompido. Não é fácil garantir a qualidade (a melhor maneira é banir a memória dinâmica).
Se um avião funcionar em 30 dias e não desligar, o que acontece se o software estiver corrompido (quando o avião ainda estiver no ar)?
2 / Os outros sistemas como web, PC (possuem grande espaço de memória):
Você deve declarar a variável "localmente" para otimizar a memória usando. Se esses sistemas funcionarem por um longo período de tempo e ocorrerem estouros de pilha (porque alguém se esqueceu de remover a memória dinâmica). Basta fazer o simples para reiniciar o PC: P Não tem impacto na vida
fonte
malloc()
. Embora eu nunca tenha visto um dispositivo incapaz dele, é uma boa prática evitar a alocação dinâmica em sistemas incorporados ( veja aqui ). Mas isso não tem nada a ver com o local em que você declara suas variáveis em uma função.sub rsp, 1008
está alocando espaço para toda a matriz fora da instrução if. Isto é verdade paraclang
egcc
em cada versão e otimização nível eu tentei.Vou citar algumas instruções do manual da versão 4.7.0 do gcc para uma explicação clara.
"O compilador pode aceitar vários padrões básicos, como 'c90' ou 'c ++ 98', e dialetos GNU desses padrões, como 'gnu90' ou 'gnu ++ 98'. Ao especificar um padrão básico, o compilador aceitará todos os programas que seguem esse padrão e aqueles que usam extensões GNU que não o contradizem. Por exemplo, '-std = c90' desativa certos recursos do GCC que são incompatíveis com a ISO C90, como asm e typeof keywords, mas não outras extensões GNU que não têm um significado na ISO C90, como omitir o termo do meio de uma expressão?: ".
Eu acho que o ponto principal da sua pergunta é por que o gcc não está em conformidade com o C89, mesmo que a opção "-std = c89" seja usada. Não sei a versão do seu gcc, mas acho que não haverá grande diferença. O desenvolvedor do gcc nos disse que a opção "-std = c89" significa apenas que as extensões que contradizem o C89 estão desativadas. Portanto, não tem nada a ver com algumas extensões que não têm significado no C89. E a extensão que não restringe o posicionamento da declaração da variável pertence às extensões que não contradizem o C89.
Para ser honesto, todos pensam que ele deve estar em conformidade com C89 totalmente à primeira vista da opção "-std = c89". Mas isso não acontece. Quanto ao problema que declara que todas as variáveis no início são melhores ou piores, é apenas uma questão de hábito.
fonte