Em C e C ++, quais métodos podem impedir o uso acidental da atribuição (=) onde a equivalência (==) é necessária?

15

Em C e C ++, é muito fácil escrever o código a seguir com um erro grave.

char responseChar = getchar();
int confirmExit = 'y' == tolower(responseChar);
if (confirmExit = 1)
{
    exit(0);
}

O erro é que a instrução if deveria ter sido:

if (confirmExit == 1)

Conforme codificado, ele sai sempre, porque confirmExitocorre a atribuição da variável e confirmExité usado como resultado da expressão.

Existem boas maneiras de evitar esse tipo de erro?

DesenvolvedorDon
fonte
39
Sim. Ative os avisos do compilador. Trate os avisos como erros e isso não é um problema.
Martin York
possível duplicação de "if (0 == value) ..." não faz mais mal do que bem?
Martin York
9
No exemplo dado, a solução é fácil. Você atribuir-lhe um valor booleano, portanto, usá-lo como um booleano: if (confirmExit).
Secure
4
O problema é que o erro foi cometido pelos "designers" da linguagem C, quando optaram por usar = para o operador de atribuição e == para comparação de igualdade. A ALGOL usou: =, porque eles especificamente queriam usar = para comparação de igualdade, e PASCAL e Ada seguiram a decisão da ALGOL. (Vale a pena notar que, quando o Departamento de Defesa solicitou uma entrada baseada em C para o DoD1, que acabou gerando Ada, a Bell Labs recusou, dizendo que "C não era agora e nunca seria robusto o suficiente para missões críticas do Departamento de Defesa. software. "Desejamos que o Departamento de Defesa e os empreiteiros tenham ouvido a Bell Labs sobre isso.)
John R. Strohm
4
@ John, a escolha dos símbolos não é o problema, é o fato de as atribuições também serem uma expressão que retorna o valor atribuído, permitindo a = bou a == bdentro de uma condicional.
Karl Bielefeldt

Respostas:

60

A melhor técnica é aumentar o nível de aviso do seu compilador. Em seguida, ele será avisado sobre uma possível atribuição na condição condicional.

Certifique-se de compilar seu código com zero avisos (o que você deve fazer de qualquer maneira). Se você quiser ser pedante, configure seu compilador para tratar os avisos como erros.

Usar condicionais Yoda (colocar a constante no lado esquerdo) foi outra técnica popular há cerca de uma década. Mas eles tornam o código mais difícil de ler (e, portanto, mantêm por causa da maneira artificial de ler (a menos que você seja Yoda)) e não oferecem maiores benefícios do que aumentar o nível de aviso (que também traz benefícios extras de mais avisos).

Os avisos são realmente erros lógicos no código e devem ser corrigidos.

Martin York
fonte
2
Definitivamente siga os avisos, não as condicionais Yoda - você pode esquecer de fazer a constante condicional primeiro tão facilmente quanto você pode esquecer de fazer ==.
Michael Kohne
8
Tive uma surpresa agradável que a resposta mais votada para essa pergunta é 'transformar avisos do compilador em erros'. Eu estava esperando o if (0 == ret)horror.
James
2
@ James: ... para não mencionar que não vai funcionar para a == b!!
Emilio Garavaglia
6
@EmilioGaravaglia: Existe uma solução fácil para isso: basta escrever 0==a && 0==b || 1==a && 1==b || 2==a && 2==b || ...(repita para todos os valores possíveis). Não se esqueça do ...|| 22==a && 22==b || 23==a && 24==b || 25==a && 25==b ||erro obrigatório ... ou os programadores de manutenção não se divertirão.
user281377
10

Você sempre pode fazer algo radical como testar seu software. Eu nem me refiro a testes de unidade automatizados, apenas os testes que todo desenvolvedor experiente faz por hábito executando seu novo código duas vezes, uma vez confirmando a saída e outra não. Essa é a razão pela qual a maioria dos programadores considera isso um problema.

