As variáveis ​​`estáticas` do escopo do arquivo em C são tão ruins quanto as variáveis ​​globais externas?

8

Em C, você costumava / às vezes (por uma questão de estilo) usar uma staticvariável de escopo de arquivo em que usaria uma variável de membro de classe privada em C ++. Ao dimensionar para programas multithread, basta adicionar o thread_localC11 ou a extensão suportada por __threadmuito tempo. Eu sei que você pode fazer exatamente o mesmo em C como C ++, colocando tudo dentro de um structe fazendo um conjunto de funções que leva um ponteiro para isso structcomo seu primeiro argumento. Algumas bibliotecas fazem isso extensivamente. Mas meu estilo pessoal é manter o structmínimo possível, se necessário.

Costumo ler ou ouvir algumas pessoas argumentando que variáveis ​​'globais' são muito ruins. Sigo suas razões, e a maioria de seus argumentos parece estar relacionada a externvariáveis ​​globais em termos de C. O que eles dizem é certamente verdade. Às vezes, uso 1 ou 2 das externvariáveis ​​declaradas em todo o programa, para simplificar bastante as coisas e quando é fácil controlá-las, mas ir mais longe tornará o programa imprevisível.

E as staticvariáveis? Eles ainda têm o mesmo problema que as variáveis ​​globais 'reais'? Talvez eu nem precise fazer essa pergunta e continue se achar que estou fazendo o certo, mas hoje vi outro tipo de postagem 'variáveis ​​globais são ruins' e finalmente cheguei aqui pensando que talvez isso seja certo lugar para esse tipo de pergunta. Qual é o seu pensamento?

Esta pergunta não é uma duplicata de presente , porque esta questão pergunta sobre externe staticvariáveis não-locais, enquanto a outra questão é sobre o arquivo-escopo e bloco de escopo de staticvariáveis.

xiver77
fonte
3
Eu não acho que isso seja uma duplicata. Esta pergunta é feita sobre uma variável estática do arquivo em comparação com uma variável estática externa (global). A outra questão é comparar variáveis ​​estáticas da função com variáveis ​​globais.
@gnat Esta não é uma pergunta duplicada. Fiz uma edição para explicar que basicamente diz o que Snowman disse em seu comentário. Até o título é diferente.
precisa saber é o seguinte
@ xiver77 Embora atualmente não haja votos para fechar como duplicado, algumas das respostas da outra pergunta podem ser interessantes aqui.
@Snowman por minha leitura, resposta aceita em que outra questão abrange todos os três casos (parece explicar quando e porquê estática escopo do arquivo devem ser preferidos sobre ambos função escopo e os globais)
mosquito

Respostas:

17

Em um programa C de design bem definido, uma variável estática de arquivo é semelhante a um membro estático privado de uma classe:

  • Ele só pode ser acessado por funções nesse arquivo, semelhante a como uma variável de membro estática privada pode ser acessada apenas por funções na classe em que está definida.

  • Existe apenas uma cópia da variável.

  • Sua vida útil é a vida útil do programa.

Uma externvariável seria uma variável global verdadeira, como em qualquer idioma que as suporte.

Uma staticvariável não global não é tão ruim quanto uma global; de fato, eles são necessários em alguns casos.

  • O acesso é controlado através das funções que você escreve. Isso ajuda na integridade dos dados, incluindo verificação de limites e segurança de threads. (nota: isso não garante a segurança da linha, é apenas uma ferramenta para ajudar ao longo do caminho)

  • Os dados são encapsulados: somente esse arquivo pode acessá-lo. É o mais próximo que C pode chegar do encapsulamento, onde várias funções podem acessar uma variável estática.

Variáveis ​​globais são ruins, não importa o quê. As variáveis ​​de arquivo estático têm os benefícios de uma variável estática privada, mas nenhuma das desvantagens de uma variável global.

O único problema é diferente de uma variável estática privada verdadeira, como no C ++, outros arquivos podem declarar uma externvariável correspondente à declaração e você não pode impedir o acesso. Em outras palavras, você conta com o sistema de honra para evitar transformá-lo em uma variável global.


fonte
6

