C Precedência

Quanto é 2 + 3 * 4? Se você respondeu 14, já conhece a ideia desta aula: alguns operadores são calculados antes de outros. Na escola isso se chama "ordem das operações"; em programação, precedência. O C tem mais de 40 operadores, e todos têm um lugar nessa fila. Nesta aula você vai ver a tabela completa, aprender a usar parênteses para mandar na ordem e conhecer as armadilhas em que até programadores experientes caem.

Você vai aprender
  • O que é precedência (quem vai primeiro) e associatividade (empate: da esquerda ou da direita?).
  • A tabela completa dos operadores do C, do mais "forte" ao mais "fraco".
  • Como os parênteses resolvem tudo, e quando usá-los mesmo sem precisar.
  • Os erros clássicos: a média n1 + n2 / 2, o cast (double) (soma / qtd), n & 1 == 0, 1 << 2 + 1 e *p++.
  • Por que precedência não é a ordem em que as coisas são calculadas.

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: a fila do banco com prioridade

Num banco, há uma fila só, mas algumas pessoas têm prioridade: são atendidas primeiro, não importa onde estejam. Entre duas pessoas com a mesma prioridade, vale a ordem de chegada.

Uma expressão em C funciona assim:

  • Precedência é a prioridade: em 2 + 3 * 4, o * tem prioridade maior que o +, então 3 * 4 é agrupado primeiro.
  • Associatividade é o desempate entre operadores do mesmo nível: em 10 - 4 - 3, os dois - têm a mesma prioridade, e o C agrupa da esquerda para a direita: (10 - 4) - 3.
  • Parênteses são o "fura-fila" oficial: o que está dentro deles é agrupado primeiro, sempre.

1. Contas: como na escola

#include <stdio.h>