Karl Bielefeldt
fonte
1
Fácil de fazer quando o comportamento do seu programa é completamente determinístico.
James
3
Pode ser difícil testar esse problema específico. Já vi pessoas picadas rc=MethodThatRarelyFails(); if(rc = SUCCESS){mais de uma vez, especialmente se o método falhar apenas em condições difíceis de testar.
Gort the Robot
3
@StevenBurnap: É para isso que servem os objetos simulados. O teste adequado inclui o teste dos modos de falha.
Jan Hudec
Essa é a resposta correta.
Lightness Races com Monica
6

Uma maneira tradicional de impedir o uso incorreto de atribuições na expressão é colocar a constante à esquerda e a variável à direita.

if (confirmExit = 1)  // Unsafe

if (1=confirmExit)    // Safe and detected at compile time.

O compilador relatará um erro para a atribuição ilegal a uma constante semelhante à seguinte.

.\confirmExit\main.cpp:15: error: C2106: '=' : left operand must be l-value

A condição revisada se:

  if (1==confirmExit)    

Como mostrado pelos comentários abaixo, este é considerado por muitos um método inadequado.

DesenvolvedorDon
fonte
13
Note-se que fazer as coisas dessa maneira o tornará bastante impopular.
riwalk
11
Por favor, não recomende esta prática.
James
5
Também não funciona se você precisar comparar duas variáveis ​​entre si.
precisa saber é
Algumas línguas realmente permitir atribuir 1 para um novo valor (sim, eu sei que o Q é uma luta c / c ++)
Matsemann
4

Concordo com todos que dizem "avisos do compilador", mas quero adicionar outra técnica: Revisões de código. Se você tem uma política de revisar todo o código que é confirmado, de preferência antes de ser confirmado, é provável que esse tipo de coisa seja detectado durante a revisão.

MatrixFrog
fonte
Discordo. Especialmente = em vez de == pode facilmente passar por uma revisão do código.
Simon
2

Primeiro, aumentar seus níveis de alerta nunca é demais.

Se você não deseja que sua condicional teste o resultado de uma atribuição na própria instrução if, depois de ter trabalhado com muitos programadores de C e C ++ ao longo dos anos, e nunca ouviu falar que comparar a constante primeiro if(1 == val)era uma coisa ruim, poderia tentar essa construção.

Se o seu líder de projeto aprovar isso, não se preocupe com o que as outras pessoas pensam. A prova real é se você ou outra pessoa pode entender seu código daqui a meses e anos.

Se você pretendia testar o resultado de uma tarefa, no entanto, o uso de avisos mais altos pode [provavelmente ter] levado a tarefa a uma constante.

octopusgrabbus
fonte
Eu levanto uma sobrancelha cética para qualquer programador que não seja iniciante que vê um Yoda condicional e não o entende imediatamente. É apenas um flip, não é difícil de ler e certamente não é tão ruim quanto alguns comentários estão reivindicando.
underscore_d
@underscore_d Na maioria dos meus empregadores, as tarefas dentro de um condicional eram desaprovadas. O pensamento era que era melhor separar a tarefa da condicional. O motivo de ser mais claro, mesmo à custa de outra linha de código, foi o fator de engenharia de sustentação. Trabalhei em locais com grandes bases de códigos e muita manutenção acumulada. Entendo perfeitamente que alguém pode querer atribuir um valor e ramificar na condição resultante da atribuição. Vejo isso sendo feito com mais frequência no Perl e, se a intenção for clara, seguirei o padrão de design do autor.
Octopusgrabbus 02/01
Eu estava pensando apenas nos condicionais Yoda (como sua demo aqui), não na atribuição (como no OP). Não me importo com o primeiro, mas também não gosto muito dele. A única forma que uso deliberadamente é if ( auto myPtr = dynamic_cast<some_ptr>(testPtr) ) {porque evita manter um nullptrescopo inútil se a conversão falhar - o que é provavelmente o motivo pelo qual o C ++ tem essa capacidade limitada de atribuir dentro de uma condicional. Quanto ao resto, sim, uma definição deve ter sua própria linha, eu diria - muito mais fácil de ver de relance e menos propenso a diversos deslizes da mente.
Underscore_d
@underscore_d Resposta editada com base nos seus comentários. Bom ponto.
Octopusgrabbus 2/16
1

Tarde para a festa como sempre, mas a Análise de código estático é a chave aqui

A maioria dos IDEs agora fornece SCA além da verificação sintática do compilador, e outras ferramentas estão disponíveis, incluindo aquelas que implementam as diretrizes MISRA (*) e / ou CERT-C.

Declaração: Faço parte do grupo de trabalho MISRA C, mas estou postando a título pessoal. Eu também sou independente de qualquer fornecedor de ferramentas

Andrew
fonte
-2

Basta usar a atribuição à esquerda, os avisos do compilador podem ajudar, mas você precisa garantir o nível certo, caso contrário você será inundado com avisos inúteis ou não receberá os avisos que deseja ver.

Lhama invertida
fonte
4
Você ficaria surpreso com o quão poucos avisos inúteis os compiladores modernos geram. Você pode achar que um aviso não é importante, mas na maioria dos casos você está errado.
9788 Kristof Provost
Confira o livro "Escrevendo código sólido" amazon.com/Writing-Solid-Microsoft-Programming-Series/dp/… . Começa com uma grande discussão sobre compiladores e quanto benefício poderíamos obter com mensagens de aviso e análises estáticas ainda mais extensas.
DeveloperDon
@KristofProvost Consegui que o visual studio me desse 10 cópias do mesmo aviso exatamente para a mesma linha de código; quando esse problema foi "corrigido", resultou em 10 avisos idênticos sobre a mesma linha de código, dizendo que o método original foi melhor.
Inverted Llama