Há algum tempo, me deparei com a ideia de como uma construção C, como (expr0, expr1, expr2)
, é avaliada (veja "O que o operador vírgula , faz?" para mais contexto).
Comecei a experimentar isso, especialmente dentro de macros do tipo função, e recentemente encontrei um código que é rejeitado por alguns compiladores, enquanto aceito por outros. Parece similar ao seguinte trecho:
#include <stdio.h>
int main(void)
{
int arr[] = {0};
(1, arr[0]) = 30; // <--- potentially (in)valid code
printf("%d\n", arr[0]);
return 0;
}
Como você pode ver, para que isso funcione, (1, arr[0])
deve ser avaliado para lvalue arr[0]
, caso contrário a atribuição NÃO seria possível. No entanto, não tenho certeza se esse comportamento é válido ou não. Ele "faz sentido" e eu encontrei um uso para ele, mas também vejo por que os desenvolvedores do compilador o rejeitariam.
O código acima é rejeitado pelo gcc, clang e msvc (observe que o msvc é principalmente um compilador C++, enquanto o gcc e o clang são front-ends C):
$ gcc main.c
main.c: In function ‘main’:
main.c:6:21: error: lvalue required as left operand of assignment
6 | (1, arr[0]) = 30;
| ^
$ clang main.c -Wno-unused-value
main.c:6:14: error: expression is not assignable
6 | (1, arr[0]) = 30;
| ~~~~~~~~~~~ ^
1 error generated.
$ cl main.c /nologo
main.c
main.c(6): error C2106: '=': left operand must be l-value
Para efeito de comparação, g++, clang++ e tcc funcionam bem com o código mencionado (observe que tcc é um compilador C, enquanto g++ e clang++ são front-ends C++):
$ tcc main.c && ./a.out
30
$ g++ main.c && ./a.out
30
$ clang++ main.c -Wno-unused-value -Wno-deprecated && ./out
30
Também tentei com algumas opções de comando diferentes, como definir explicitamente o msvc para ser executado nos modos /std:c++latest
e /std:c99
, ou definir algo diferente -std
para gcc/clang/g++/clang++, mas isso não mudou nada.
No começo, pensei que fosse um bug dentro do tcc, já que é o único compilador C que não rejeita o código "defeituoso", mas então verifiquei os front-ends C++ e não tenho mais tanta certeza sobre isso. Especialmente porque o msvc o rejeita, diferentemente do g++/clang++.
- O código que acabei de apresentar é um C válido, ou C++, ou ambos/nenhum?
- Os padrões C/C++ descrevem o que deve acontecer aqui?
- Quais compiladores estão certos/errados aqui? (tcc e msvc parecem muito estranhos)
Para referência, estou no Linux x86_64, usando gcc/g++ 14.2.1, clang 18.1.8, msvc 19.40.33811 (executando através do wine) e tcc 0.9.28rc (mob@08a4c52d).
Em C, o código não é válido. Como diz N3096 6.5.17 ,
A nota 129 diz:
Portanto, é um "não-lvalue" (ou seja, r-value) e não pode ser usado como um l-value.
Para C++, por outro lado, por
[expr.comma]
Como é a mesma categoria de valor que o operando da direita, seria um l-valor e seria válido.
Note que seu código compila bem no MSVC como um arquivo C++. O MSVC, ou mais especificamente o CL, tratará arquivos .c como C, e arquivos .cpp como C++ por padrão. Veja aqui onde o compilador esquerdo é o MSVC forçado a compilar como código C, e o direito é o MSVC compilando como C++
Para C: de acordo com N3096 (PDF) , § 6.3.2.1 parágrafo 2 (página 48):
No seu exemplo,
arr[0]
é um operando do,
operador, então deve perder seu valor l; portanto, tcc está errado, e os outros compiladores C estão corretos.Seu parênteses está fora do lugar.
(1,arr)[0]
Deve funcionar.