O que significa i = (i, ++ i, 1) + 1; Faz?

174

Depois de ler esta resposta sobre comportamento indefinido e pontos de sequência, escrevi um pequeno programa:

#include <stdio.h>

int main(void) {
  int i = 5;
  i = (i, ++i, 1) + 1;
  printf("%d\n", i);
  return 0;
}

A saída é 2. Oh Deus, eu não vi o decréscimo chegando! O que esta acontecendo aqui?

Além disso, ao compilar o código acima, recebi um aviso dizendo:

px.c: 5: 8: aviso: o operando esquerdo da expressão de vírgula não tem efeito

  [-Wunused-value]   i = (i, ++i, 1) + 1;
                        ^

Por quê? Mas provavelmente será respondido automaticamente pela resposta da minha primeira pergunta.

gsamaras
fonte
289
Não faça coisas estranhas, você não terá amigos :(
Maroun
9
A mensagem de aviso é a resposta para sua primeira pergunta.
Yu Hao
2
@gsamaras: não. o valor resultante é descartado, não a modificação. a resposta real: o operador de vírgula cria um ponto de sequência.
Karoly Horvath
3
@gsamaras Você não deve se importar quando tiver pontuação positiva e mais ainda com mais de 10 perguntas.
LyingOnTheSky
9
Nota: Um compilador otimizador pode ser simplesprintf("2\n");
chux - Reinstala Monica 3/15

Respostas:

256

Na expressão (i, ++i, 1), a vírgula usada é o operador de vírgula

o operador de vírgula (representado pelo token ,) é um operador binário que avalia seu primeiro operando e descarta o resultado, avalia o segundo operando e retorna esse valor (e tipo).

Como descarta seu primeiro operando, geralmente é útil apenas quando o primeiro operando tem efeitos colaterais desejáveis . Se o efeito colateral no primeiro operando não ocorrer, o compilador poderá gerar um aviso sobre a expressão sem efeito.

Portanto, na expressão acima, a extremidade esquerda iserá avaliada e seu valor será descartado. Em seguida ++i, será avaliado e aumentará i1 e novamente o valor da expressão ++iserá descartado, mas o efeito colateral para ié permanente . Então 1será avaliado e o valor da expressão será 1.

É equivalente a

i;          // Evaluate i and discard its value. This has no effect.
++i;        // Evaluate i and increment it by 1 and discard the value of expression ++i
i = 1 + 1;  

Observe que a expressão acima é perfeitamente válida e não invoca um comportamento indefinido porque há um ponto de sequência entre a avaliação dos operandos esquerdo e direito do operador de vírgula.

haccks
fonte
1
embora a expressão final seja válida, a segunda expressão ++ i não é um comportamento indefinido? é avaliado e o valor da variável não inicializada é pré-incrementado, o que não é correto? Ou eu estou esquecendo de alguma coisa?
Koushik Shetty
2
@Koushik; ié inicializado com 5. Veja a declaração int i = 5;.
haccks
1
Oh culpa minha. Desculpe, honestamente, não vejo isso.
Koushik Shetty
Há um erro aqui: ++ eu incrementarei, então o avaliarei, enquanto o i ++ avaliará, em seguida, incrementá-lo.
Quentin Hayot
1
@QuentinHayot; O que? Quaisquer efeitos colaterais ocorrem após a avaliação da expressão. No caso de ++i, essa expressão será avaliada, iserá incrementada e esse valor incrementado será o valor da expressão. No caso de i++, essa expressão será avaliada, o valor antigo de iserá o valor da expressão, iserá incrementado a qualquer momento entre o ponto de sequência anterior e o próximo da expressão.
haccks
62

Citando C11, capítulo 6.5.17, operador de vírgula

O operando esquerdo de um operador de vírgula é avaliado como uma expressão nula; existe um ponto de sequência entre sua avaliação e a do operando certo. Então o operando certo é avaliado; o resultado tem seu tipo e valor.

Então, no seu caso,

(i, ++i, 1)

é avaliado como

  1. i, é avaliado como uma expressão nula, valor descartado
  2. ++i, é avaliado como uma expressão nula, valor descartado
  3. finalmente, 1valor retornado.

Então, a declaração final parece

i = 1 + 1;

e ichega a 2. Acho que isso responde às duas perguntas,

  • Como iobtém um valor 2?
  • Por que existe uma mensagem de aviso?

Nota: Fwiw, como existe um ponto sequência presente após a avaliação do operando mão esquerda, uma expressão como (i, ++i, 1)não invocação UB, como uma pode geralmente pensar por engano.

Sourav Ghosh
fonte
+1 Sourav, pois isso explica por que a inicialização de iclaramente não tem efeito! No entanto, não acho que fosse tão óbvio para um cara que não conhece o operador de vírgula (e não sabia como procurar ajuda, além de fazer uma pergunta). Pena que eu tenho tantos votos negativos! Vou verificar as outras respostas e depois decidir qual aceitar. Obrigado! Resposta superior agradável btw.
gsamaras
Eu sinto que tenho que explicar por que aceitei a resposta de hackers. Eu estava pronto para aceitar a sua, pois ela realmente responde às minhas duas perguntas. No entanto, se você verificar os comentários da minha pergunta, verá que algumas pessoas não conseguem ver à primeira vista por que isso não invoca o UB. As respostas dos hacks fornecem algumas informações relevantes. Obviamente, tenho a resposta sobre o UB vinculada na minha pergunta, mas algumas pessoas podem sentir falta disso. Espero que você concorde com minha decisão, se não me avise. :)
gsamaras
30
i = (i, ++i, 1) + 1;

