Por que c = ++ (a + b) dá erro de compilação?

111

Depois de pesquisar, li que o operador de incremento requer que o operando tenha um objeto de dados modificável: https://en.wikipedia.org/wiki/Increment_and_decrement_operators .

A partir disso, acho que dá erro de compilação porque (a+b)é um inteiro temporário e, portanto, não é modificável.

Este entendimento está correto? Esta foi a primeira vez que tentei pesquisar um problema, então se havia algo que eu deveria ter procurado, por favor, avise.

dng
fonte
35
Isso não é ruim em termos de pesquisa. Você está no caminho certo.
StoryTeller - Unslander Monica
35
O que você espera que a expressão faça?
qrdl
4
de acordo com o padrão C11 6.5.3.1: O operando do operador de incremento ou decremento de prefixo deve ter o tipo real ou ponteiro atômico, qualificado ou não qualificado e deve ser um valor modificável
Christian Gibbons
10
Como você gostaria que 1 fosse distribuído entre a e b? "Os índices de matriz devem começar em 0 ou 1? Meu compromisso de 0,5 foi rejeitado sem, pensei, a devida consideração." - Stan Kelly-Bootle
Andrew Morton
5
Acho que uma pergunta que se segue é por que você faria isso quando c = a + b + 1torna sua intenção mais clara e também é mais fácil de digitar. Os operadores de incremento / decremento fazem duas coisas 1. eles e seus argumentos formam uma expressão (que pode ser usada, por exemplo, em um loop for), 2. eles modificam o argumento. Em seu exemplo, você está usando a propriedade 1. mas não a propriedade 2., uma vez que você descarta o argumento modificado. Se você não precisa da propriedade 2. e apenas deseja a expressão, pode simplesmente escrever uma expressão, por exemplo, x + 1 em vez de x ++.
Trevor

Respostas:

117

É apenas uma regra, só isso, e possivelmente existe para (1) tornar mais fácil escrever compiladores C e (2) ninguém convenceu o comitê de padrões C a relaxá-lo.

Falando informalmente, você só pode escrever ++foose fooaparecer no lado esquerdo de uma expressão de atribuição como foo = bar. Já que você não pode escrever a + b = bar, você também não pode escrever ++(a + b).

Não há nenhuma razão real para a + bnão produzir um temporário no qual ++possa operar, e o resultado disso é o valor da expressão ++(a + b).

Bate-Seba
fonte
4
Acho que o ponto (1) acerta na mosca. Apenas olhar para as regras para materialização temporária em C ++ pode virar o estômago (mas é poderoso, entretanto, tenho que dizer isso).
StoryTeller - Unslander Monica
4
@StoryTeller: Na verdade, ao contrário de nossa amada linguagem C ++, C ainda compila em assembly de forma relativamente trivial.
Bathsheba de
29
Aqui está um verdadeiro motivo, IMHO: seria uma confusão terrível se ++às vezes tivesse o efeito colateral de modificar algo e às vezes simplesmente não o fizesse.
aschepler de
5
@dng: É verdade; é por isso que os termos lvalue e rvalue foram introduzidos, embora as coisas sejam mais complicadas do que isso hoje em dia (particularmente em C ++). Por exemplo, uma constante nunca pode ser um lvalue: algo como 5 = a não faz sentido.
Bate
6
@Bathsheba Isso explica por que 5 ++ também causa um erro de compilação
dng
40

O padrão C11 afirma na seção 6.5.3.1

O operando do operador de incremento ou decremento de prefixo deve ter tipo real ou ponteiro atômico, qualificado ou não qualificado, e deve ser um lvalue modificável

E "valor modificável" é descrito na seção 6.3.2.1 subseção 1

Um lvalue é uma expressão (com um tipo de objeto diferente de void) que potencialmente designa um objeto; se um lvalue não designa um objeto quando é avaliado, o comportamento é indefinido. Quando se diz que um objeto tem um tipo específico, o tipo é especificado pelo lvalue usado para designar o objeto. Um lvalue modificável é um lvalue que não tem um tipo de array, não tem um tipo incompleto, não tem um tipo qualificado const e, se for uma estrutura ou união, não tem nenhum membro (incluindo, recursivamente, nenhum membro ou elemento de todos os agregados ou uniões contidos) com um tipo qualificado const.

Portanto, (a+b)não é um lvalue modificável e, portanto, não é elegível para o operador de incremento de prefixo.

Christian Gibbons
fonte
1
Sua conclusão dessas definições está faltando ... Você quer dizer que (a + b) não designa potencialmente um objeto, mas esses parágrafos não permitem isso.
hkBst
21

Você está certo. o ++tenta atribuir o novo valor à variável original. Então ++apegará o valor de a, adiciona 1a ele e, em seguida, atribui de volta a a. Como, como você disse, (a + b) é um valor temporário e não uma variável com endereço de memória atribuído, a atribuição não pode ser realizada.

Roee Gavirel
fonte
12

Acho que você respondeu principalmente à sua própria pergunta. Eu poderia fazer uma pequena mudança em seu fraseado e substituir "variável temporária" por "rvalue", como C. Gibbons mencionou.

