c++ - setup - seu projeto não contém informações de depuração(-g)




Por que eu sempre devo habilitar avisos do compilador? (10)

Por que ativar avisos?

Os compiladores C e C ++ são notoriamente ruins em relatar alguns erros comuns do programador por padrão , como:

  • esquecendo de inicializar uma variável
  • esquecendo de return um valor de uma função
  • argumentos nas famílias printf e scanf que não correspondem à string de formato
  • uma função é usada sem ser declarada previamente (somente C)

Eles podem ser detectados e relatados, mas geralmente não são por padrão; esse recurso deve ser solicitado explicitamente por meio de opções do compilador.

Como habilitar avisos?

Isso depende do seu compilador.

Os compiladores Microsoft C e C ++ entendem opções como /W1 , /W2 , /W3 , /W4 e /Wall . Use pelo menos /W3 . /W4 e /Wall podem emitir avisos falsos para arquivos de cabeçalho do sistema, mas se o seu projeto compilar corretamente com uma dessas opções, siga-o. Essas opções são mutuamente exclusivas.

A maioria dos outros compiladores entende opções como -Wpedantic , -Wpedantic e -Wextra . -Wall é essencial e todo o resto é recomendado (observe que, apesar do nome, -Wall apenas habilita os avisos mais importantes, nem todos ). Essas opções podem ser usadas separadamente ou todas juntas.

Seu IDE pode ter uma maneira de habilitá-los na interface do usuário.

Por que tratar os avisos como erros? São apenas avisos!

Um aviso do compilador sinaliza um problema potencialmente sério no seu código. Os problemas listados acima são quase sempre fatais; outras podem ou não estar, mas você deseja que a compilação falhe, mesmo que seja um alarme falso. Investigue cada aviso, encontre a causa raiz e corrija-a. No caso de um alarme falso, contorne-o, ou seja, use um recurso ou construção de idioma diferente para que o aviso não seja mais acionado. Se isso for muito difícil, desative esse aviso específico caso a caso.

Você não deseja apenas deixar avisos como avisos, mesmo que todos sejam falsos alarmes. Pode ser bom para projetos muito pequenos, em que o número total de avisos emitidos seja menor que 7. Mais alguma coisa e é fácil que um novo aviso se perca em uma enxurrada de velhos e familiares. Não permita isso. Apenas faça com que todo o seu projeto seja compilado de maneira limpa.

Observe que isso se aplica ao desenvolvimento do programa. Se você estiver liberando seu projeto para o mundo no formulário de origem, pode ser uma boa idéia não fornecer -Werror ou equivalente em seu script de construção liberado . As pessoas podem tentar criar seu projeto com uma versão diferente do compilador ou com um compilador completamente diferente, o que pode ter um conjunto diferente de avisos habilitados. Você pode querer que a construção deles seja bem-sucedida. Ainda é uma boa ideia manter os avisos ativados, para que as pessoas que vêem mensagens de aviso possam enviar relatórios ou correções de bugs.

Como tratar os avisos como erros?

Isso é feito novamente com opções de compilador. /WX é para a Microsoft, a maioria dos outros usa -Werror . Em ambos os casos, a compilação falhará se houver algum aviso produzido.

Costumo ouvir que, ao compilar programas em C e C ++, eu deveria "sempre ativar avisos do compilador". Por que isso é necessário? Como faço isso?

Às vezes, também ouço dizer que devo "tratar os avisos como erros". Eu devo? Como faço isso?


Avisos não fixos , mais cedo ou mais tarde, levarão a erros no seu código .

A depuração de uma falha de segmentação, por exemplo, exige que o programador rastreie a raiz (causa) da falha, que geralmente está localizada em um local anterior no seu código do que a linha que eventualmente causou a falha de segmentação.

É muito típico que a causa seja uma linha para a qual o compilador emitiu um aviso que você ignorou e a linha que causou a segmentação falha na linha que acabou gerando o erro.

A correção do aviso leva à correção do problema. Um clássico!

Uma demonstração do acima. Considere o seguinte código:

#include <stdio.h>

int main(void) {
  char* str = "Hello world!";
  int idx;

  // Colossal amount of code here, irrelevant to 'idx'

  printf("%c\n", str[idx]);

  return 0;
}

que quando compilado com o sinalizador "Wextra" passado para o GCC, fornece:

main.c: In function 'main':
main.c:9:21: warning: 'idx' is used uninitialized in this function [-Wuninitialized]
    9 |   printf("%c\n", str[idx]);
      |                     ^

que eu poderia ignorar e executar o código de qualquer maneira. E então eu testemunharia uma "grande" falha de segmentação, como meu professor de epicurus de IP costumava dizer:

falha de segmentação

Para depurar isso em um cenário do mundo real, seria iniciado a partir da linha que causa a falha de segmentação e tentado rastrear qual é a raiz da causa. Eles teriam que procurar o que aconteceu com i e str dentro desse quantidade colossal de código por lá ...

Até que, um dia, eles se encontraram na situação em que descobrem que i é usado não inicializado, portanto, tem um valor de lixo, o que resulta em indexar a string (muito) além de seus limites, o que leva a uma falha de segmentação.

