C++ Avisos (-Wall -Wextra)

Um erro impede o programa de existir. Um aviso (warning) deixa o programa ser criado, mas o compilador comenta: "isto aqui está com cara de engano". Muitos iniciantes ignoram os avisos, porque "o programa rodou". Nesta aula você vai ver, com resultados reais, programas que compilam, rodam e dão a resposta errada, e como o compilador avisou de cada um deles, se alguém tivesse pedido.

Você vai aprender
  • A diferença entre aviso e erro, e como reconhecer um aviso na tela.
  • O que -Wall e -Wextra ligam, e por que não vêm ligados de fábrica.
  • Quatro avisos que salvam programas: variável não usada, = no lugar de ==, comparação entre int e unsigned e variável sem valor.
  • Por que alguns avisos só aparecem com -O2.
  • O -Werror, que transforma avisos em erros.
  • Por que a meta é sempre zero avisos.

Os programas foram compilados na máquina da Ana com o g++ 15.2 e o clang++ 21.1. Alguns usam recursos das próximas aulas (variáveis, if); para esta aula, basta entender a ideia de cada um, que está explicada ao lado.


A ideia: a luz do painel do carro

Quando a luz do óleo acende no painel, o carro continua andando. Dá para ir trabalhar, voltar, passar a semana assim. Até o dia em que o motor funde. A luz não era o problema: era o aviso do problema.

Os avisos do compilador são essas luzes. E, por padrão, o g++ vem com quase todas elas desligadas, para não incomodar quem compila código antigo. As opções -Wall e -Wextra, que você conheceu em Compilar com g++, acendem o painel.


1. Aviso × erro

Erro (error:)Aviso (warning:)
O código segue as regras do C++?NãoSim
O programa é criado?NãoSim
Quer dizer que…"Não entendi o que você escreveu.""Entendi, mas duvido que você quisesse isso."
No fim da mensagem(nada)O nome do aviso entre colchetes, como [-Wunused-variable]

Um aviso tem o mesmo formato de um erro (aula Lendo Erros do Compilador), com warning: no lugar de error::

#include <iostream>

int main() {
    int itens = 3;
    std::cout << "Lista de compras da Ana\n";
}
$ g++ -std=c++23 compras.cpp -o compras
$                        « sem -Wall: silêncio
$ g++ -std=c++23 -Wall -Wextra compras.cpp -o compras
compras.cpp: In function ‘int main()’:
compras.cpp:4:9: warning: unused variable ‘itens’ [-Wunused-variable]
    4 |     int itens = 3;
      |         ^~~~~
compras.cpp:4:9:
Arquivo, linha e coluna, como num erro.
warning:
"Aviso". O programa foi criado.
unused variable ‘itens’
"Variável não usada: itens." A Ana guardou o número 3 e nunca mais olhou para ele.
[-Wunused-variable]
O nome do aviso. É a opção que o liga (e o termo para pesquisar, se a mensagem não ficar clara). Ele faz parte do grupo -Wall.

Uma variável não usada não quebra nada. Mas ela é uma pista: ou sobrou de uma versão antiga do código (apague), ou você esqueceu de usá-la (a Ana queria mostrar "3 itens"). O aviso apontou o esquecimento.


2. O que -Wall e -Wextra ligam

  • -Wall (all, "todos"): liga os avisos que quase sempre indicam um problema real. Apesar do nome, não são todos.
  • -Wextra (extra): liga mais alguns, um pouco mais exigentes.

Um exemplo do -Wextra: a main pode receber as palavras digitadas depois do nome do programa (argc e argv). Declarar e não usar dá aviso:

