Diferenças entre C e C++: precedência!
Caloni, 2006-05-03 computer ccpp blogNum belo dia, meu amigo Kabloc escreveu uma pequena e singela função para imprimir a tabuada de 1 a 10. O código era mais ou menos assim:
#include <stdio.h>
int main()
{
int f1,f2,s=0;
for(f1=1;(f1==11&&s!=5)?s=5,f1=0,putchar(10):(f1<=10)?f1=f1:f1=12,f1<=11;f1++)
for(f2=1+s;f2<=5+s;f2++)printf("%dx%d=%d%c",f2,f1,f1*f2,(f2==5+s)?10:9);
return 0;
}
Tirando o fato que o fonte é candidato forte ao The International Obfuscated C Code Contest, o pessoal do linux disse para ele que o dito cujo não compilava no GCC, e que em algum lugar naquelas 4 linhas dentro do main alguma coisa não era padrão da linguagem.
Procurado para resolver o problema, dado minha inata vocação para assuntos aleatórios, resolvi resolver as coisas no bom e velho Visual Studio 2003. Afinal de contas, ele compila fontes em C, também. Basta alterarmos a extensão de um fonte de .cpp para .c. E foi o que eu fiz. Foi assim que encontrei o seguinte erro de compilação, que não ocorre ao compilar o mesmo fonte em um arquivo C++:
error C2106: '=' : left operand must be l-value
Isso acontece na linha 6, a linha do primeiro for. E eis um fonte em C que compila em C++, mas dá erro de falta de l-value quando compilado como C puro.
Pensando um pouco sobre o problema, mais de maneira intuitiva do que propriamente destrinchando e analisando a linha de código, imaginei que poderia haver alguma diferença minúscula entre a tabela de precedência de operadores dessas duas linguagens. Tendo isso em mente, saquei os padrões das respectivas linguagens da minha pasta de e-books e, vejam só:
| C C++ | | ------------------- | | [R2L] ?: = [R2L] | | [R2L] = ?: [L2R] | | [L2R] , , [L2R] |
Nota-se que no final da tabela existe uma inversão na precedência entre ooperador condicional ternário e os operadores de atribuição; isso semcontar que a ordem de avaliação também muda. Em C, o operador ternário éavaliado da direita pra esquerda (R2L, Right to Left), enquanto em C++ daesquerda pra direita (L2R), aliás como a maioria na tabela completa. Isso vai fazer com que, na linha 6, a mesma expressão tenha diferentes interpretações para cada linguagem.
Para entender passo a passo o problema, vamos destrinchar a segunda parte do laço for da linha 6:
( f1 == 11 && s != 5 ) ?
s = 5, f1 = 0, putchar(10)
:
( f1 <= 10 ) ?
f1 = f1
:
f1 = 12, f1 <= 11;
Nós temos dois operadores ternários aninhados. De acordo com o padrão C++, os operadores ternário têm precedência menor que os operadores de atribuição, e são avaliados da esquerda pra direita. Ou seja, primeiro são avaliadas todas as atribuições da expressão antes de qualquer operador ternário. Depois disso, o primeiro operador ternário é avaliado, seguido do segundo:
// C++ understanding
(
( f1 == 11 && s != 5 ) ?
(s = 5), (f1 = 0), putchar(10)
:
( f1 <= 10 )
) ?
(f1 = f1)
:
(f1 = 12), f1 <= 11;
2026-03-22 Antigamente meus códigos possuíam cores e havia uma separaçãoaqui entre vermelho, verde e azul. Eu removi o parágrafo descrevendo aordem de precedência por cores e reformatei o código para explicitar pelosparênteses a ordem a ser seguida.
Agora analisemos em C. Nessa linguagem, ao contrário de C++, os operadores ternários possuem precedência maior que os operadores de atribuição, e são avaliados da direita para a esquerda. Isso quer dizer que primeiro o último operador ternário é avaliado, ignorando a atribuição mais à direita, e depois o primeiro operador ternário é avaliado. Só depois disso que a atribuição mais à direita é feita:
// C understanding
(
( f1 == 11 && s != 5 ) ?
s = 5, f1 = 0, putchar(10)
:
(
( f1 <= 10 ) ?
f1 = f1
:
f1
)
) = 12, f1 <= 11;
Tudo isso para compreendermos que a atribuição do valor 12 será feita em cima do resultado do primeiro operador ternário, cujos valores possíveis poderão ser ou o retorno de putchar ou f1. Lembre-se de que o operador vírgula serve para encadear expressões, avaliá-las, e retornar o valor da expressão mais à direita:
// atribui 5 a s, 0 a f1 e retorna o valor da chamada a putchar. s = 5, f1 = 0, putchar(10) // em ambos os casos o valor retornado é a variável f1. f1 = f1 : f1
Ora, a variável f1 é do tipo int. E putchar também retorna um int. Isso não quebra nenhuma regra de tipagem. Porém, quebra a regra de ouro da atribuição, que possui um lugar de destaque nas escrituras sagradas: "colocarás um lvalue ao lado direito de uma atribuição; não serás um 10, muito menos uma constante, mas uma variável; e assim foi feito!".
// certo; nada a declarar (exceto o f1) f1 = 12; // opa! que que é isso? putchar não retorna variável, e sim uma constante putchar(10) = 12;
Isso termina a longa explicação do porquê aquele errinho lá no começo do artigo ocorreu apenas na linguagem C. Esse é um exemplo das pequenas diferenças entre essas duas linguagens. Provavelmente se você usa parênteses feito um doente mental não encontrará problemas desse tipo em seus códigos. Lembrando que isso não desmerece o operador ternário, tão útil e econômica expressão dessas duas linguagens que primam pela elegância e exatidão.
// Update (2026-01-25): Eu pedi ao Copilot para consertar o código em C
// e ele gerou esse código formatado que conserta o programa e remove
// toda a diversão.
#include <stdio.h>
int main()
{
int f1, f2, s = 0;
for (; s < 2; s++)
{
for (f1 = 1; f1 <= 10; f1++)
{
for (f2 = 1 + s * 5; f2 <= 5 + s * 5; f2++)
{
printf("%dx%d=%d%c", f2, f1, f1 * f2, (f2 == 5 + s * 5) ? 10 : 9);
}
}
putchar(10);
}
return 0;
}
2006-05-03 Daniel Quadros:
Felizmente eu costumo usar parenteses ao invés de depender da precedênciados operadores. Por exemplo, eu escreveria ((f1==11)&&(s!=5)). Além disso,já passei da fase de economizar linhas e colunas no fonte. A minha versãopara a tabuada (não reproduzindo a esquisitice do fator 0 na segunda metade)seria:
#include < stdio.h >
int main()
{
int f1,f2,s=0;
for (s = 0; s < 10; s += 5)
{
for (f1 = 1; f1 <= 10; f1++)
{
for (f2 = 1+s; f2 <= (5+s); f2++)
printf (\"%dx%d=%d%c\", f2, f1, f1*f2, (f2==5+s) ? \'\\n\': \'\\t\');
}
if (s != 5)
putchar(\'\\n\');
}
return 0;
}
Abraços,
Daniel Quadros
2006-05-05 Kabloc:
Quando eu estava fazendo este código eu estava brincando com o pessoal da faculdade:
É que o professor passou a difícil tarefa "Escrever um três código que imprime a tabuada do '7', um com 'while', um com 'do while' e outro com 'for'", aí eu queria imprimir todas as tabuadas com um fonte do mesmo tamanho, e o mais confuso possível.
Tudo não passou de uma brincadeira...
2006-05-05 Caloni:
Pois é, deu pra perceber que seu código não era de "produção" =).