O estado global, incluindo externvariáveis ​​e não const staticvariáveis ​​no escopo do arquivo ou nas funções, freqüentemente pode ser uma solução fácil para um determinado problema, mas há três problemas:

  1. statictorna o código não testável , porque as staticvariáveis ​​tendem a ser dependências não substituíveis. Ou, em mais palavras OOP-y: você não está seguindo o Princípio da inversão de dependência. Eu vim para C e C ++ a partir de linguagens dinâmicas, como Perl, portanto, meu modelo de custo é inclinado para indicadores de despacho e função virtuais e assim por diante. Com os idiomas atuais, existe algum conflito entre testabilidade e boa arquitetura, mas acho que o pequeno incômodo de tornar suas dependências explícitas e permitir que elas sejam substituídas nos testes é visivelmente compensado pela facilidade de escrever testes e, assim, garantir que seu software esteja funcionando como esperado. Sem tornar seu código mais dinâmico, o único mecanismo disponível para injetar dependências para um teste é a compilação condicional.

  2. O estado global torna difícil raciocinar sobre correção , e isso leva a erros. Quanto mais bits e partes têm acesso a uma variável e podem modificá-la, mais fácil é perder o controle do que está acontecendo. Em vez disso: prefira a atribuição única de variáveis! Prefira constsempre que razoável! Prefira guardar variáveis ​​por meio de getters e setters, onde você pode introduzir verificações de correção. Enquanto o estado é statice não extern, ainda é possívelpara manter a correção, mas é sempre melhor presumir que eu em uma semana não será tão inteligente quanto eu agora. Especialmente em C ++, podemos usar classes para modelar várias abstrações que tornam impossível o uso indevido de algo; portanto, tente utilizar o sistema de tipos em vez de sua inteligência - você tem coisas mais importantes para pensar.

  3. O estado global pode implicar que suas funções não sejam reentrantes ou que possam ser usadas apenas em um contexto por vez. Imagine um driver de banco de dados que possa gerenciar apenas uma conexão! Essa é uma restrição totalmente desnecessária. Na realidade, as limitações geralmente são mais sutis, como uma variável global usada para agregar resultados. Em vez disso, torne seu fluxo de dados explícito e passe tudo pelos parâmetros de função. Novamente, as classes C ++ podem tornar isso mais gerenciável.

Obviamente, static const NAMED_CONSTANTSestão bem. O uso staticde funções internas é muito mais complicado: embora seja útil para constantes inicializadas lentamente, pode ser bastante testável. Um compromisso é separar o cálculo do valor inicial da variável estática, para que ambas as partes possam ser testadas separadamente.

Em programas pequenos e independentes, tudo isso não importa, e você pode continuar usando o staticestado para seu deleite do coração. Porém, ao passar em torno de 500 LOC ou se estiver escrevendo uma biblioteca reutilizável, você deve realmente começar a pensar em boa arquitetura e uma boa interface sem restrições desnecessárias.

amon
fonte
Algumas de suas sugestões sobre design e segurança, por vezes, é impossível para cuidar de em programas que precisa ser escrito em C.
xiver77
E o estado global não é necessariamente um tópico hostil. O mais recente padrão C e C ++ possui thread_locale compiladores há muito tempo o suportam como uma extensão antes da padronização.
precisa saber é o seguinte
Para evitar muitos dos problemas do estado global que você mencionou, a maioria dos programas C bem escritos possui uma estrutura rasa e transparente com dependências externas minimizadas.
precisa saber é o seguinte
Manter o código estático e os dados mutáveis, embora propenso a erros, é a única maneira de escrever o programa mais eficiente possível na arquitetura atual do computador.
X266
@ xiver77 Eu tenho o luxo de escrever mais códigos de alto nível, de modo que nem sacrificar a arquitetura pelo desempenho nem usar C é necessário em minha linha de trabalho :) Ao escrever C, acho muito mais desafiador escrever código correto e a escolha de abstrações adequadas é diferente do que eu preferiria, mas ainda é possível fornecer muita segurança. Minha resposta tenta marcar claramente as idéias específicas do C ++ como tal. Seu ponto de vista sobre arquivos pequenos é muito bom, o que facilita o raciocínio sobre o programa e reduz, mas não elimina, o problema apresentado pelas staticvariáveis.
amon
1

