2015-02-28 27 views
5
void swap(int* a, int* b) { 
    if (a != b) 
     *a ^= *b ^= *a ^= *b; 
} 

Jako wyżej *a ^= *b ^= *a ^= *b tylko skrót *a = *a^(*b = *b^(*a = *a^*b)), może (na przykład) 2^*a ocenia (dla XOR) tuż przed 3. *a jest zmodyfikowany (z =)?Niezdefiniowane zachowanie operatorów w algorytmie zamiany XOR?

Czy to ma znaczenie, czy piszę to w C99/C11/C++ 98/C++ 11?

+0

pamiętam dyskusję o tym, czy jest to dozwolone w C11 z nowej reguły sekwencjonowania. W C99 jest wyraźnie niezdefiniowany ('* a' jest modyfikowany dwukrotnie bez punktu sekwencji pomiędzy). – mafso

+1

Pamiętam, że C++ daje pewne dodatkowe gwarancje sekwencjonowania dla operatorów przypisania, potrzebne, ponieważ operatory przypisania C zwracają wartości, ale operatory przypisania C++ zwracają wartości l, a następna konwersja l-do-r-wartości powinna mieć dobrze określone zachowanie. Rezultatem * może * być to, że jest to poprawne w C++, ale nie jestem pewien. – hvd

+0

@hvd: C11 przyjął model sekwencjonowania C++ ze względu na wątkowanie standaryzacji. Modyfikacja LHS zadania jest teraz sekwencjonowana po ocenie LHS i RHS. – mafso

Odpowiedz

10

Standard C++ 11 mówi:

5,17/1: operatora przypisania (=) i związek operatorzy przyporządkowanie wszystkich grupa prawej do lewej. (...) przypisanie jest sekwencjonowane po obliczeniu wartości prawego i lewego argumentu operacji, i przed obliczeniem wartości wyrażenia przypisania.

1,9/15: przypadku efektem ubocznym w skalarnej przedmiotu jest unsequenced względem albo innego efektu siebie na tej samej skalarnej obiektu lub wartość obliczeń z wykorzystaniem wartości tego samego skalarnej obiektu, zachowanie nieokreślone .

Więc *a ^= *b się w następującej kolejności:

  1. *a i *b oblicza. To NIE ustalona w którym zamówienie
  2. operacja xor jest wykonywane
  3. przydział odbywa, czyli nowa wartość jest przechowywana w *a
  4. nowa wartość jest używana jako skutek ekspresji (*a ^= *b)

teraz *b ^= *a ^= *b, które zgodnie z zasadą pierwszeństwa jest *b ^= (*a ^= *b):

  1. *b i (*a ^= *b) są obliczane. Jest to określone w jakiej kolejności. Ale jako *b nie jest modyfikowany przez (*a ^= *b), nie ma to znaczenia.
  2. operacja xor jest wykonywane
  3. przyporządkowanie jest zrobione, czyli nowa wartość jest przechowywana w *b

Ale teraz do nieokreślonym sekwencjonowania z *a ^= *b ^= *a ^= *b która jest według zasad pierwszeństwa *a ^= (*b ^= (*a ^= *b)):

  1. *a i (*b ^= (*a ^= *b)) są obliczane. Jest to określone w jakiej kolejności. Ale jako *a IS został zmodyfikowany przez (*b ^= (*a ^= *b)). Wynik będzie zatem zależał od tego, która wartość zostanie obliczona jako pierwsza. To wyraźnie U.B.

Załóżmy *a ocenia najpierw (czyli zanim cokolwiek innego):
co można uzyskać oryginalną wartość tego, który będzie XORed z wartością (*b ^= (*a ^= *b)), że jest oryginalny *b XORed z oryginałem *a ponownie zmieniono na: *b. To spowoduje 0 (które będą przechowywane w *a).

Załóżmy (*b ^= (*a ^= *b)) ocenia najpierw, to jego wynik jest oryginalny *a, ale treść *a zmienia się na oryginalnym *a XORed z oryginalnym *b. Tak więc będzie to prowadzić do pierwotnego *b (który będzie przechowywany w *a)

na drodze, w obu przypadkach *b zawiera oryginalna wartość *a XORed dwukrotnie *b sposób, że będą zawierały *b oryginalnego *a.

Wnioski: Tutaj wykazano, że końcowa wartość *b jest jednoznacznie określony przez wyrażenie to, ale ostateczna wartość *a nie jest jednoznacznie określone (dwie wartości możliwe). Wyraźnie jest to UNSPECIFIED/UNDEFINED RESULT! Może się zamienić lub może stracić *a w zależności od kompilatora.

Jak dokonać zamiany na pewno?

Wykazałem powyżej, że pierwsze dwa zadania złożone są dobrze określone. Musimy więc po prostu upewnić się, że ostatnie zadanie złożone zostało wykonane po tym. Może być zagwarantowana przez operatora przecinkami:

5,18/1: Para wyrażeń oddzielonych przecinkiem ocenia się od lewej do prawej strony, a wartością lewe jest odrzucane

Stąd dodaje będzie działać:

void safe_swap(int* a, int* b) { 
    if (a != b) 
     *b ^= *a ^= *b, *a ^= *b; 
} 
+0

Jeśli miałeś rację, że dokładnie dwie wartości są możliwe dla '* a', to nie byłoby niezdefiniowane zachowanie, które w najgorszym razie nie byłoby bliżej określone. W większości wersji standardów C i C++ zachowanie jest właściwie niezdefiniowane, co nie tylko oznacza, że ​​* dowolna * wartość jest poprawna, ale także oznacza, że ​​* nie * wartość jest poprawna (na przykład awaria programu). Ale rzeczywiście podnosić interesujący punkt, że nawet jeśli zachowanie jest zdefiniowane, wartość nie musi być. – hvd

+0

@hvd Masz rację co do terminologii: w 5/4 podano, że niezdefiniowane zachowanie występuje, gdy "wynik nie jest zdefiniowany matematycznie lub nie mieści się w zakresie reprezentowalnych wartości dla jego typu". Przepraszam, jeśli używam "nieokreślonego" w sensie matematycznym. Ja to poprawię. – Christophe

+0

Nie ma czegoś takiego jak "NIEZAKRESOWANY/NIEOFINANSOWANY WYNIK". Jest to niezdefiniowane zachowanie lub nieokreślone zachowanie. –