Em C / C ++, as variáveis globais são tão ruins quanto meu professor pensa que são?
Em C / C ++, as variáveis globais são tão ruins quanto meu professor pensa que são?
Respostas:
O problema com variáveis globais é que, como todas as funções têm acesso a elas, fica cada vez mais difícil descobrir quais funções realmente lêem e escrevem essas variáveis.
Para entender como o aplicativo funciona, é preciso levar em consideração todas as funções que modificam o estado global. Isso pode ser feito, mas à medida que o aplicativo cresce, fica mais difícil a ponto de ser praticamente impossível (ou pelo menos uma completa perda de tempo).
Se você não confiar em variáveis globais, poderá passar o estado entre diferentes funções, conforme necessário. Dessa forma, você tem uma chance muito maior de entender o que cada função faz, pois não precisa levar em consideração o estado global.
O importante é lembrar o objetivo geral: clareza
A regra "sem variáveis globais" existe porque, na maioria das vezes, as variáveis globais tornam o significado do código menos claro.
No entanto, como muitas regras, as pessoas lembram-se da regra, e não o que a regra deveria fazer.
Eu já vi programas que parecem dobrar o tamanho do código, passando um enorme número de parâmetros simplesmente para evitar o mal das variáveis globais. No final, o uso de globais tornaria o programa mais claro para quem o lê. Ao aderir irremediavelmente à palavra da regra, o programador original falhou na intenção da regra.
Então, sim, os globais são geralmente ruins. Mas se você achar que, no final, a intenção do programador é esclarecida pelo uso de variáveis globais, vá em frente. No entanto, lembre-se da queda na clareza que ocorre automaticamente quando você força alguém a acessar um segundo trecho de código (os globais) para entender como o primeiro trecho funciona.
Meu professor costumava dizer algo como: usar variáveis globais é bom se você usá-las corretamente. Acho que nunca fui bom em usá-los corretamente, então raramente os usava.
static
muito variáveis globais, a linguagem é C. Estando confinados a unidades de conversão relativamente pequenas, eles começam a se parecer com variáveis de classe de objetos C ++.
program lifetime, file scope variables
,. E eles tornaram-se bastante mundial uma vez que você passar um ponteiro para a variável para o mundo exterior (o que é impossível com variáveis automáticas) ..
static
as variáveis globais têm escopo limitado à mesma unidade de tradução. Mas eles têm vida útil até o final do programa como qualquer variável global.
Variáveis globais só devem ser usadas quando você não tem alternativa. E sim, isso inclui Singletons. 90% do tempo, variáveis globais são introduzidas para economizar o custo de passar um parâmetro. E então acontece a codificação multithreading / teste de unidade / manutenção, e você tem um problema.
Então, sim, em 90% das situações as variáveis globais são ruins. As exceções provavelmente não serão vistas por você em seus anos de faculdade. Uma exceção que posso pensar em detalhes é lidar com objetos inerentemente globais, como tabelas de interrupção. Coisas como a conexão com o banco de dados parecem ser globais, mas não são.
O problema que as variáveis globais criam para o programador é que ele expande a superfície de acoplamento entre componentes entre os vários componentes que estão usando as variáveis globais. O que isso significa é que, à medida que o número de componentes usando uma variável global aumenta, a complexidade das interações também pode aumentar. Esse aumento no acoplamento geralmente facilita a injeção de defeitos no sistema ao fazer alterações e também dificulta o diagnóstico e a correção dos defeitos. Esse aumento de acoplamento também pode reduzir o número de opções disponíveis ao fazer alterações e pode aumentar o esforço necessário para as alterações, pois é necessário rastrear os vários módulos que também estão usando a variável global para determinar as conseqüências das alterações.
O objetivo do encapsulamento , que é basicamente o oposto do uso de variáveis globais, é diminuir o acoplamento para facilitar o entendimento e a alteração da fonte, a segurança e o teste mais fácil. É muito mais fácil usar o teste de unidade quando as variáveis globais não são usadas.
Por exemplo, se você tem uma variável inteira global simples que está sendo usada como um indicador enumerado que vários componentes usam como uma máquina de estado e você faz uma alteração adicionando um novo estado a um novo componente, você deve rastrear todas as outras componentes para garantir que a alteração não os afete. Um exemplo de um possível problema seria se uma switch
instrução para testar o valor da variável global de enumeração com case
instruções para cada um dos valores atuais estivesse sendo usada em vários locais e acontece que algumas das switch
instruções não têm um default
caso para tratar um valor inesperado para o global, de repente, você tem um comportamento indefinido no que diz respeito ao aplicativo.
Por outro lado, o uso de uma área de dados compartilhados pode ser usado para conter um conjunto de parâmetros globais que são referenciados em todo o aplicativo. Essa abordagem geralmente é usada em aplicativos incorporados com pequenas dimensões de memória.
Ao usar variáveis globais nesse tipo de aplicativo, normalmente a responsabilidade de gravar na área de dados é alocada para um único componente e todos os outros componentes veem a área como const
e lida nela, nunca gravando nela. A adoção dessa abordagem limita os problemas que podem se desenvolver.
Alguns problemas de variáveis globais que precisam ser contornados
Quando a origem de uma variável global, como uma struct, é modificada, tudo o que é usado deve ser recompilado para que tudo o que for usado na variável conheça seu verdadeiro tamanho e modelo de memória.
Se mais de um componente puder modificar a variável global, você poderá encontrar problemas com dados inconsistentes na variável global. Com um aplicativo multiencadeamento, você provavelmente precisará adicionar algum tipo de região crítica ou de bloqueio para fornecer uma maneira de que apenas um encadeamento de cada vez possa modificar a variável global e quando um encadeamento estiver modificando a variável, todas as alterações serão concluídas. e confirmado antes que outros threads possam consultar a variável ou modificá-la.
Depurar um aplicativo multithread que usa uma variável global pode ser mais difícil. Você pode encontrar condições de corrida que podem criar defeitos difíceis de replicar. Com vários componentes se comunicando por meio de uma variável global, especialmente em um aplicativo multithread, é muito difícil entender qual componente está alterando a variável quando e como ela está alterando a variável.
Conflito de nomes pode ser um problema com o uso de variáveis globais. Uma variável local que tem o mesmo nome que uma variável global pode ocultar a variável global. Você também enfrenta o problema da convenção de nomenclatura ao usar a linguagem de programação C. Uma solução alternativa é dividir o sistema em subsistemas com as variáveis globais de um subsistema específico, todas começando com as mesmas três primeiras letras (consulte isso sobre como resolver colisões de espaços de nomes no objetivo C ). O C ++ fornece espaços de nomes e, com o C, você pode contornar isso criando uma estrutura visível globalmente, cujos membros são vários itens de dados e ponteiros para dados e funções que são fornecidas em um arquivo como estático, portanto, apenas com visibilidade do arquivo, para que possam ser referenciadas apenas através a estrutura globalmente visível.
Em alguns casos, a intenção original do aplicativo é alterada para que as variáveis globais que forneceram o estado de um único encadeamento sejam modificadas para permitir a execução de vários encadeamentos duplicados. Um exemplo seria um aplicativo simples projetado para um único usuário usando variáveis globais para estado e, em seguida, um pedido é enviado pelo gerenciamento para adicionar uma interface REST para permitir que aplicativos remotos atuem como usuários virtuais. Portanto, agora é necessário duplicar as variáveis globais e suas informações de estado para que o usuário único e cada um dos usuários virtuais de aplicativos remotos tenham seu próprio conjunto exclusivo de variáveis globais.
Usando C ++ namespace
e a struct
técnica para C
Para a linguagem de programação C ++, a namespace
diretiva é uma grande ajuda para reduzir as chances de um conflito de nomes. namespace
juntamente com class
e as várias palavras-chave de acesso ( private
, protected
, e public
) fornecem a maior parte das ferramentas necessárias para variáveis Encapsular. No entanto, a linguagem de programação C não fornece essa diretiva. Esta postagem de stackoverflow, Namespaces em C , fornece algumas técnicas para C.
Uma técnica útil é ter uma única área de dados residentes na memória, definida como uma struct
que tenha visibilidade global e, dentro disso, struct
sejam ponteiros para as várias variáveis e funções globais que estão sendo expostas. As definições reais das variáveis globais recebem escopo de arquivo usando a static
palavra - chave Se você usar a const
palavra-chave para indicar quais são somente leitura, o compilador poderá ajudá-lo a impor o acesso somente leitura.
O uso da struct
técnica também pode encapsular o global, para que ele se torne um tipo de pacote ou componente que passa a ser global. Por ter um componente desse tipo, fica mais fácil gerenciar alterações que afetam o global e a funcionalidade usando o global.
No entanto, enquanto namespace
ou a struct
técnica puder ajudar a gerenciar conflitos de nomes, ainda existem os problemas subjacentes ao acoplamento entre componentes que o uso de globais introduz especialmente em um aplicativo multithread moderno.
Variáveis globais são tão ruins quanto você as cria, nada menos.
Se você estiver criando um programa totalmente encapsulado, poderá usar globais. É um "pecado" usar globais, mas os pecados de programação são amplamente filosóficos.
Se você verificar L.in.oleum , verá um idioma cujas variáveis são exclusivamente globais. Não é escalável, porque todas as bibliotecas não têm escolha a não ser usar globais.
Dito isto, se você tem opções e pode ignorar a filosofia do programador, os globais não são tão ruins assim.
Nem Gotos, se você usá-los corretamente.
O grande problema "ruim" é que, se você usá-los erradamente, as pessoas gritam, o Mars Lander cai e o mundo explode .... ou algo assim.
Sim, mas você não incorre no custo de variáveis globais até parar de trabalhar no código que usa variáveis globais e começar a escrever outra coisa que use o código que usa variáveis globais. Mas o custo ainda está lá.
Em outras palavras, é um custo indireto de longo prazo e, como tal, a maioria das pessoas pensa que não é ruim.
Se for possível que seu código acabe sendo analisado intensivamente durante um julgamento na Suprema Corte , você deve evitar variáveis globais.
Veja este artigo: O código do bafômetro reflete a importância da revisão da fonte
Houve alguns problemas com o estilo do código que foram identificados pelos dois estudos. Uma das questões estilísticas que preocuparam os revisores foi o uso extensivo de variáveis globais desprotegidas . Isso é considerado ruim porque aumenta o risco de o estado do programa se tornar inconsistente ou de que valores sejam modificados ou substituídos inadvertidamente. Os pesquisadores também expressaram alguma preocupação com o fato de que a precisão decimal não é mantida de forma consistente em todo o código.
Cara, aposto que esses desenvolvedores desejam que não usem variáveis globais!
Eu responderia a essa pergunta com outra pergunta: Você usa singeltons / singeltons são ruins?
Porque (quase todo) o uso de singelton é uma variável global glorificada.
O problema é menos ruim e mais perigoso . Eles têm seu próprio conjunto de prós e contras, e há situações em que são a maneira mais eficiente ou a única maneira de realizar uma tarefa específica. No entanto, eles são muito fáceis de usar incorretamente, mesmo que você tome medidas para sempre usá-las corretamente.
Alguns profissionais:
Alguns contras:
Observe, se você preferir, que os dois primeiros profissionais e os dois primeiros contras listados são exatamente a mesma coisa, apenas com palavras diferentes. Isso ocorre porque os recursos de uma variável global podem realmente ser úteis, mas os próprios recursos que os tornam úteis são a fonte de todos os seus problemas.
Algumas soluções possíveis para alguns dos problemas:
Globals
ou GlobalVars
) ou use uma convenção de nomenclatura padronizada para variáveis globais (como global_[name]
ou g_module_varNameStyle
(como mencionado por underscore_d nos comentários) )). Isso documentará seu uso (você pode encontrar o código que usa variáveis globais pesquisando o espaço para nome / nome da estrutura) e minimizar o impacto no espaço para nome global.extern
no cabeçalho associado, para que seu uso possa ser limitado às unidades de compilação que precisam acessá-los. Se o seu código depende de muitas variáveis globais, mas cada unidade de compilação precisa apenas acessar algumas delas, você pode classificá-las em vários arquivos de origem, para que seja mais fácil limitar o acesso de cada arquivo às variáveis globais.Se eles são bons ou ruins, depende de como você os usa. A maioria tende a usá-los mal, daí a cautela geral em relação a eles. Se usados corretamente, eles podem ser um grande benefício; se usado mal, no entanto, eles podem e vai voltar a morder-lhe quando e como você menos espera.
Uma boa maneira de ver isso é que eles próprios não são ruins, mas habilitam o design incorreto e podem multiplicar os efeitos do design incorreto exponencialmente.
Mesmo que você não pretenda usá-los, é melhor saber usá-los com segurança e optar por não usá-los, do que não usá-los, porque você não sabe usá-los com segurança. Se você se encontrar em uma situação em que precisa manter um código preexistente que depende de variáveis globais, poderá ter dificuldades se não souber usá-las adequadamente.
g_module_varNameStyle
perfeitamente legível. Para ser claro, não estou usando globais se puder evitá-la facilmente - palavra-chave facilmente , porque desde que parei de acreditar que elas devem ser evitadas - ou melhor, ofuscadas - a todo custo, estou tendo um tempo muito melhor, e meu código é (choque!) muito mais organizado
Como alguém disse (estou parafraseando) em outro tópico "Regras como essa não devem ser violadas, até que você entenda completamente as conseqüências disso".
Há momentos em que variáveis globais são necessárias ou pelo menos muito úteis (por exemplo, trabalhando com retornos de chamada definidos pelo sistema). Por outro lado, eles também são muito perigosos por todos os motivos pelos quais você foi informado.
Existem muitos aspectos da programação que provavelmente devem ser deixados para os especialistas. Às vezes você precisa de uma faca muito afiada. Mas você não pode usar um até estar pronto ...
As variáveis globais geralmente são ruins, especialmente se outras pessoas estiverem trabalhando no mesmo código e não quiserem gastar 20 minutos pesquisando todos os locais em que a variável é referenciada. E adicionar threads que modificam as variáveis traz um novo nível de dores de cabeça.
As constantes globais em um espaço de nome anônimo usado em uma única unidade de tradução são excelentes e onipresentes em aplicativos e bibliotecas profissionais. Mas se os dados forem mutáveis e / ou tiverem que ser compartilhados entre várias TUs, convém encapsulá-los - se não for por motivos de design, por qualquer pessoa que depure ou trabalhe com seu código.
Usar variáveis globais é como varrer a sujeira sob um tapete. É uma solução rápida e muito mais fácil a curto prazo do que usar um recipiente de pó ou aspirador para limpá-lo. No entanto, se você acabar movendo o tapete mais tarde, terá uma grande bagunça surpresa por baixo.
Acho que seu professor está tentando parar um mau hábito antes mesmo de começar.
As variáveis globais têm seu lugar e, como muitas pessoas disseram, saber onde e quando usá-las pode ser complicado. Então, acho que, em vez de entrar no âmago da questão do porquê, como, quando e onde das variáveis globais seu professor decidiu banir. Quem sabe, ele pode proibi-los no futuro.
Absolutamente não. Mas usá-los mal ... isso é ruim.
Removê-los sem pensar é apenas isso ... sem sentido. A menos que você conheça as vantagens e desvantagens, é melhor ficar claro e fazer o que foi ensinado / aprendido, mas não há nada implicitamente errado com as variáveis globais. Quando você entender os prós e contras, tome melhor sua própria decisão.
Variáveis globais são boas em programas pequenos, mas horríveis se usadas da mesma maneira em programas grandes.
Isso significa que você pode facilmente adquirir o hábito de usá-los enquanto aprende. É disso que seu professor está tentando protegê-lo.
Quando você é mais experiente, será mais fácil aprender quando eles estiverem bem.
Não, eles não são ruins. Você precisa observar o código (de máquina) produzido pelo compilador para fazer essa determinação; às vezes é muito pior usar um local do que um global. Observe também que colocar "estático" em uma variável local está basicamente tornando-o global (e cria outros problemas feios que um global real resolveria). "globais locais" são particularmente ruins.
Os Globals também oferecem controle limpo sobre o uso da memória, algo muito mais difícil de fazer com os habitantes locais. Hoje em dia isso só importa em ambientes incorporados, onde a memória é bastante limitada. Algo a saber antes de você assumir que incorporado é o mesmo que outros ambientes e assumir que as regras de programação são as mesmas em todos os aspectos.
É bom que você questione as regras que estão sendo ensinadas, a maioria delas não é pelas razões pelas quais você está sendo informado. A lição mais importante, porém, não é que essa seja uma regra a ser levada para sempre, mas essa é uma regra que deve ser respeitada para passar nesta classe e seguir em frente. Na vida, você descobrirá que, para a empresa XYZ, você terá outras regras de programação que, no final, terá que respeitar para continuar recebendo um salário. Nas duas situações, você pode argumentar sobre a regra, mas acho que você terá muito mais sorte no trabalho do que na escola. Você é apenas mais um dos muitos estudantes, seu lugar será substituído em breve, os professores não. Em um trabalho, você faz parte de uma pequena equipe de jogadores que precisam ver esse produto até o fim e nesse ambiente as regras desenvolvidas são para benefício dos membros da equipe, bem como do produto e da empresa, portanto, se todos tiverem a mesma opinião ou se, para o produto em particular, houver boas razões de engenharia para violar algo que você aprendeu na faculdade ou algum livro sobre programação genérica, venda sua ideia para a equipe e escreva-a como um método válido, se não o preferido. . Tudo é um jogo justo no mundo real.
Se você seguir todas as regras de programação ensinadas na escola ou nos livros, sua carreira em programação será extremamente limitada. É provável que você possa sobreviver e ter uma carreira frutífera, mas a amplitude e a largura dos ambientes disponíveis serão extremamente limitadas. Se você sabe como e por que a regra existe e pode defendê-la, isso é bom, se você apenas raciocinar é "porque meu professor disse isso", isso não é tão bom.
Observe que tópicos como esse são frequentemente discutidos no local de trabalho e continuarão sendo, à medida que os compiladores e processadores (e idiomas) evoluem, o mesmo ocorre com esses tipos de regras e sem defender sua posição e possivelmente receber uma lição de alguém com outra opinião que você não terá. Siga em frente.
Nesse meio tempo, basta fazer o que diz o que fala mais alto ou carrega o maior bastão (até que você seja o que mais grita e o maior).
Eu gostaria de argumentar contra o argumento de que este multi-threading torna mais difícil ou impossível por si só. Variáveis globais são estado compartilhado, mas as alternativas aos globais (por exemplo, passando indicadores) também podem compartilhar estado. O problema com o multiencadeamento é como usar corretamente o estado compartilhado, não se esse estado é compartilhado por meio de uma variável global ou outra coisa.
Na maioria das vezes, quando você faz multi-threading, precisa compartilhar algo. Em um padrão produtor-consumidor, por exemplo, você pode compartilhar alguma fila de thread-safe que contenha as unidades de trabalho. E você pode compartilhá-lo porque essa estrutura de dados é segura para threads. Se essa fila é global ou não, é completamente irrelevante quando se trata de segurança de threads.
A esperança implícita expressa ao longo deste thread de que transformar um programa de single-thread para multi-thread será mais fácil quando não usar globals for ingênuo. Sim, os globais facilitam o tiro no pé, mas há muitas maneiras de se dar um tiro.
Não estou defendendo os globais, como os outros pontos ainda permanecem, meu argumento é apenas que o número de threads em um programa não tem nada a ver com escopo variável.
Sim, porque se você permitir que programadores incompetentes os usem (leia 90%, especialmente cientistas), você terá mais de 600 variáveis globais espalhadas por mais de 20 arquivos e um projeto de 12.000 linhas em que 80% das funções serão anuladas, retornadas e operadas. inteiramente no estado global.
Torna-se rapidamente impossível entender o que está acontecendo a qualquer momento, a menos que você conheça todo o projeto.
O uso de variáveis globais realmente depende dos requisitos. Sua vantagem é que, reduz a sobrecarga de passar os valores repetidamente.
Mas seu professor está certo porque levanta questões de segurança, portanto o uso de variáveis globais deve ser evitado o máximo possível. Variáveis globais também criam problemas que às vezes são difíceis de depurar .
Por exemplo:-
Situações em que os valores das variáveis estão sendo modificados no tempo de execução . Nesse momento, é difícil identificar qual parte do código está modificando-o e em que condições.
Globais são bons quando se trata de configuração . Quando queremos que nossa configuração / alterações tenham um impacto global em todo o projeto .
Assim, podemos alterar uma configuração e as alterações são direcionadas para o projeto inteiro . Mas devo advertir que você teria que ser muito inteligente para usar os globais.
Mais cedo ou mais tarde, você precisará alterar como essa variável é configurada ou o que acontece quando é acessada, ou você apenas precisa procurar onde ela é alterada.
É praticamente sempre melhor não ter variáveis globais. Escreva os métodos de obtenção e definição da barragem e fique à vontade quando precisar deles um dia, semana ou mês depois.
Eu geralmente uso globais para valores que raramente são alterados, como singletons ou ponteiros de função para funções na biblioteca carregada dinamicamente. O uso de globais mutáveis em aplicativos multithread tende a levar a erros difíceis de rastrear, por isso tento evitar isso como regra geral.
Usar um global em vez de passar um argumento geralmente é mais rápido, mas se você estiver escrevendo um aplicativo multithread, o que costuma fazer hoje em dia, geralmente não funciona muito bem (você pode usar a estatística de threads, mas o ganho de desempenho é questionável) .
Em aplicativos da web dentro de uma empresa, pode ser usado para manter dados específicos da sessão / janela / encadeamento / usuário no servidor por razões de otimização e preservar contra a perda de trabalho em que a conexão é instável. Como mencionado, as condições da corrida precisam ser tratadas. Usamos uma única instância de uma classe para essas informações e elas são cuidadosamente gerenciadas.
No final do dia, seu programa ou aplicativo ainda pode funcionar, mas é uma questão de organização e compreensão completa do que está acontecendo. Se você compartilhar um valor variável entre todas as funções, pode ser difícil rastrear qual função está alterando o valor (se a função o fizer) e dificultar a depuração um milhão de vezes
segurança é menor significa que qualquer um pode manipular as variáveis se elas forem declaradas globais. Para isso, veja este exemplo, se você tiver saldo como uma variável global em seu programa bancário, a função de usuário pode manipular isso e o funcionário do banco também pode manipular isso para que haja um problema. apenas o usuário deve receber a função somente leitura e retirar, mas o funcionário do banco pode adicionar o valor quando o usuário pessoalmente der o dinheiro na mesa. é assim que funciona
Em um aplicativo multithread, use variáveis locais no lugar de variáveis globais para evitar uma condição de corrida.
Uma condição de corrida ocorre quando vários threads acessam um recurso compartilhado, com pelo menos um segmento tendo acesso de gravação aos dados. Então, o resultado do programa não é previsível e depende da ordem de acessos aos dados por diferentes threads.
Mais sobre isso aqui, https://software.intel.com/en-us/articles/use-intel-parallel-inspector-to-find-race-conditions-in-openmp-based-multithreaded-code