int main(void) {
    printf("%d\n", 2 + 3 * 4);
    printf("%d\n", (2 + 3) * 4);
    printf("%d\n", 10 - 4 - 3);
    printf("%d\n", 100 / 10 / 2);
    printf("%d\n", 7 + 10 % 3);
    printf("%d\n", -2 * -3);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra conta.c -o conta
$ ./conta
14       « 2 + (3 * 4): a multiplicação vai primeiro
20       « (2 + 3) * 4: os parênteses mandam
3        « (10 - 4) - 3: da esquerda para a direita
5        « (100 / 10) / 2, e não 100 / (10 / 2), que daria 20
8        « 7 + (10 % 3): o resto (%) tem a prioridade do * e do /
6        « (-2) * (-3): o sinal de menos "grudado" vai antes de tudo
*, / e %
Mesmo nível, acima do + e do -. Como na escola, com o % (resto da divisão; aula Operadores Aritméticos) junto da multiplicação.
esquerda para a direita
Quase todos os operadores de dois lados se agrupam assim. Com + e * tanto faz, mas com - e / faz diferença.
-2
O - na frente de um número só (o menos unário, "de um operando") tem prioridade maior que qualquer conta de dois lados.

Experimente: antes de rodar, calcule de cabeça o resultado de cada linha. Depois rode e confira. Mude os parênteses de lugar e veja o que muda.

#include <stdio.h>

int main(void) {
    printf("%d\n", 1 + 2 * 3 + 4);
    printf("%d\n", (1 + 2) * (3 + 4));
    printf("%d\n", 20 / 2 * 5);
    printf("%d\n", 20 / (2 * 5));
    return 0;
}

2. Dois erros de conta muito comuns

A média que sai errada

#include <stdio.h>

int main(void) {
    int n1 = 8;
    int n2 = 6;
    printf("Errado: %d\n", n1 + n2 / 2);
    printf("Certo: %d\n", (n1 + n2) / 2);
    int soma = 15;
    int qtd = 2;
    printf("%.1f\n", (double) soma / qtd);
    printf("%.1f\n", (double) (soma / qtd));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra media.c -o media
$ ./media
Errado: 11       « 8 + (6 / 2) = 8 + 3
Certo: 7         « (8 + 6) / 2 = 14 / 2
7.5              « ((double) 15) / 2 = 15.0 / 2
7.0              « (double) (15 / 2) = (double) 7: a vírgula já tinha sido perdida

O primeiro erro é de matemática pura: sem parênteses, só o n2 é dividido. O gcc não avisa nada, porque n1 + n2 / 2 é uma conta válida; só você sabe que queria a média.

O segundo é do C: o cast (double) (a conversão de tipo; aula Conversão de Tipos) é um operador de um operando só e tem prioridade alta, como o menos unário. Ele pega só o que vem logo depois:

  • (double) soma / qtd converte soma para 15.0 e então divide: 15.0 / 2 é uma divisão com vírgula, 7.5.
  • (double) (soma / qtd) primeiro divide os inteiros (15 / 2 = 7, a parte depois da vírgula é jogada fora) e só depois converte o 7 em 7.0. Tarde demais.

Missão: a média das três notas da Ana (7, 8 e 9) é 8, mas o programa mostra 18. Coloque os parênteses no lugar certo:

Média: 8
#include <stdio.h>

int main(void) {
    int n1 = 7;
    int n2 = 8;
    int n3 = 9;
    int media = n1 + n2 + n3 / 3;
    printf("Média: %d\n", media);
    return 0;
}
#include <stdio.h>

int main(void) {
    int n1 = 7;
    int n2 = 8;
    int n3 = 9;
    int media = (n1 + n2 + n3) / 3;
    printf("Média: %d\n", media);
    return 0;
}

3. A tabela completa

Esta é a tabela de precedência do C23, do nível mais alto (agrupado primeiro) ao mais baixo. Você não precisa decorá-la: guarde a ideia geral e consulte quando tiver dúvida. Os operadores que você ainda não conhece têm aula própria.

NívelOperadoresO que sãoAssociatividade
1x++ x-- f() v[i] s.campo p->campo (tipo){…}pós-incremento (++ e --), chamada de função, índice de array, campo de struct, literal compostoesquerda → direita
2++x --x +x -x ! ~ (tipo) *p &x sizeof alignofoperadores de um operando: pré-incremento, sinal, não lógico, não de bits, cast, conteúdo e endereço (ponteiros), tamanhodireita → esquerda
3* / %multiplicação, divisão, restoesquerda → direita
4+ -soma, subtraçãoesquerda → direita
5<< >>deslocamento de bits (Operadores de Bits)esquerda → direita
6< <= > >=comparações de ordem (Comparação)esquerda → direita
7== !=igual, diferenteesquerda → direita
8&E de bitsesquerda → direita
9^OU exclusivo de bitsesquerda → direita
10|OU de bitsesquerda → direita
11&&E lógico (Lógicos)esquerda → direita
12||OU lógicoesquerda → direita
13? :operador ternário (Ternário)direita → esquerda
14= += -= *= /= %= <<= >>= &= ^= |=atribuições (Atribuição)direita → esquerda
15,operador vírgulaesquerda → direita

Um resumo para guardar de cabeça, de cima para baixo: "de um lado só" → contas (* / antes de + -) → comparações → lógicos (&& antes de ||) → ternário → atribuição. Os operadores de bits (&, ^, |) ficam num lugar estranho, abaixo das comparações, e é daí que vêm as armadilhas da seção 5.

Sobre o nível 2: alignof virou palavra-chave no C23 C23; antes era _Alignof (C11). A vírgula do nível 15 é um operador raro; a vírgula que separa os argumentos do printf é outra coisa, só um separador.


4. Os que agrupam da direita para a esquerda

Três grupos da tabela se agrupam da direita para a esquerda: os de um operando, o ternário e as atribuições. Isso permite escrever:

#include <stdio.h>

int main(void) {
    int a, b, c;
    a = b = c = 7;
    printf("%d %d %d\n", a, b, c);
    int x = 2;
    x *= 3 + 1;
    printf("%d\n", x);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra atribs.c -o atribs
$ ./atribs
7 7 7    « a = (b = (c = 7)): o 7 "escorre" da direita para a esquerda
8        « x *= (3 + 1): a atribuição é a última, então x = 2 * 4
a = b = c = 7;
Agrupado como a = (b = (c = 7)). Primeiro c recebe 7; o valor dessa atribuição (7) vai para b; e o de b vai para a. Útil para zerar várias variáveis de uma vez.
x *= 3 + 1;
As atribuições estão quase no fim da tabela: todo o lado direito é calculado antes. Então é x = x * (3 + 1), e não (x * 3) + 1.
-!x, !-x…
Com os de um operando, "da direita para a esquerda" quer dizer: o que está mais perto do valor vai primeiro. Em -!x, primeiro o !, depois o -.

5. Armadilhas: operadores de bits e comparações

Este programa quer saber se n é par. Um número é par quando o último bit dele é 0, e n & 1 pega esse último bit (aula Operadores de Bits; aqui basta saber que n & 1 dá 0 para números pares e 1 para ímpares):

#include <stdio.h>

int main(void) {
    int n = 4;
    printf("%d\n", n & 1 == 0);
    printf("%d\n", (n & 1) == 0);
    printf("%d\n", 1 << 2 + 1);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra par.c -o par
par.c: In function ‘main’:
par.c:5:22: warning: suggest parentheses around comparison in operand of ‘&’ [-Wparentheses]
    5 |     printf("%d\n", n & 1 == 0);
      |                      ^
par.c:7:22: warning: suggest parentheses around ‘+’ inside ‘<<’ [-Wparentheses]
    7 |     printf("%d\n", 1 << 2 + 1);
      |                      ^~
$ ./par
0        « "4 é par?" respondeu NÃO
1        « com parênteses: sim
8        « 1 << (2 + 1) = 1 << 3, e não (1 << 2) + 1 = 5
  1. Na tabela, o == (nível 7) está acima do & (nível 8). Então n & 1 == 0 é agrupado como n & (1 == 0).
  2. 1 == 0 dá 0. Sobra n & 0, que é sempre 0, para qualquer n.
  3. Com (n & 1) == 0, o último bit de 4 (que é 0) é comparado com 0: sim, 1.

Essa posição do & abaixo do == é considerada um erro histórico do C, mantido até hoje para não quebrar programas antigos. O aviso diz "sugiro parênteses em volta da comparação usada no &". O Clang do editor explica melhor: & has lower precedence than ==; == will be evaluated first ("& tem precedência menor que ==; o == será calculado primeiro").

Na linha 7, o deslocamento << (nível 5) fica abaixo do + (nível 4), e muita gente espera o contrário. O gcc avisa: "sugiro parênteses em volta do + dentro do <<". Os dois avisos vêm do -Wparentheses, ligado pelo -Wall.


6. Prévia: *p++

Esta armadilha só faz sentido de verdade depois da aula Ponteiros e Arrays, mas ela é tão famosa que vale ver agora. Um ponteiro p é uma variável que guarda a posição de outra (ele "aponta" para ela); *p quer dizer "o valor que está onde p aponta". Aqui, p começa apontando para o primeiro número da lista v:

#include <stdio.h>

int main(void) {
    int v[3] = {10, 20, 30};
    int *p = v;
    printf("%d\n", *p++);
    printf("%d\n", *p);
    (*p)++;
    printf("%d\n", *p);
    printf("%d %d %d\n", v[0], v[1], v[2]);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra ponteiro.c -o ponteiro
$ ./ponteiro
10           « *p++: mostrou o 10 e ANDOU o ponteiro para o próximo número
20           « agora p aponta para o 20
21           « (*p)++: somou 1 no NÚMERO apontado
10 21 30     « a lista mudou: o 20 virou 21
  • O ++ de depois (nível 1) é mais forte que o * (nível 2). Então *p++ é *(p++): o ++ anda o ponteiro, e o * lê o valor da posição antiga (porque o pós-incremento entrega o valor antigo).
  • Para somar 1 no valor apontado, os parênteses são obrigatórios: (*p)++.

O *p++ é muito usado de propósito, para percorrer listas e textos (aula Ponteiros e Strings). O erro é escrever *p++ querendo (*p)++, e aí nenhum compilador avisa.


7. Precedência não é ordem de cálculo

Precedência decide como a expressão é agrupada (quem é operando de quem). Ela não decide em que ordem as partes são calculadas. Em a() + b() * c(), a precedência garante que o resultado de b() é multiplicado pelo de c() antes da soma, mas o C não promete chamar a() primeiro: o compilador pode calcular as três partes na ordem que quiser.

Na maioria das vezes isso não importa. Importa quando as partes mudam alguma coisa, como em printf("%d %d\n", n++, n++). Na aula ++ e -- esse mesmo código mostrou 2 1 no gcc e 1 2 no Clang. As exceções, que têm ordem garantida (esquerda primeiro), são &&, || (o curto-circuito de Operadores Lógicos), ? : e o operador vírgula.


Formas diferentes: parênteses que não mudam nada

Parênteses "a mais" não deixam o programa mais lento nem mudam o resultado. Eles servem para quem lê. Compare:

Sem parênteses extrasCom parênteses extrasQual usar
total = preco * qtd + frete;total = (preco * qtd) + frete;Tanto faz: * antes de + todo mundo sabe.
ok = nota >= 6 && faltas < 10;ok = (nota >= 6) && (faltas < 10);Os dois são comuns; os parênteses ajudam quem está começando.
a || b && ca || (b && c)Com parênteses: o gcc até avisa sem eles.
n & 1 == 0(n & 1) == 0Com parênteses, sempre: sem eles a resposta é outra.
x << 2 + 1(x << 2) + 1 ou x << (2 + 1)Com parênteses, deixando claro qual você quer.

A regra prática: se você teve de pensar para saber a ordem, ponha parênteses. E sempre compile com -Wall, que avisa nos casos mais perigosos.


Quando dá errado

warning: suggest parentheses around comparison in operand of ‘&’

O que significa: você escreveu algo como n & 1 == 0. O == é feito antes do &. Também aparece com | e ^. No Clang: & has lower precedence than ==; == will be evaluated first.

Como consertar: (n & 1) == 0.

warning: suggest parentheses around ‘+’ inside ‘<<’

O que significa: em 1 << 2 + 1, a soma é feita antes do deslocamento. No Clang: operator '<<' has lower precedence than '+'; '+' will be evaluated first [-Wshift-op-parentheses].

Como consertar: parênteses em volta do que você quer primeiro.

warning: suggest parentheses around ‘&&’ within ‘||’

O que significa: mistura de && e || sem parênteses. O && vai primeiro (veja Operadores Lógicos).

Como consertar: escreva os parênteses do jeito que você quer.

A conta dá um número errado, sem aviso nenhum

O que significa: provavelmente faltaram parênteses numa conta: n1 + n2 / 2 no lugar de (n1 + n2) / 2, f - 32 * 5 / 9 no lugar de (f - 32) * 5 / 9, ou um cast no lugar errado, (double) (a / b). Nenhum compilador sabe o que você queria calcular.

Como consertar: leia a conta em voz alta e confira com a tabela. Teste com números fáceis, que você consegue calcular de cabeça.


Referência rápida

ExpressãoO C lê comoCuidado
a + b * ca + (b * c)Como na escola.
a - b - c(a - b) - cEsquerda para a direita.
(double) a / b((double) a) / bO cast pega só o vizinho.
a = b = ca = (b = c)Direita para a esquerda.
x *= a + bx = x * (a + b)Atribuição por último.
a < b == c < d(a < b) == (c < d)< antes de ==.
a || b && ca || (b && c)&& antes de ||.
!x == y(!x) == yEscreva x != y.
n & 1 == 0n & (1 == 0)Sempre (n & 1) == 0.
1 << a + b1 << (a + b)Contas antes do deslocamento.
*p++*(p++)Anda o ponteiro; para mudar o valor, (*p)++.

Exercícios

1. De cabeça

Quanto vale 10 - 2 * 3?

2. Da esquerda para a direita

Quanto vale 24 / 4 / 2?

3. O cast no lugar certo

Complete para a divisão ter vírgula (o resultado deve ser 2.5 com a = 5 e b = 2):

double r = a / b;

4. Com int n = 6;, quanto vale n & 1 == 0?

5. Depois de int a, b; a = b = 3 + 2;, quanto valem a e b?

6. O que *p++ faz?

Missão final: a temperatura

Missão: para converter Fahrenheit em Celsius, a fórmula é: subtrair 32, depois multiplicar por 5 e dividir por 9. O programa da Ana diz que a água ferve a 194.2 °C. Conserte a conta com parênteses para a saída ficar assim:

212.0 °F = 100.0 °C
#include <stdio.h>

int main(void) {
    double f = 212.0;
    double c = f - 32 * 5 / 9.0;
    printf("%.1f °F = %.1f °C\n", f, c);
    return 0;
}
#include <stdio.h>

int main(void) {
    double f = 212.0;
    double c = (f - 32) * 5 / 9.0;
    printf("%.1f °F = %.1f °C\n", f, c);
    return 0;
}

Repare no 9.0: com 9 o resultado seria o mesmo aqui, porque f já é double e contamina a conta toda. Troque f por 32.0 e por 98.6 para conferir outros valores (0 °C e 37 °C).


Resumo

  • Precedência: quem é agrupado primeiro (* antes de +). Associatividade: o desempate no mesmo nível (quase sempre da esquerda para a direita).
  • Ordem geral: de um operando (! - ++ (tipo) *p) → * / % → + - → << >> → comparações → & ^ | → && → || → ?: → atribuições → vírgula.
  • Da direita para a esquerda: os de um operando, o ?: e as atribuições (a = b = c = 0).
  • Parênteses mandam em tudo e não custam nada. Use sempre que precisou pensar.
  • Armadilhas: n1 + n2 / 2, (double) (a / b), n & 1 == 0, 1 << 2 + 1, *p++ × (*p)++. O -Wall avisa as de bits.
  • Precedência não define a ordem de cálculo das partes: só &&, ||, ?: e a vírgula garantem a esquerda primeiro.