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.
- A diferença entre aviso e erro, e como reconhecer um aviso na tela.
- O que
-Walle-Wextraligam, 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 entreinteunsignede 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ão | Sim |
| O programa é criado? | Não | Sim |
| 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++) | Exemplo | O 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 effect | std::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 devolve | Esse vem ligado até sem -Wall, de tão grave (aula Retorno). |
unused parameter [-Wunused-parameter] | int main(int argc, char* argv[]) sem usar | Parâ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
- Os avisos apontam bugs reais. Os quatro exemplos da seção 3 compilavam e rodavam, e todos davam resultado errado.
- 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.
- 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.
- Corrigir na hora é barato. O mesmo engano, achado depois que o programa está nas mãos de alguém, custa muito mais.
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 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ção | O que faz |
|---|---|
-Wall | Liga os avisos principais (sempre use) |
-Wextra | Liga avisos extras (sempre use) |
-O2 | Otimiza; de quebra, revela avisos como -Wmaybe-uninitialized |
-Werror | Trata avisos como erros: o programa não é criado |
-w | Desliga todos os avisos (não use) |
| Aviso | Grupo | Correção típica |
|---|---|---|
unused variable | -Wall | Use a variável ou apague-a |
unused parameter | -Wextra | Tire o parâmetro ou use-o |
suggest parentheses around assignment | -Wall | = → == |
different signedness | -Wall (no C++) | Mesmo tipo dos dois lados |
is used uninitialized | -Wall | Valor inicial: int x = 0; |
may be used uninitialized | -Wall + -O2 | Valor inicial: int x = 0; |
statement has no effect | -Wall | Provavelmente == → = |
Exercícios
1. Ligar os avisos
Complete o comando padrão do guia:
2. Avisos como erros
Que opção faz qualquer aviso impedir a criação do programa?
3. Corrija a condição
Complete para comparar, e não guardar:
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 -Wextraos ligam: use sempre. - Avisos que salvam programas:
=no lugar de==,intcomparado comunsigned, 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. -Werrortransforma avisos em erros;-wesconde todos (não use).- A meta é zero avisos: a correção é sempre mudar o código.