Co znaczy i = (i, ++ i, 1) + 1; robić?

174

Po przeczytaniu tej odpowiedzi o niezdefiniowanych zachowaniach i punktach sekwencji napisałem mały program:

#include <stdio.h>

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

Wynik jest 2. O Boże, nie widziałem nadchodzącego dekretu! Co tu się dzieje?

Ponadto podczas kompilacji powyższego kodu otrzymałem ostrzeżenie o treści:

px.c: 5: 8: ostrzeżenie: lewostronny operand wyrażenia przecinka nie ma żadnego efektu

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

Czemu? Ale prawdopodobnie automatycznie odpowie na to odpowiedź na moje pierwsze pytanie.

gsamaras
źródło
289
Nie rób dziwnych rzeczy, nie będziesz mieć przyjaciół :(
Maroun
9
Komunikat ostrzegawczy to odpowiedź na Twoje pierwsze pytanie.
Yu Hao
2
@gsamaras: nope. wynikowa wartość jest odrzucana, a nie modyfikacja. prawdziwa odpowiedź: operator przecinka tworzy punkt sekwencji.
Karoly Horvath
3
@gsamaras Nie powinno Cię to obchodzić, kiedy masz pozytywny wynik, a nawet więcej z pytaniami 10+.
LyingOnTheSky
9
Uwaga: optymalizujący kompilator może w prosty sposób zrobićprintf("2\n");
chux - Przywrócić Monikę

Odpowiedzi:

256

W wyrażeniu (i, ++i, 1)użytym przecinkiem jest operator przecinka

operator przecinka (reprezentowany przez token ,) jest operatorem binarnym, który oblicza swój pierwszy operand i odrzuca wynik, a następnie oblicza drugi operand i zwraca tę wartość (i typ).

Ponieważ odrzuca swój pierwszy operand, jest generalnie użyteczny tylko wtedy, gdy pierwszy operand ma pożądane skutki uboczne . Jeśli efekt uboczny pierwszego operandu nie występuje, kompilator może wygenerować ostrzeżenie o wyrażeniu bez efektu.

Tak więc w powyższym wyrażeniu izostanie obliczony lewy element, a jego wartość zostanie odrzucona. Następnie ++izostanie oszacowany i zwiększy się io 1 i ponownie wartość wyrażenia ++izostanie odrzucona, ale efekt uboczny ijest trwały . Następnie 1zostanie obliczona i wartość wyrażenia będzie 1.

Jest odpowiednikiem

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;  

Zauważ, że powyższe wyrażenie jest całkowicie poprawne i nie wywołuje niezdefiniowanego zachowania, ponieważ istnieje punkt sekwencji między oceną lewego i prawego operandu operatora przecinka.

haccks
źródło
1
chociaż końcowe wyrażenie jest prawidłowe, czy drugie wyrażenie ++ i nie jest niezdefiniowanym zachowaniem? jest oceniany, a wartość niezainicjowanej zmiennej jest wstępnie zwiększana, co nie jest prawidłowe? czy coś mi brakuje?
Koushik Shetty
2
@Koushik; ijest inicjowany przez 5. Spójrz na oświadczenie deklaracji int i = 5;.
haccks
1
o mój Boże. Przepraszam, szczerze, nie widzę tego.
Koushik Shetty
Jest tu błąd: ++ i zwiększę, a następnie oceniam, podczas gdy i ++ oceniam i, a następnie zwiększam.
Quentin Hayot
1
@QuentinHayot; Co? Jakiekolwiek skutki uboczne mają miejsce po ocenie ekspresji. W przypadku ++i, to wyrażenie zostanie ocenione, izostanie zwiększone, a ta zwiększona wartość będzie wartością wyrażenia. W przypadku i++, to wyrażenie zostanie obliczone, stara wartość ibędzie wartością wyrażenia, izostanie zwiększona w dowolnym momencie między poprzednim a następnym punktem sekwencji wyrażenia.
haccks
62

Cytowanie z C11rozdziału 6.5.17, operator przecinka

Lewy operand operatora przecinka jest oceniany jako puste wyrażenie; istnieje punkt sekwencji między jego oceną a właściwym operandem. Następnie oceniany jest prawy operand; wynik ma swój typ i wartość.

Więc w twoim przypadku

(i, ++i, 1)

jest oceniany jako

  1. i, jest oceniany jako void wyrażenie, wartość odrzucana
  2. ++i, jest oceniany jako void wyrażenie, wartość odrzucana
  3. wreszcie 1zwrócona wartość.

Tak wygląda końcowe stwierdzenie

i = 1 + 1;

i idociera 2. Myślę, że to odpowiada na oba pytania,

  • Jak iuzyskuje wartość 2?
  • Dlaczego pojawia się komunikat ostrzegawczy?

Uwaga: FWIW, ponieważ po ocenie operandu lewej ręki występuje punkt sekwencji , wyrażenie takie jak (i, ++i, 1)nie będzie wywoływać UB, jak można ogólnie pomyśleć przez pomyłkę.

Sourav Ghosh
źródło
+1 Sourav, ponieważ to wyjaśnia, dlaczego inicjalizacja iwyraźnie nie daje efektu! Nie wydaje mi się jednak, żeby było to takie oczywiste dla gościa, który nie zna operatora przecinka (a ja nie wiedziałem, jak szukać pomocy poza zadaniem pytania). Szkoda, że ​​mam tyle głosów przeciw! Sprawdzę pozostałe odpowiedzi i zdecyduję, które zaakceptować. Dzięki! Przy okazji fajna najlepsza odpowiedź.
gsamaras
Czuję, że muszę wyjaśnić, dlaczego przyjąłem odpowiedź hacka. Byłem gotowy przyjąć twoje, ponieważ tak naprawdę odpowiada na moje oba pytania. Jeśli jednak sprawdzisz komentarze do mojego pytania, zobaczysz, że niektórzy na pierwszy rzut oka nie widzą, dlaczego nie wywołuje to UB. Odpowiedzi haccka zawierają przydatne informacje. Oczywiście mam odpowiedź dotyczącą UB w linku w moim pytaniu, ale niektórym może brakować. Mam nadzieję, że zgadzasz się z moim pragnieniem, jeśli nie, daj mi znać. :)
gsamaras
30
i = (i, ++i, 1) + 1;