Os termos variável, argumento, variável temporária e assim por diante ficarão mais claros conforme você aprende sobre o modelo de memória do C (parece uma boa visão geral: https://www.geeksforgeeks.org/memory-layout-of-c-program/ )

O termo "rvalue" pode parecer opaco quando você está apenas começando, então espero que o seguinte ajude a desenvolver uma intuição sobre ele.

Lvalue / rvalue estão falando sobre os diferentes lados de um sinal de igual (operador de atribuição): lvalue = lado esquerdo (L minúsculo, não um "um") rvalue = lado direito

Aprender um pouco sobre como C usa a memória (e os registros) será útil para ver por que a distinção é importante. Em grandes pinceladas , o compilador cria uma lista de instruções em linguagem de máquina que calculam o resultado de uma expressão (o rvalue) e, em seguida, coloca esse resultado em algum lugar (o lvalue). Imagine um compilador lidando com o seguinte fragmento de código:

x = y * 3

No pseudocódigo de montagem, pode ser parecido com este exemplo de brinquedo:

load register A with the value at memory address y
load register B with a value of 3
multiply register A and B, saving the result in A
write register A to memory address x

O operador ++ (e sua contraparte) precisa de um "algum lugar" para modificar, essencialmente qualquer coisa que possa funcionar como um lvalue.

Entender o modelo de memória C será útil porque você terá uma ideia melhor em sua cabeça sobre como os argumentos são passados ​​para funções e (eventualmente) como trabalhar com alocação de memória dinâmica, como a função malloc (). Por razões semelhantes, você pode estudar alguma programação em assembly simples em algum ponto para ter uma ideia melhor do que o compilador está fazendo. Além disso, se você estiver usando o gcc , a opção -S "Parar após o estágio de compilação adequada; não montar." pode ser interessante (embora eu recomende tentar em um pequeno fragmento de código).

Apenas como um aparte: a instrução ++ existe desde 1969 (embora tenha começado no predecessor do C, B):

A observação (de Ken Thompson) (era) de que a tradução de ++ x era menor do que x = x + 1. "

Seguir essa referência da Wikipedia levará você a um artigo interessante de Dennis Ritchie (o "R" em "K&R C") sobre a história da linguagem C, linkado aqui para sua conveniência: http://www.bell-labs.com/ usr / dmr / www / chist.html onde você pode procurar por "++".

jgreve
fonte
6

A razão é que o padrão requer que o operando seja um lvalue. A expressão (a+b)não é um lvalue, portanto, não é permitido aplicar o operador de incremento.

Agora, pode-se dizer "OK, isso é de fato a razão, mas não há realmente nenhuma * real * razão que não seja" , mas infelizmente a redação específica de como o operador trabalha factualmente não exigem que seja o caso.

A expressão ++ E é equivalente a (E + = 1).

Obviamente, você não pode escrever E += 1se Enão for um lvalue. O que é uma pena, porque poderíamos muito bem ter dito: "incrementos E por um" e pronto. Nesse caso, aplicar o operador a um não-valor seria (em princípio) perfeitamente possível, à custa de tornar o compilador um pouco mais complexo.

Agora, a definição poderia ser reformulada trivialmente (acho que não é nem originalmente C, mas uma herança de B), mas fazer isso mudaria fundamentalmente a linguagem para algo que não é mais compatível com suas versões anteriores. Como o benefício possível é pequeno, mas as implicações possíveis são enormes, isso nunca aconteceu e provavelmente nunca vai acontecer.

Se você considerar C ++ além de C (a questão está marcada como C, mas houve uma discussão sobre sobrecargas de operadores), a história se torna ainda mais complicada. Em C, é difícil imaginar que esse seja o caso, mas em C ++ o resultado de (a+b)pode muito bem ser algo que você não pode incrementar de forma alguma, ou incrementar pode ter efeitos colaterais consideráveis ​​(não apenas adicionar 1). O compilador deve ser capaz de lidar com isso e diagnosticar casos problemáticos à medida que ocorrem. Em um lvalue, isso ainda é meio trivial para verificar. Não é assim para qualquer tipo de expressão aleatória dentro de um parêntese que você joga na coitada.
Esta não é uma razão real pela qual não poderia ser feito, mas com certeza empresta como uma explicação por que as pessoas que implementaram isso não estão exatamente em êxtase em adicionar tal recurso que promete muito pouco benefício para muito poucas pessoas.

Damon
fonte
3

(a + b) avalia para um rvalue, que não pode ser incrementado.

Casper B. Hansen
fonte
3

++ tenta dar o valor à variável original e, como (a + b) é um valor temporário, não pode realizar a operação. E são basicamente regras das convenções de programação C para facilitar a programação. É isso aí.

Babu Chandermani
fonte
2

Quando a expressão ++ (a + b) é executada, por exemplo:

int a, b;
a = 10;
b = 20;
/* NOTE :
 //step 1: expression need to solve first to perform ++ operation over operand
   ++ ( exp );
// in your case 
   ++ ( 10 + 20 );
// step 2: result of that inc by one 
   ++ ( 30 );
// here, you're applying ++ operator over constant value and it's invalid use of ++ operator 
*/
++(a+b);
Jeet Parikh
fonte