Qual é o resultado de + = em C e C ++?

93

Eu tenho o seguinte código:

#include <stdio.h>
int main(int argc, char **argv) {
    int i = 0;
    (i+=10)+=10;
    printf("i = %d\n", i);
    return 0;
}

Se eu tentar compilá-lo como uma fonte C usando gcc, recebo um erro:

error: lvalue required as left operand of assignment

Mas se eu compilá-lo como uma fonte C ++ usando g ++, não recebo nenhum erro e quando executo o executável:

i = 20

Por que o comportamento diferente?

Svetlin Mladenov
fonte
85
Linguagem diferente, regras de sintaxe diferentes ?. Pessoalmente, eu rejeitaria esse código na revisão do código.
Máx.
7
Evite código como este imo ... Confuso para todos.
allaire de
1
Sem dúvida, o código não está limpo e deve ser evitado no desenvolvimento "real". Mesmo assim, observo o mesmo comportamento e gostaria de saber as razões para isso.
ulidtko
9
Este NÃO é um trecho de código de um software real. Esta é apenas uma torção que encontrei acidentalmente.
Svetlin Mladenov
3
@JohnDibling Eu acho que o upvote é especificamente o (i + = 10) + = 10 eu não sei em qual linguagem o código é legítimo e o fato de ele dizer que o C ++ realmente o compila me intriga.
Tony318

Respostas:

133

A semântica dos operadores de atribuição compostos é diferente em C e C ++:

Padrão C99, 6.5.16, parte 3:

Um operador de atribuição armazena um valor no objeto designado pelo operando esquerdo. Uma expressão de atribuição tem o valor do operando esquerdo após a atribuição, mas não é um lvalue.

Em C ++ 5.17.1:

O operador de atribuição (=) e os operadores de atribuição compostos agrupam-se da direita para a esquerda. Todos requerem um lvalue modi fi cável como seu operando esquerdo e retornam um lvalue com o tipo e valor do operando esquerdo após a atribuição ter ocorrido.

EDITAR: O comportamento de (i+=10)+=10em C ++ é indefinido em C ++ 98, mas bem definido em C ++ 11. Veja esta resposta à pergunta da NPE para as partes relevantes dos padrões.

dasblinkenlight
fonte
Corrigir. Um retorna o valor do resultado e o outro retorna a variável (endereço)
texasbruce
7
Importante : Observe que (i+=10)+=10é um comportamento indefinido em C ++, consulte a resposta @aix.
David Rodríguez - dribeas
@ DavidRodríguez-dribeas Você quis dizer não especificado , não indefinido , certo?
dasblinkenlight
4
@dasblinkenlight: Não, ele quis dizer indefinido . Em C ++ 03 e anteriores, a modificação do resultado lvalue de uma expressão se comporta de maneira imprevisível em todos os compiladores devido à falta de um ponto de sequência intermediário. Se não fosse especificado , ele se comportaria de maneira previsível, mas diferente em diferentes compiladores .
Justin ᚅᚔᚈᚄᚒᚔ
2
Teria sido útil em uma configuração como int f(int &y); f(x += 10);- passar uma referência à variável modificada em uma função.
Phil Miller de
51

Além de ser um código C inválido, a linha

(i+=10)+=10;

resultaria em um comportamento indefinido em C e C ++ 03 porque modificaria i duas vezes entre os pontos de sequência.

Por que é permitido compilar em C ++:

[C ++ N3242 5.17.1] O operador de atribuição (=) e os operadores de atribuição compostos agrupam-se da direita para a esquerda. Todos requerem um lvalue modificável como seu operando esquerdo e retornam um lvalue referindo-se ao operando esquerdo.

O mesmo parágrafo diz que

Em todos os casos, a atribuição é sequenciada após o cálculo do valor dos operandos direito e esquerdo e antes do cálculo do valor da expressão de atribuição.

Isso sugere que no C ++ 11, a expressão não tem mais comportamento indefinido.

NPE
fonte
3
Bem, certamente é UB por causa dos pontos de sequência. Também é um código inválido em C (mas não em C ++), mas não está relacionado aos pontos de sequência e deve ser capturado pelo compilador.
Konrad Rudolph
2
@KonradRudolph: não, o compilador não é obrigado a capturar um comportamento indefinido, ao contrário do código malformado. Discordamos da parte "deve ser capturado pelo compilador".
2
Os pontos de sequência não existem no C ++ 11, então o motivo real para o UB é que há duas modificações ique não são sequenciadas.
Mankarse
4
É falso que esse seja um comportamento indefinido. Se a atribuição não era uma sequência antes do cálculo do valor da expressão de atribuição, entãoi = j+=1 o resultado seria um valor indeterminado. Do mesmo parágrafo, você cita "Em todos os casos, a atribuição é sequenciada após o cálculo do valor dos operandos direito e esquerdo e antes do cálculo do valor da expressão de atribuição." Portanto, (i+=10)+=10está bem definido para fazer i += 10; i += 10;. Por outro lado, (i+=10)+=(i+=10)é UB.
bames53
2
Como vi que havia alguma discordância sobre isso entre os comentadores, postei isso como uma pergunta separada: stackoverflow.com/questions/10655290/…
NPE