$ g++ -std=c++23 -Wall -Wextra args.cpp -o args          « int main(int argc, char* argv[]) {
args.cpp: In function ‘int main(int, char**)’:
args.cpp:3:14: warning: unused parameter ‘argc’ [-Wunused-parameter]
    3 | int main(int argc, char* argv[]) {
      |          ~~~~^~~~
args.cpp:3:26: warning: unused parameter ‘argv’ [-Wunused-parameter]
    3 | int main(int argc, char* argv[]) {
      |                    ~~~~~~^~~~~~

Correção: se a main não usa os argumentos, escreva só int main().

O Clang, o compilador do editor do guia, reconhece as mesmas opções e dá os avisos com os mesmos nomes entre colchetes. O editor compila sempre com -Wall -Wextra e mostra os avisos em cima da saída do programa.


3. Avisos que salvam programas

3.1. = no lugar de ==

Em C++, = guarda um valor (atribuição) e == compara dois valores (aulas Atribuição e Comparação). Trocar um pelo outro é um dos erros mais comuns da programação. A Ana tirou 4 e quer parabenizar só quem tirou 10:

#include <iostream>

int main() {
    int nota = 4;
    if (nota = 10) {
        std::cout << "Nota máxima!\n";
    }
    std::cout << "A nota é " << nota << "\n";
}

O if ("se") executa o bloco só se a condição for verdadeira (aula if e else). Ela queria escrever "se a nota for igual a 10", mas escreveu "guarde 10 na nota":

$ g++ -std=c++23 igual.cpp -o igual && ./igual
Nota máxima!             « errado: a nota era 4!
A nota é 10              « e pior: o 4 foi apagado e virou 10

Compila em silêncio, roda, e as duas linhas estão erradas. Com -Wall:

$ g++ -std=c++23 -Wall -Wextra igual.cpp -o igual && ./igual
igual.cpp: In function ‘int main()’:
igual.cpp:5:14: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
    5 |     if (nota = 10) {
      |         ~~~~~^~~~
Nota máxima!
A nota é 10

"Sugiro parênteses em volta da atribuição usada como valor de verdade." Em outras palavras: "você usou uma atribuição (=) como condição; se era isso mesmo, ponha parênteses extras para eu saber". Quase nunca era. O Clang é ainda mais claro e oferece as duas saídas:

$ clang++ -std=c++23 -Wall -Wextra igual.cpp -o igual
igual.cpp:5:14: warning: using the result of an assignment as a condition without parentheses [-Wparentheses]
    5 |     if (nota = 10) {
      |         ~~~~~^~~~
igual.cpp:5:14: note: place parentheses around the assignment to silence this warning
    5 |     if (nota = 10) {
      |              ^
      |         (        )
igual.cpp:5:14: note: use '==' to turn this assignment into an equality comparison
    5 |     if (nota = 10) {
      |              ^
      |              ==
1 warning generated.

A segunda nota: "use == para transformar esta atribuição numa comparação de igualdade". Correção: if (nota == 10).

Missão: rode e veja o aviso (o editor compila com -Wall -Wextra). Corrija o programa para que ele não mostre "Nota máxima!" para uma nota 4 e compile sem nenhum aviso.

#include <iostream>

int main() {
    int nota = 4;
    if (nota = 10) {
        std::cout << "Nota máxima!\n";
    }
    std::cout << "A nota é " << nota << "\n";
}
#include <iostream>

int main() {
    int nota = 4;
    if (nota == 10) {
        std::cout << "Nota máxima!\n";
    }
    std::cout << "A nota é " << nota << "\n";
}

3.2. Comparar int com unsigned

O C++ tem números inteiros com sinal (int, que aceita negativos) e sem sinal (unsigned int, só zero e positivos). Misturar os dois numa comparação dá um resultado surpreendente. A Ana está com o saldo negativo, −1, e o limite é 10:

#include <iostream>

int main() {
    int saldo = -1;
    unsigned int limite = 10;
    if (saldo < limite) {
        std::cout << "Saldo abaixo do limite\n";
    } else {
        std::cout << "Saldo OK\n";
    }
}
$ g++ -std=c++23 sinal.cpp -o sinal && ./sinal
Saldo OK                 « −1 não é menor que 10?!

−1 é menor que 10, mas o programa disse que não. O que aconteceu: para comparar, o C++ converteu o -1 para unsigned, e −1 sem sinal vira um número enorme (4294967295). E esse número não é menor que 10. Os detalhes estão em Inteiros; o que importa aqui é que o -Wall avisa:

$ g++ -std=c++23 -Wall -Wextra sinal.cpp -o sinal && ./sinal
sinal.cpp: In function ‘int main()’:
sinal.cpp:6:15: warning: comparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’ [-Wsign-compare]
    6 |     if (saldo < limite) {
      |         ~~~~~~^~~~~~~~
Saldo OK
$ clang++ -std=c++23 -Wall -Wextra sinal.cpp -o sinal
sinal.cpp:6:15: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
    6 |     if (saldo < limite) {
      |         ~~~~~ ^ ~~~~~~
1 warning generated.

"Comparação de expressões inteiras com sinais diferentes: int e unsigned int." Correção: compare valores do mesmo tipo (aqui, int limite = 10;). Você vai reencontrar esse aviso ao percorrer um std::vector com int.

3.3. Variável sem valor

Em C++, uma variável criada sem valor inicial (int pontos;) não vale zero: ela contém o que estiver naquele pedaço da memória, um valor "lixo". Usá-la antes de dar um valor é comportamento indefinido: o programa pode fazer qualquer coisa.

#include <iostream>

int main() {
    int pontos;
    std::cout << "Pontos: " << pontos << "\n";
}
$ g++ -std=c++23 semvalor.cpp -o semvalor && ./semvalor
Pontos: 0                « deu 0 por sorte; não conte com isso
$ g++ -std=c++23 -Wall -Wextra semvalor.cpp -o semvalor
semvalor.cpp: In function ‘int main()’:
semvalor.cpp:5:32: warning: ‘pontos’ is used uninitialized [-Wuninitialized]
    5 |     std::cout << "Pontos: " << pontos << "\n";
      |                                ^~~~~~
semvalor.cpp:4:9: note: ‘pontos’ was declared here
    4 |     int pontos;
      |         ^~~~~~
$ clang++ -std=c++23 -Wall -Wextra semvalor.cpp -o semvalor
semvalor.cpp:5:32: warning: variable 'pontos' is uninitialized when used here [-Wuninitialized]
    5 |     std::cout << "Pontos: " << pontos << "\n";
      |                                ^~~~~~
semvalor.cpp:4:15: note: initialize the variable 'pontos' to silence this warning
    4 |     int pontos;
      |               ^
      |                = 0
1 warning generated.

"pontos é usada sem ter sido inicializada" (sem valor inicial), e a nota diz onde ela foi criada. O Clang sugere a correção, = 0. Correção: dê sempre um valor ao criar a variável: int pontos = 0;. É uma regra deste guia desde a aula Variáveis.

3.4. O aviso que só aparece com -O2

No caso acima, a variável nunca recebe valor, e o g++ percebe isso de cara. Mas quando ela recebe valor às vezes, perceber exige uma análise mais funda do programa, que o g++ só faz quando otimiza (-O2, aula Compilar). Neste programa (o std::cin lê um número do teclado e o switch escolhe um caso; aulas Entrada com cin e switch), o desconto só recebe valor para os tipos 1 e 2:

#include <iostream>

int main() {
    int tipo = 0;
    std::cin >> tipo;
    int desconto;
    switch (tipo) {
        case 1: desconto = 5; break;
        case 2: desconto = 10; break;
    }
    std::cout << "Desconto: " << desconto << "%\n";
}
$ g++ -std=c++23 -Wall -Wextra v1.cpp -o v1
$                        « sem -O2: nenhum aviso
$ g++ -std=c++23 -Wall -Wextra -O2 v1.cpp -o v1
v1.cpp: In function ‘int main()’:
v1.cpp:11:34: warning: ‘desconto’ may be used uninitialized [-Wmaybe-uninitialized]
   11 |     std::cout << "Desconto: " << desconto << "%\n";
      |                                  ^~~~~~~~
v1.cpp:6:9: note: ‘desconto’ was declared here
    6 |     int desconto;
      |         ^~~~~~~~
$ echo 1 | ./v1          « o echo … | digita o número para o programa
Desconto: 5%
$ echo 3 | ./v1
Desconto: 5%             « tipo 3 não tem desconto, mas o programa deu 5%!

"desconto pode ser usada sem valor" (may be: em alguns caminhos, sim). E o resultado mostra o perigo: para o tipo 3, o programa inventou um desconto de 5%. Sem -O2, a mesma entrada deu Desconto: 0%: o lixo muda conforme a compilação. Por isso, de vez em quando, compile também com -O2 só para ver se aparecem avisos novos. (O Clang 21 não avisa neste caso, nem com -O2: cada compilador enxerga coisas diferentes.)

Correção: int desconto = 0;. Com um valor inicial, todos os caminhos ficam cobertos.

3.5. Outros avisos que você vai encontrar

Aviso (g++)ExemploO que quer dizer
statement has no effect [-Wunused-value]total == 20;Uma comparação solta, que não faz nada. Provavelmente era total = 20;. (Clang: equality comparison result unused, e sugere o =.)
right operand of comma operator has no effectstd::cout << "a", "b";Vírgula no lugar de << (aula Saída com cout).
multi-line comment [-Wcomment]// C:\fotos\A linha seguinte virou comentário (aula Comentários).
no return statement in function returning non-void [-Wreturn-type]Função que promete devolver um valor e não devolveEsse vem ligado até sem -Wall, de tão grave (aula Retorno).
unused parameter [-Wunused-parameter]int main(int argc, char* argv[]) sem usarParâmetro recebido e ignorado (-Wextra).

4. -Werror: avisos viram erros

Com -Werror, todo aviso passa a ser tratado como erro, e o programa não é criado:

$ g++ -std=c++23 -Wall -Wextra -Werror igual.cpp -o igual
igual.cpp: In function ‘int main()’:
igual.cpp:5:14: error: suggest parentheses around assignment used as truth value [-Werror=parentheses]
    5 |     if (nota = 10) {
      |         ~~~~~^~~~
cc1plus: all warnings being treated as errors
$ echo $?
1
$ g++ -std=c++23 -Wall -Wextra -Werror compras.cpp -o compras
compras.cpp: In function ‘int main()’:
compras.cpp:4:9: error: unused variable ‘itens’ [-Werror=unused-variable]
    4 |     int itens = 3;
      |         ^~~~~
cc1plus: all warnings being treated as errors

Repare: warning: virou error:, o nome entre colchetes virou [-Werror=…], e a última linha explica: "todos os avisos estão sendo tratados como erros". O Clang marca assim: [-Werror,-Wunused-variable].

Quando usar? Em projetos sérios, o -Werror costuma estar ligado no sistema de compilação (Make, CMake) e nos servidores que testam cada mudança: ninguém consegue entregar código com aviso. Para quem está aprendendo, é uma ótima disciplina: ligue-o e o compilador não deixa você "deixar para depois".


5. Por que nunca ignorar avisos

  1. Os avisos apontam bugs reais. Os quatro exemplos da seção 3 compilavam e rodavam, e todos davam resultado errado.
  2. Avisos se acumulam. Um aviso "inofensivo" hoje vira dez amanhã, e o aviso importante se perde no meio deles. Com zero avisos, qualquer aviso novo salta aos olhos.
  3. O compilador sabe mais que você sobre as armadilhas da linguagem. O C++ tem muitas, e os avisos são décadas de experiência resumidas.
  4. Corrigir na hora é barato. O mesmo engano, achado depois que o programa está nas mãos de alguém, custa muito mais.
Não "conserte" calando o compilador

Existe a opção -w (minúsculo), que desliga todos os avisos:

$ g++ -std=c++23 -w igual.cpp -o igual
$                        « o aviso sumiu; o erro do programa continua lá

É apagar a luz do óleo em vez de pôr óleo. Não use. A correção de um aviso é sempre mudar o código.

A regra do guia: compile sempre com -Wall -Wextra e só considere o programa pronto quando a compilação estiver em silêncio. As missões do editor muitas vezes exigem isso (a meta "sem avisos").


Quando dá errado

"O programa rodou, mas o resultado está errado"

O que significa: muitas vezes, o compilador sabia. Você compilou sem -Wall -Wextra, ou viu o aviso e ignorou.

Como consertar: compile de novo com -Wall -Wextra (e, para garantir, com -O2 também) e leia cada warning:.

suggest parentheses around assignment used as truth value

O que significa: um = dentro de um if (ou while). Quase sempre era ==.

Como consertar: troque por ==.

comparison of integer expressions of different signedness

O que significa: você está comparando um número que aceita negativos com um que não aceita. Se o primeiro for negativo, o resultado sai errado.

Como consertar: use o mesmo tipo dos dois lados.

is used uninitialized / may be used uninitialized

O que significa: uma variável é lida antes de receber valor (sempre, ou em algum caminho).

Como consertar: dê um valor inicial na criação: int x = 0;.

cc1plus: all warnings being treated as errors

O que significa: o comando tem -Werror; um aviso impediu a criação do programa.

Como consertar: corrija o aviso indicado acima dessa linha. (Tirar o -Werror só esconde o problema.)


Referência rápida

OpçãoO que faz
-WallLiga os avisos principais (sempre use)
-WextraLiga avisos extras (sempre use)
-O2Otimiza; de quebra, revela avisos como -Wmaybe-uninitialized
-WerrorTrata avisos como erros: o programa não é criado
-wDesliga todos os avisos (não use)
AvisoGrupoCorreção típica
unused variable-WallUse a variável ou apague-a
unused parameter-WextraTire o parâmetro ou use-o
suggest parentheses around assignment-Wall= → ==
different signedness-Wall (no C++)Mesmo tipo dos dois lados
is used uninitialized-WallValor inicial: int x = 0;
may be used uninitialized-Wall + -O2Valor inicial: int x = 0;
statement has no effect-WallProvavelmente == → =

Exercícios

1. Ligar os avisos

Complete o comando padrão do guia:

g++ -std=c++23 jogo.cpp -o jogo

2. Avisos como erros

Que opção faz qualquer aviso impedir a criação do programa?

g++ -std=c++23 -Wall -Wextra jogo.cpp -o jogo

3. Corrija a condição

Complete para comparar, e não guardar:

if (pontos 100) {

4. O que acontece quando a compilação dá só um warning:?

5. Por que if (saldo < limite), com saldo = -1 (int) e limite = 10 (unsigned), deu falso?

6. Qual é a forma certa de lidar com um aviso?

Missão final: zero avisos

Missão: este programa da lojinha compila com três avisos e mostra coisas erradas. Corrija o código (não use -w!) até a compilação ficar sem nenhum aviso e a saída ser:

Estoque da lojinha
Canetas: 0
Repor canetas!

Dicas: uma variável sobrando, uma variável sem valor (as canetas começam em 0) e um = que deveria ser ==.

#include <iostream>

int main() {
    int cadernos = 12;
    int canetas;
    std::cout << "Estoque da lojinha\n";
    std::cout << "Canetas: " << canetas << "\n";
    if (canetas = 0) {
        std::cout << "Repor canetas!\n";
    }
}
#include <iostream>

int main() {
    int canetas = 0;
    std::cout << "Estoque da lojinha\n";
    std::cout << "Canetas: " << canetas << "\n";
    if (canetas == 0) {
        std::cout << "Repor canetas!\n";
    }
}

Resumo

  • Um aviso (warning:) não impede a criação do programa, mas quase sempre aponta um engano real.
  • O g++ vem com quase todos os avisos desligados. -Wall -Wextra os ligam: use sempre.
  • Avisos que salvam programas: = no lugar de ==, int comparado com unsigned, variável sem valor, variável não usada.
  • Alguns avisos (may be used uninitialized) só aparecem com -O2; compile com ele de vez em quando.
  • -Werror transforma avisos em erros; -w esconde todos (não use).
  • A meta é zero avisos: a correção é sempre mudar o código.