C ++ e --

Contar é uma das coisas que um programa mais faz: mais uma pessoa entrou na loja, mais um ponto no jogo, menos uma vida, menos uma vaga. Somar 1 e tirar 1 são tão comuns que o C tem dois operadores só para isso: ++ (incremento) e -- (decremento). O nome da linguagem C++ vem daí: "o C, mais um". Nesta aula você vai usar os dois, entender a diferença entre x++ e ++x e ver por que linhas como i = i++ são proibidas, mesmo compilando.

Você vai aprender
  • Como somar 1 com ++ e tirar 1 com --.
  • A diferença entre o pré (++x) e o pós (x++), que só aparece quando o resultado é usado numa expressão.
  • Como usar ++ dentro do printf e com char e double.
  • Por que i = i++ e printf("%d %d", n++, n++) são comportamento indefinido, e o aviso -Wsequence-point.
  • Os erros lvalue required as increment operand e increment of read-only variable.

Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.


A ideia: o contador da porta

Na entrada de um show, o segurança segura um contador de mão: a cada pessoa que passa, ele aperta o botão e o número sobe 1. Ele não precisa fazer conta nenhuma, só apertar. Se alguém sai, ele usa outro botão, que desce 1.

O ++ é o botão "mais um" e o -- é o botão "menos um". pessoas++ faz a mesma coisa que pessoas = pessoas + 1, só que mais curto.


1. Somar 1 e tirar 1

#include <stdio.h>

int main(void) {
    int pessoas = 0;
    pessoas++;
    pessoas++;
    pessoas++;
    printf("Entraram: %d\n", pessoas);
    pessoas--;
    printf("Ficaram: %d\n", pessoas);
    ++pessoas;
    --pessoas;
    printf("No fim: %d\n", pessoas);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra contador.c -o contador
$ ./contador
Entraram: 3
Ficaram: 2
No fim: 2        « ++pessoas subiu para 3, --pessoas voltou para 2
pessoas++;
Soma 1 em pessoas e guarda o resultado na própria variável. É o mesmo que pessoas = pessoas + 1; ou pessoas += 1; (aula Operadores de Atribuição).
pessoas--;
Tira 1. O mesmo que pessoas = pessoas - 1;.
++pessoas;
O sinal também pode vir antes do nome. Numa linha sozinha, ++pessoas; e pessoas++; fazem exatamente a mesma coisa.
sem espaço
Os dois sinais vão grudados: ++. Com espaço, + + são dois "mais" separados: pessoas+ +; nem compila (error: expected expression before ‘;’ token).

Os nomes: incremento é "aumento" e decremento é "diminuição". Quando o sinal vem antes (++x), chama-se pré-incremento; depois (x++), pós-incremento. Você vai ver o ++ o tempo todo nos laços, como em for (int i = 0; i < 10; i++) (aula for).

Experimente: o placar começa em 0. Acrescente mais linhas gols++; e veja o total mudar. Depois anule um gol com gols--;.

#include <stdio.h>

int main(void) {
    int gols = 0;
    gols++;
    gols++;
    printf("Placar: %d gols\n", gols);
    return 0;
}

2. Pré × pós: quando o resultado é usado

Até aqui, x++ e ++x pareciam iguais. A diferença aparece quando o ++ está dentro de outra coisa, como uma atribuição ou um printf. Além de mudar a variável, o ++ produz um valor, e esse valor depende do lado do sinal:

  • x++ (pós): entrega o valor antigo, e depois soma.
  • ++x (pré): soma primeiro, e entrega o valor novo.
#include <stdio.h>

int main(void) {
    int a = 5;
    int b = a++;
    printf("depois de b = a++: a = %d, b = %d\n", a, b);

    int c = 5;
    int d = ++c;
    printf("depois de d = ++c: c = %d, d = %d\n", c, d);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra prepos.c -o prepos
$ ./prepos
depois de b = a++: a = 6, b = 5     « b recebeu o valor ANTIGO de a
depois de d = ++c: c = 6, d = 6     « d recebeu o valor NOVO de c
LinhaPasso a passoDepois
int b = a++;1) pega o valor de a (5) para entregar; 2) soma 1 em a; 3) b recebe o 5 entreguea = 6, b = 5
int d = ++c;1) soma 1 em c (vira 6); 2) entrega o 6; 3) d recebe 6c = 6, d = 6

