Diferenças entre C e C++: precedência!

Caloni, 2006-05-03 computer ccpp blog

Num 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;
}

// Comments

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" =).

[visual_studio_2005] [erros_esquisitos]