Mehr über Zuweisungen in C++
Undefiniertes Verhalten
Zwei Schreibzugriffe
Ein Programm, in dem zwei Schreibzugriffe auf ein Objekt stattfinden, für die es keine festgelegte zeitliche Reihenfolge gibt, hat undefiniertes Verhalten.
Die Reihenfolge der Auswertung von Operanden eines Operators ist in C++ im allgemeinen nicht festgelegt.
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ::std::stringusing namespace ::std::literals;
int main()
{ int v = 1;
::std::cout <<( v = 2 )<<( v = 3 )<< "\n"s; }Protokoll
22
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ::std::stringusing namespace ::std::literals;
int main()
{ int v = 1;
::std::cout <<( v = v + 1 )<<( v = v + 1 )<< "\n"s; }transcript
33
Zur Korrektur dieses Fehlers teile man die Schreibzugriff so auf mehrere Auswertungsanweisungen auf, daß jede Auswertungsanweisung nur einen Schreibzugriff enthält. Die Reihenfolge des Auswertung von Anweisungen, welche direkt im selben Block stehen, ist festgelegt.
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ::std::stringusing namespace ::std::literals;
int main()
{ int v = 1;
::std::cout <<( v = 2 );
::std::cout <<( v = 3 )<< "\n"s; }Protokoll
23
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ::std::stringusing namespace ::std::literals;
int main()
{ int v = 1;
::std::cout <<( v = v + 1 );
::std::cout <<( v = v + 1 )<< "\n"s; }transcript
23
Schreib- und Lesezugriff
Auch ein Programm, in dem ein Schreibzugriff und ein Lesezugriff auf ein Objekt stattfinden, für die es keine festgelegte zeitliche Reihenfolge gibt, hat undefiniertes Verhalten.
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ::std::stringusing namespace ::std::literals;
int main()
{ int v = 1;
::std::cout <<( v = v + 1 )<< v << "\n"s; }transcript
22
Zur Korrektur dieses Fehlers teile man die beiden Zugriffe auf mehrere Auswertungsanweisungen auf.
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ::std::stringusing namespace ::std::literals;
int main()
{ int v = 1;
::std::cout <<( v = v + 1 );
::std::cout << v << "\n"s; }transcript
22
- Zitate *
- “Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.” – C++ 2017 1.9 15
- “If a side effect on a memory location is unsequenced relative to either another side effect on the same memory location or a value computation using the value of any object in the same memory location … the behavior is undefined.” – C++ 2017 1.9 15
PS: Seit C++17 ist die Reihenfolge der Auswertung der Operanden von »<<« definiert, und daher hat die Auswertung von »::std::cout <<( v = v + 1 )<< v« nun kein undefiniertes Verhalten mehr! (Siehe auch: http://www.open-std.org/jtc1/sc22/WG21/docs/papers/2016/p0145r1.pdf)
Undefiniertes Verhalten
Es gibt in C++ vier verschiedene Möglichkeiten, wie das Verhalten eines Programmteils gegeben sein kann.
- definiert: das Verhalten (hier: der Wert) ist durch C++ definiert
1 + 2
- unspezifiziert: Es gibt eine Auswahl verschiedener Verhalten, welches davon sich bei der Auswertung manifestiert ist nicht festgelegt, das Verhalten (hier: der Wert) des folgenden Ausdrucks ist weder von C++ noch von einer Implementation spezifiziert
rand()
- undefiniert: Es kann alles mögliche passieren (Programmabbruch, Festplattenlöschung, Auslösung einer Katastrophe …), undefiniertes Verhalten ist normalerweise ein schwerer Fehler
{ int a; ::std::cout << a; }
n3290 4.1: “A glvalue (…) can be converted to a prvalue. (…) if the object is uninitialized, a program that necessitates this conversion has undefined behavior. ”
Mehrfache Schreibzugriffe auf ein Objekt innerhalb eines einzigen Ausdrucks sind ebenfalls undefiniertes Verhalten !
Beispiel für undefiniertes Verhalten
::std::cout <<( i = i + 1 )<<( i = i + 1 )<< '\n';
moegliche Verhinderung des undefinierten Verhaltens
::std::cout <<( i = i + 1 );
::std::cout <<( i = i + 1 )<< '\n';Beispiel für undefiniertes Verhalten
::std::cout <<( i = i + 1 )<< i << '\n';
moegliche Verhinderung des undefinierten Verhaltens
::std::cout <<( i = i + 1 );
::std::cout << i << '\n';Beispiel für undefiniertes Verhalten
i =( i = i + 1 )+ 7;
moegliche Verhinderung des undefinierten Verhaltens
i = i + 1;
i = i + 7;- C++14 clause 1 section 9 paragraphs 13 – 15 (gekürzt und leicht überarbeitet)
- If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, and they are not potentially concurrent (1.10), the behavior is undefined.
- [Note: The next section imposes similar, but more complex restrictions on potentially concurrent computations. – end note]