Um jeito de lembrar: leia da esquerda para a direita. Em a++, o nome vem primeiro: "usa o a, depois soma". Em ++c, o ++ vem primeiro: "soma, depois usa o c". Nos dois casos, a variável termina com 1 a mais; o que muda é só o valor entregue.

Dentro do printf

Uso típico: a fila de senhas de um banco. O painel mostra a senha atual e já deixa a próxima preparada:

#include <stdio.h>

int main(void) {
    int senha = 41;
    printf("Chamando a senha %d\n", senha++);
    printf("Chamando a senha %d\n", senha++);
    printf("Próxima a ser chamada: %d\n", senha);
    int vagas = 3;
    printf("Restam %d vagas\n", --vagas);
    printf("Restam %d vagas\n", --vagas);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra senhas.c -o senhas
$ ./senhas
Chamando a senha 41      « mostrou 41, depois senha virou 42
Chamando a senha 42      « mostrou 42, depois senha virou 43
Próxima a ser chamada: 43
Restam 2 vagas           « --vagas: tirou 1 (3 → 2) e depois mostrou
Restam 1 vagas

Missão: o painel mostra a mesma senha três vezes. Sem acrescentar linhas, use ++ dentro dos printf para a saída ficar assim (pense: pós ou pré?):

Senha 1
Senha 2
Senha 3
#include <stdio.h>

int main(void) {
    int senha = 1;
    printf("Senha %d\n", senha);
    printf("Senha %d\n", senha);
    printf("Senha %d\n", senha);
    return 0;
}
#include <stdio.h>

int main(void) {
    int senha = 1;
    printf("Senha %d\n", senha++);
    printf("Senha %d\n", senha++);
    printf("Senha %d\n", senha++);
    return 0;
}

Com senha começando em 1, o certo é o pós (senha++): mostra o 1 e só depois soma. Com ++senha, a saída começaria em 2.


3. Com char e double

O ++ e o -- funcionam com qualquer tipo de número. Um char é guardado como o código do caractere (aula Caracteres), então somar 1 passa para o próximo caractere da tabela. Um double ganha 1 inteiro, e a parte depois do ponto continua igual:

#include <stdio.h>

int main(void) {
    char letra = 'a';
    letra++;
    double preco = 9.75;
    preco++;
    printf("letra = %c\n", letra);
    printf("preco = %.2f\n", preco);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra tipos.c -o tipos
$ ./tipos
letra = b        « 'a' (97) + 1 = 'b' (98)
preco = 10.75    « soma 1, não 0.01

Com double funciona, mas é raro: quase sempre você vai usar ++ com inteiros, para contar.


4. i = i++: comportamento indefinido

Agora a parte perigosa. A Ana quis somar 1 nos pontos de um jogo e tirar uma vida, e escreveu "duplicado", achando que ficaria mais garantido:

#include <stdio.h>

int main(void) {
    int pontos = 10;
    int vidas = 3;
    pontos = pontos++;
    vidas = --vidas;
    printf("Pontos: %d\nVidas: %d\n", pontos, vidas);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra jogo.c -o jogo
jogo.c: In function ‘main’:
jogo.c:6:12: warning: operation on ‘pontos’ may be undefined [-Wsequence-point]
    6 |     pontos = pontos++;
      |     ~~~~~~~^~~~~~~~~~
jogo.c:7:11: warning: operation on ‘vidas’ may be undefined [-Wsequence-point]
    7 |     vidas = --vidas;
      |     ~~~~~~^~~~~~~~~
$ ./jogo
Pontos: 10       « o ++ "sumiu"!
Vidas: 2

O aviso diz: "a operação em pontos pode ser indefinida". O problema é que, na mesma linha, pontos é mudado duas vezes: uma pelo ++ e outra pelo =. As regras do C não dizem qual das duas mudanças acontece primeiro. Nesse caso, o padrão da linguagem diz que o programa tem comportamento indefinido (undefined behavior): o compilador pode fazer qualquer coisa, e cada compilador faz uma.

Aqui o gcc fez assim: guardou o 10 para entregar, somou 1 (11), e depois o = guardou o 10 entregue por cima do 11. O vidas = --vidas deu 2 "por sorte". Nenhum dos dois resultados é garantido.

Dois compiladores, duas respostas

O mesmo problema acontece quando a variável muda duas vezes dentro dos argumentos de uma função:

#include <stdio.h>

int main(void) {
    int i = 5;
    i = i++;
    printf("i = %d\n", i);
    int n = 1;
    printf("%d %d\n", n++, n++);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra indefinido.c -o indefinido
indefinido.c: In function ‘main’:
indefinido.c:5:7: warning: operation on ‘i’ may be undefined [-Wsequence-point]
    5 |     i = i++;
      |     ~~^~~~~
indefinido.c:8:29: warning: operation on ‘n’ may be undefined [-Wsequence-point]
    8 |     printf("%d %d\n", n++, n++);
      |                            ~^~
$ ./indefinido
i = 5
2 1              « o gcc calculou o segundo n++ primeiro
$ clang -std=c23 -Wall -Wextra indefinido.c -o indefinido
indefinido.c:5:10: warning: multiple unsequenced modifications to 'i' [-Wunsequenced]
    5 |     i = i++;
      |       ~  ^
indefinido.c:8:24: warning: multiple unsequenced modifications to 'n' [-Wunsequenced]
    8 |     printf("%d %d\n", n++, n++);
      |                        ^    ~~
2 warnings generated.
$ ./indefinido
i = 5
1 2              « o Clang, na mesma máquina, deu o contrário

O mesmo código, na mesma máquina, mostrou 2 1 com o gcc e 1 2 com o Clang (o compilador do editor do guia também mostra 1 2). O aviso do Clang explica bem: "várias modificações sem ordem definida em n". A vírgula que separa os argumentos de uma função não define ordem: o C não promete calcular o primeiro argumento antes do segundo.

O nome do aviso do gcc, -Wsequence-point, vem de um termo antigo do padrão: ponto de sequência, um ponto do programa em que tudo o que veio antes já terminou (o ; no fim de uma instrução é um deles). Desde o C11, o padrão fala em operações "sequenciadas" e "não sequenciadas", que é a palavra do Clang. O aviso vem com o -Wall. A aula Comportamento Indefinido mostra outros casos.

A regra prática

Numa mesma instrução, não mude uma variável duas vezes, nem mude e leia a mesma variável em outro lugar. Use o ++ sozinho na linha (pontos++;) ou numa expressão em que a variável aparece uma vez só (printf("%d", senha++);). Se o gcc disser may be undefined, reescreva em duas linhas.


Formas diferentes de somar 1

FormaO que fazQuando usar
x = x + 1;Calcula x + 1 e guarda em xPara quem está começando, é a mais óbvia. Na prática, pouco usada para somar 1.
x += 1;O mesmo, mais curtoQuando o passo pode mudar: x += 2;, x += bonus;.
x++;O mesmo, ainda mais curtoPara contar de 1 em 1. É a forma mais comum em C, sobretudo nos laços.
++x;O mesmo, numa linha sozinhaIgual a x++;. Em C, escolha um e seja consistente; o guia usa x++.
y = x++; / y = ++x;Muda x e entrega o valor antigo / novoCom moderação, e só quando a variável aparece uma vez na instrução. Duas linhas (y = x; x++;) costumam ser mais claras.

Quando dá errado

warning: operation on ‘i’ may be undefined [-Wsequence-point]

O que significa: a mesma variável é mudada duas vezes (ou mudada e lida) na mesma instrução, como em i = i++ ou printf("%d %d", n++, n++). O resultado é imprevisível. No Clang: multiple unsequenced modifications to 'i' [-Wunsequenced].

Como consertar: separe em linhas. Para somar 1, basta i++;, sem o i =.

error: lvalue required as increment operand
$ gcc -std=c23 -Wall -Wextra lvalue.c -o lvalue
lvalue.c: In function ‘main’:
lvalue.c:5:12: error: lvalue required as increment operand
    5 |     (x + 1)++;
      |            ^~
lvalue.c:6:6: error: lvalue required as increment operand
    6 |     5++;
      |      ^~

O que significa: o ++ precisa de um lugar para guardar o resultado, isto é, uma variável. 5 e (x + 1) são só valores, não têm onde guardar. lvalue é o nome técnico de "algo que pode ficar do lado esquerdo de um =" (left value). No Clang: expression is not assignable ("a expressão não pode receber valor").

Como consertar: aplique o ++ numa variável. Se você queria "x mais 1" sem mudar x, escreva só x + 1.

error: increment of read-only variable ‘dias’
$ gcc -std=c23 -Wall -Wextra constante.c -o constante
constante.c: In function ‘main’:
constante.c:5:9: error: increment of read-only variable ‘dias’
    5 |     dias++;
      |         ^~

O que significa: dias foi declarada com const ("somente leitura"; aula Constantes) e não pode mudar. No Clang: cannot assign to variable 'dias' with const-qualified type 'const int'.

Como consertar: se o valor precisa mudar, tire o const; se não precisa, não use ++ nela.

O número saiu 1 a mais (ou a menos) do que você esperava

O que significa: você usou o pré onde queria o pós, ou o contrário. printf("%d", ++senha) mostra o valor já somado.

Como consertar: lembre: x++ entrega o antigo, ++x entrega o novo. Na dúvida, faça o ++ numa linha separada, antes ou depois do printf.


Referência

ExpressãoMuda a variável paraValor entregueExemplo (x vale 5)
x++x + 1o antigoy = x++; → x = 6, y = 5
++xx + 1o novoy = ++x; → x = 6, y = 6
x--x - 1o antigoy = x--; → x = 4, y = 5
--xx - 1o novoy = --x; → x = 4, y = 4
x = x++Comportamento indefinido: nunca escreva (-Wsequence-point).

Funciona com variáveis de qualquer tipo de número (int, long, char, double…), e com ponteiros (aula Ponteiros e Arrays). Não funciona com constantes, valores calculados nem variáveis const.


Exercícios

1. Mais um

Complete para somar 1 em visitas:

visitas;

2. Valor entregue

Depois de int a = 8; int b = a--;, quanto vale b?

b vale

3. E agora?

Depois de int a = 8; int b = --a;, quanto vale b?

b vale

4. O que este trecho mostra? int n = 3; printf("%d ", n++); printf("%d", n);

5. Por que i = i++; é um erro, mesmo compilando?

6. O que o gcc diz de 10++;?

Missão final: o placar do jogo

Missão: o jogo da Ana deveria dar 1 ponto e tirar 1 vida, mas as linhas 6 e 7 têm comportamento indefinido. Rode, leia os avisos e reescreva essas duas linhas para a saída ficar assim, sem avisos:

Pontos: 11
Vidas: 2
#include <stdio.h>

int main(void) {
    int pontos = 10;
    int vidas = 3;
    pontos = pontos++;
    vidas = --vidas;
    printf("Pontos: %d\nVidas: %d\n", pontos, vidas);
    return 0;
}
#include <stdio.h>

int main(void) {
    int pontos = 10;
    int vidas = 3;
    pontos++;
    vidas--;
    printf("Pontos: %d\nVidas: %d\n", pontos, vidas);
    return 0;
}

Resumo

  • x++ soma 1 em x; x-- tira 1. Numa linha sozinha, ++x e x++ são iguais.
  • Dentro de uma expressão: x++ (pós) entrega o valor antigo e depois soma; ++x (pré) soma e entrega o novo.
  • Funciona com int, char (próximo caractere), double (soma 1 inteiro) e outros tipos de número.
  • Mudar a mesma variável duas vezes na mesma instrução (i = i++, printf("%d %d", n++, n++)) é comportamento indefinido. O gcc avisa com -Wsequence-point, o Clang com -Wunsequenced, e cada um pode dar um resultado.
  • 5++ e (x + 1)++: lvalue required as increment operand. Variável const: increment of read-only variable.