Se eles não tivessem ignorado o aviso, teriam encontrado o bug imediatamente!


Algum aviso pode significar possível erro semântico no código ou possível UB. Por exemplo ; depois de if() , variável não utilizada, variável global mascarada por local ou comparação de assinado e não assinado. Muitos avisos estão relacionados ao analisador de código estático no compilador ou a violações do padrão ISO detectável em tempo de compilação, que "exigem diagnósticos". Embora essas ocorrências possam ser legais em um caso específico, elas resultariam de problemas de design na maioria das vezes.

Alguns compiladores, por exemplo, o gcc, têm uma opção de linha de comando para ativar o modo "avisos como erros"; é uma ferramenta legal, embora cruel, para educar codificadores iniciantes.


As outras respostas são excelentes e não quero repetir o que disseram.

Um outro aspecto do "por que ativar avisos" que não foi abordado adequadamente é que eles ajudam enormemente na manutenção do código. Quando você escreve um programa de tamanho significativo, torna-se impossível manter tudo na cabeça de uma só vez. Você normalmente tem uma função ou três nas quais está escrevendo e pensando ativamente, e talvez um arquivo ou três na tela que possa se referir, mas a maior parte do programa existe em segundo plano em algum lugar e você deve confiar que ele continua trabalhando.

Ter avisos e tê-los tão enérgicos quanto possível, ajuda a alertá-lo se algo que você mudar causar problemas para algo que você não pode ver.

Tomemos, por exemplo, o aviso de clang -Wswitch-enum . Isso aciona um aviso se você usar uma opção em uma enumeração e perder um dos valores possíveis da enumeração. É algo que você pode pensar que seria um erro improvável de cometer: você provavelmente ao menos olhou para a lista de valores de enumeração ao escrever a instrução switch. Você pode até ter um IDE que gerou as opções de opção para você, sem deixar espaço para erro humano.

Esse aviso realmente ocorre quando, seis meses depois, você adiciona outra entrada possível ao enum. Novamente, se você estiver pensando no código em questão, provavelmente ficará bem. Mas se esse enum é usado para vários propósitos diferentes e é para um daqueles que você precisa da opção extra, é muito fácil esquecer de atualizar uma opção em um arquivo que você não tocou por 6 meses.

Você pode pensar em avisos da mesma maneira que em casos de teste automatizados: eles ajudam a garantir que o código seja sensato e a fazer o que você precisa quando você o escreve pela primeira vez, mas ajudam ainda mais a garantir que ele continua fazendo o que você precisa enquanto estimula. A diferença é que os casos de teste funcionam muito estreitamente com os requisitos do seu código e você deve escrevê-los, enquanto os avisos funcionam amplamente para padrões sensíveis para quase todo o código, e são muito generosamente fornecidos pelos boffins que fazem os compiladores.


Como alguém que trabalha com código C incorporado herdado, a ativação de avisos do compilador ajudou a mostrar muitas fraquezas e áreas a serem investigadas ao propor correções. No gcc, utilizando -Wextra e -Wextra e até -Wshadow se tornaram vitais. Não vou correr todos os riscos, mas vou listar alguns que apareceram que ajudaram a mostrar problemas de código.

Variáveis ​​sendo deixadas para trás

Este pode facilmente apontar para trabalhos inacabados e áreas que podem não estar utilizando todas as variáveis ​​passadas, o que pode ser um problema. Vejamos uma função simples que pode desencadear isso:

int foo(int a, int b)
{
   int c = 0;

   if (a > 0)
   {
        return a;
   }
   return 0;
}

Apenas compilar isso sem -Wall ou -Wextra não gera problemas. -Wall lhe dirá que c nunca é usado:

foo.c: Na função 'foo':

foo.c: 9: 20: aviso: variável não utilizada 'c' [-Wunused-variable]

-Wextra também dirá que seu parâmetro b não faz nada:

foo.c: Na função 'foo':

foo.c: 9: 20: aviso: variável não utilizada 'c' [-Wunused-variable]

foo.c: 7: 20: aviso: parâmetro não utilizado 'b' [-Wunused-parameter] int foo (int a, int b)

Global Variable Shadowing

Isso foi um pouco difícil e não apareceu até que -Wshadow fosse usado. Vamos modificar o exemplo acima para adicionar, mas acontece que existe um global com o mesmo nome que um local, o que causa muita confusão ao tentar usar os dois.

int c = 7;

int foo(int a, int b)
{
   int c = a + b;
   return c;
}

Quando -Wshadow foi ativado, é fácil identificar esse problema.

foo.c: 11: 9: aviso: a declaração de 'c' sombreia uma declaração global [-Wshadow]

foo.c: 1: 5: note: a declaração sombreada está aqui

Formatar strings

Isso não requer nenhum sinalizador extra no gcc, mas ainda tem sido a fonte de problemas no passado. Uma função simples que tenta imprimir dados, mas possui um erro de formatação, pode se parecer com o seguinte:

void foo(const char * str)
{
    printf("str = %d\n", str);
}