Vamos analisá-lo passo a passo.

(i,   // is evaluated but ignored, there are other expressions after comma
++i,  // i is updated but the resulting value is ignored too
1)    // this value is finally used
+ 1   // 1 is added to the previous value 1

Então, obtemos 2. E a tarefa final agora:

i = 2;

O que quer que estivesse em i antes de ser substituído agora.

dlask
fonte
Seria bom afirmar que isso acontece por causa do operador de vírgula. +1 para a análise passo a passo! Resposta superior agradável btw.
gsamaras
Sinto muito por uma explicação insuficiente, só tenho uma nota lá ( ... mas ignorada, tem ... ). Eu queria explicar principalmente por que ++iisso não contribui para o resultado.
precisa
agora meu para loops vai ser sempre assimint i = 0; for( ;(++i, i<max); )
CoffeDeveloper
19

O resultado de

(i, ++i, 1)

é

1

Para

(i,++i,1) 

a avaliação ocorre de tal forma que o ,operador descarta o valor avaliado e retém apenas o valor mais correto que é1

assim

i = 1 + 1 = 2
Gopi
fonte
1
Sim, eu pensei nisso também, mas não sei por que!
gsamaras
@gsamaras porque o operador vírgula avalia o termo anterior, mas descarta-lo (ou seja, não usá-lo para tarefas ou semelhantes)
Marco A.
14

Você encontrará boas leituras na página wiki do operador Vírgula .

Basicamente,

... avalia seu primeiro operando e descarta o resultado, avalia o segundo operando e retorna esse valor (e tipo).

Isso significa que

(i, i++, 1)

por sua vez, avaliará i, descartará o resultado, avaliará i++, descartará o resultado e, em seguida, avaliará e retornará 1.

Tomas Aschan
fonte
O_O inferno, faz que a sintaxe é válida em C ++, eu lembro que eu tinha poucos lugares onde eu precisava que a sintaxe (basicamente escrevi: (void)exp; a= exp2;enquanto eu só precisava a = exp, exp2;)
CoffeDeveloper
13

Você precisa saber o que o operador de vírgula está fazendo aqui:

Sua expressão:

(i, ++i, 1)

A primeira expressão i,, é avaliada, a segunda expressão ++i,, é avaliada e a terceira expressão 1, é retornada para toda a expressão.

Assim, o resultado é: i = 1 + 1.

Para sua pergunta de bônus, como você vê, a primeira expressão inão tem efeito algum, então o compilador reclama.

songyuanyao
fonte
5

A vírgula tem uma precedência "inversa". É isso que você obtém de livros antigos e manuais C da IBM (anos 70/80). Portanto, o último 'comando' é o que é usado na expressão pai.

No C moderno, seu uso é estranho, mas é muito interessante no antigo C (ANSI):

do { 
    /* bla bla bla, consider conditional flow with several continue's */
} while ( prepAnything(), doSomethingElse(), logic_operation);

Embora todas as operações (funções) sejam chamadas da esquerda para a direita, apenas a última expressão será usada como resultado para 'while' condicional. Isso impede o manuseio de 'goto's' para manter um bloco de comandos exclusivo a ser executado antes da verificação da condição.

EDIT: Isso evita também uma chamada para uma função de manipulação que pode cuidar de toda a lógica nos operandos da esquerda e, portanto, retornar o resultado lógico. Lembre-se de que não tínhamos função embutida no passado de C. Portanto, isso poderia evitar uma sobrecarga de chamada.

Luciano
fonte
Luciano, você também tem um link para esta resposta: stackoverflow.com/questions/17902992/… .
gsamaras
No início dos anos 90, antes das funções em linha, eu o usei bastante para otimizar e manter o código organizado.
Luciano