Não considero variáveis ​​com escopo de arquivo tão ruins quanto variáveis ​​globais. Afinal, todos os acessos a essas variáveis ​​são limitados a um único arquivo de origem. Com essa restrição, as variáveis ​​de escopo do arquivo são tão boas ou ruins quanto um membro de dados estáticos privados em C ++, e você não proíbe o uso deles, não é?

cmaster - restabelece monica
fonte
1
No c ++, o tipo de sintaxe incentiva você a colocar muitas variáveis ​​de membro nas classes, portanto, não é necessário ter um membro estático privado. Mas em C, se você não emular completamente o que faria com uma classe C ++, variáveis ​​estáticas ou thread_local podem ser usadas em vez de um membro struct, se você quiser evitar passar o ponteiro struct em todos os lugares. Minha pergunta é sobre isso.
precisa saber é o seguinte
Estou falando de membros com staticarmazenamento, que existem exatamente uma vez, não para cada objeto. Essas variáveis não precisam de nenhuma instância da classe para serem referenciadas pelo código da classe, portanto, não há necessidade de passar uma referência / ponteiro para um singleton. É exatamente isso que as variáveis ​​de escopo do arquivo também alcançam. A única diferença é que o staticmembro tem escopo de classe enquanto a staticvariável "global" possui escopo de arquivo. Mas esses dois escopos são muito parecidos em extensão, o que é o meu ponto. Eu concordo que os diferentes significados de staticsão confusos, no entanto.
cmaster - restabelece monica 27/08/2015
1
Em C, pelo menos, estática realmente tem um significado para variáveis ​​(não para funções).
X266
1

Está tudo ligado ao escopo da variável (não constante, algo mutável) na minha opinião. É uma opinião que carece de alguma nuance, mas é um contador e apelo pragmático para retornar aos fundamentos mais básicos àqueles que dizem: "Isso é absolutamente mau!" apenas para tropeçar em questões semelhantes às associadas ao que criticam, como condições de corrida.

Imagine que você tem uma função de 50.000 linhas com todos os tipos de variáveis ​​declaradas no topo e gotoinstruções para saltar por todo o lugar. Isso não é muito agradável com um escopo de variável tão monstruoso, e tentar raciocinar sobre a função, e o que está acontecendo com essas variáveis, será extremamente difícil. Nesse caso monstruoso, a distinção normal entre efeito colateral "externo" e "interno" perde muito de seu objetivo prático.

Imagine que você tenha um programa simples de 80 linhas, basta escrever uma vez e iniciar uma variável global (com link interno e escopo de arquivo ou link externo, mas de qualquer forma o programa é pequeno). Isso não é tão ruim.

Imagine que você tem uma classe monstruosa em uma linguagem orientada a objetos que contém a lógica de todo o programa com milhares e milhares de linhas de código para sua implementação. Nesse caso, suas variáveis-membro são mais problemáticas que as globais no programa de 80 linhas acima.

Se você deseja raciocinar melhor e com mais confiança sobre seu código, a segurança do thread (ou a falta dele), torná-lo mais previsível, verifique se seus testes têm uma boa cobertura sem que todos os tipos de possíveis casos extremos sejam perdidos, etc. , ajuda a restringir o acesso a variáveis.

A estática do escopo do arquivo tenderá a ter um escopo mais restrito do que aqueles com vínculo externo, mas se o seu arquivo de origem tiver 100.000 linhas de código, isso ainda é muito amplo. Portanto, para a estática do escopo do arquivo, se você não puder evitá-las, eu tentaria manter o escopo restrito, não tornando o arquivo de origem que pode acessá-las gigantesco, pois nesse caso, reduzir o escopo é reduzir o tamanho e o escopo do design do arquivo de origem, em oposição à função (para variáveis ​​locais, incluindo parâmetros), classe (para variáveis-membro) ou possivelmente módulo (para globais com ligação externa, mas acessível apenas dentro do módulo) ou até mesmo software inteiro (para globais com ligação externa acessível a todo o software).

Dragon Energy
fonte