Przeanalizujmy to krok po kroku.

(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

Otrzymujemy więc 2. A teraz ostatnie zadanie:

i = 2;

Cokolwiek znajdowało się w i, zanim zostało nadpisane.

dlask
źródło
Byłoby miło stwierdzić, że dzieje się tak z powodu operatora przecinka. +1 do analizy krok po kroku! Przy okazji fajna najlepsza odpowiedź.
gsamaras
Przepraszam za niewystarczające wyjaśnienie, mam tam tylko notatkę ( ... ale zignorowana, są ... ). Chciałem wyjaśnić głównie, dlaczego ++inie przyczynia się do wyniku.
dlask
teraz moje pętle for będą zawsze takie jakint i = 0; for( ;(++i, i<max); )
CoffeDeveloper
19

Wynik

(i, ++i, 1)

jest

1

Dla

(i,++i,1) 

ocena ma miejsce w taki sposób, że ,operator odrzuca oszacowaną wartość i zachowuje tylko najbardziej właściwą wartość, którą jest1

Więc

i = 1 + 1 = 2
Gopi
źródło
1
Tak, też o tym myślałem, ale nie wiem dlaczego!
gsamaras
@gsamaras, ponieważ operator przecinka ocenia poprzedni termin, ale go odrzuca (tj. nie używa go do przydziałów itp.)
Marco A.
14

Na stronie wiki znajdziesz dobre informacje na temat operatora przecinka .

Zasadniczo to

... oblicza swój pierwszy operand i odrzuca wynik, a następnie oblicza drugi operand i zwraca tę wartość (i typ).

To znaczy że

(i, i++, 1)

z kolei oceni i, odrzuci wynik, oszacuje i++, odrzuci wynik, a następnie oceni i zwróci 1.

Tomas Aschan
źródło
O_O cholera, czy ta składnia jest prawidłowa w C ++, pamiętam, że miałem kilka miejsc, w których potrzebowałem tej składni (w zasadzie napisałem: (void)exp; a= exp2;podczas gdy tylko potrzebowałem a = exp, exp2;)
CoffeDeveloper
13

Musisz wiedzieć, co robi tutaj operator przecinka:

Twoje wyrażenie:

(i, ++i, 1)

Obliczane jest pierwsze wyrażenie i,,, drugie wyrażenie ++i,, i 1zwracane jest trzecie wyrażenie, dla całego wyrażenia.

Więc wynik jest: i = 1 + 1.

Jak widzisz, na Twoje pytanie dodatkowe pierwsze wyrażenie inie ma żadnego efektu, więc kompilator narzeka.

songyuanyao
źródło
5

Przecinek ma „odwrotny” priorytet. To właśnie otrzymasz ze starych książek i podręczników C z IBM (lata 70. / 80.). Zatem ostatnie „polecenie” jest używane w wyrażeniu nadrzędnym.

We współczesnym C jego użycie jest dziwne, ale jest bardzo interesujące w starym C (ANSI):

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

Podczas gdy wszystkie operacje (funkcje) są wywoływane od lewej do prawej, tylko ostatnie wyrażenie zostanie użyte jako wynik warunkowego „while”. Zapobiega to obsłudze „goto” w celu zachowania unikalnego bloku poleceń do wykonania przed sprawdzeniem warunków.

EDYCJA: Pozwala to uniknąć również wywołania funkcji obsługującej, która mogłaby zająć się całą logiką po lewej stronie operandów, a więc zwrócić wynik logiczny. Pamiętaj, że w przeszłości nie mieliśmy funkcji inline w C. Dzięki temu można uniknąć narzutu wywołań.

Luciano
źródło
Luciano, masz również link do tej odpowiedzi: stackoverflow.com/questions/17902992/… .
gsamaras
We wczesnych latach 90. funkcji wbudowanych często używałem go do optymalizacji i uporządkowania kodu.
Luciano,