Isso não imprime a cadeia de caracteres, pois o sinalizador de formatação está errado e o gcc informa com satisfação que isso provavelmente não é o que você queria:

foo.c: Na função 'foo':

foo.c: 10: 12: aviso: o formato '% d' espera o argumento do tipo 'int', mas o argumento 2 tem o tipo 'const char *' [-Wformat =]

Essas são apenas três das muitas coisas que o compilador pode verificar para você. Existem muitos outros como usar uma variável não inicializada que outros apontaram.


Esta é uma resposta específica para C e por que isso é muito mais importante para C do que para qualquer outra coisa.

#include <stdio.h>
int main()
{
   FILE *fp = "some string";
}

Esse código é compilado com um aviso . O que são e devem ser erros em praticamente todos os outros idiomas do planeta (exceto linguagem assembly) são avisos em C. Os avisos em C são quase sempre erros disfarçados. Os avisos devem ser corrigidos, não suprimidos.

Com o gcc , fazemos isso como gcc -Wall -Werror .

Esse também foi o motivo da alta segurança de alguns avisos de API não seguros da MS. A maioria das pessoas que programa C aprendeu a maneira mais difícil de tratar os avisos como erros e essas coisas pareciam não ser o mesmo tipo de coisa e queriam correções não portáteis.


Os avisos consistem nos melhores conselhos que alguns dos desenvolvedores de C ++ mais qualificados poderiam usar em um aplicativo. Vale a pena ficar por aqui.

O C ++, sendo uma linguagem completa de Turing, tem muitos casos em que o compilador deve simplesmente confiar que você sabia o que está fazendo. No entanto, existem muitos casos em que o compilador pode perceber que você provavelmente não pretendia escrever o que escreveu. Um exemplo clássico são os códigos printf () que não correspondem aos argumentos, ou std :: strings passados ​​para printf (não que isso aconteça comigo!). Nesses casos, o código que você escreveu não é um erro. É uma expressão válida do C ++ com uma interpretação válida para a ação do compilador. Mas o compilador tem um forte palpite de que você simplesmente ignorou algo que é fácil para um compilador moderno detectar. Estes são avisos. São coisas óbvias para um compilador, usando todas as regras estritas do C ++ à sua disposição, que você pode ter esquecido.

Desativar avisos ou ignorá-los é como optar por ignorar o aconselhamento gratuito daqueles mais qualificados que você. É uma lição de huberis que termina quando você voa muito perto do sol e suas asas derretem ou ocorre um erro de corrupção de memória. Entre os dois, eu vou cair do céu a qualquer dia!

"Tratar avisos como erros" é a versão extrema dessa filosofia. A idéia aqui é que você resolva todos os avisos que o compilador lhe der - ouça todos os conselhos gratuitos e aja de acordo. Se este é um bom modelo de desenvolvimento para você, depende da equipe e de que tipo de produto você está trabalhando. É a abordagem ascética que um monge pode ter. Para alguns, funciona muito bem. Para outros, não.

Em muitos dos meus aplicativos, não tratamos os avisos como erros. Fazemos isso porque esses aplicativos específicos precisam compilar em várias plataformas com vários compiladores de idades variadas. Às vezes, achamos que é realmente impossível corrigir um aviso de um lado sem que ele se transforme em um aviso em outra plataforma. Portanto, somos apenas cuidadosos. Nós respeitamos os avisos, mas não nos inclinamos para trás.


Os avisos do compilador em C ++ são muito úteis por alguns motivos.

1 - Permite mostrar onde você pode ter cometido um erro que pode impactar o resultado final de suas operações. Por exemplo, se você não inicializou uma variável ou se colocou "=" em vez de "==" (existem apenas exemplos)

2 - Permite também mostrar onde seu código não está em conformidade com o padrão do c ++. É útil porque, se o código estiver em conformidade com o padrão real, será fácil movê-lo para outra forma de placa, por exemplo.

Em geral, os avisos são muito úteis para mostrar onde há erros no seu código que podem afetar o resultado do seu algoritmo ou evitar algum erro quando o usuário usar o seu programa.


Você deve habilitar definitivamente os avisos do compilador, pois alguns compiladores são ruins em relatar alguns erros comuns de programação, incluindo o seguinte:

-> inicializa uma variável é esquecida -> retorna um valor de uma função - - os argumentos simples nas famílias printf e scanf que não correspondem à string de formato em que uma função é usada sem ser declarada anteriormente, embora ocorra apenas em c

Portanto, como essas funções podem ser detectadas e relatadas, geralmente não é o padrão; portanto, esse recurso deve ser solicitado explicitamente por meio de opções do compilador.


Você sempre deve habilitar avisos do compilador, pois o compilador pode frequentemente dizer o que há de errado com seu código. Para fazer isso, você passa -Wall -Wextra para o compilador.

Você geralmente deve tratar os avisos como erros, porque geralmente os avisos significam que há algo errado com o seu código. No entanto, geralmente é muito fácil ignorar esses erros. Portanto, tratá-los como erros fará com que a compilação falhe, portanto você não poderá ignorar os erros. Para tratar avisos como erros, passe -Werror para o compilador.







compiler-warnings