Open In App

How Do Sequence Points Relate to Undefined Behaviour in C++?

Last Updated : 04 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

In C++, sequence points are used to determine the order of evaluation of expressions to avoid undefined behavior in our programs. In this article, we will learn how do sequence points relate to undefined behavior in C++.

What are Sequence Points in C++?

In C++, sequence points define the order in which expressions involving side effects are evaluated such that all side effects of previous evaluations are guaranteed to be complete, and no side effects from subsequent evaluations have started, here a side effect is a change in the system state that can be a change in the value of an object, or any modification of an object.

Sequence Points in C++ Operations

The following operations in C++ introduce sequence points:

  • The end of a full expression (a statement).
  • The end of a function call.
  • After the evaluation of the first operand of the comma operator(,)
  • After the evaluation of the first operand of the logical AND (&&), logical OR (||), and conditional (?:) operators.

Rules of Sequence Points

  • Full expressions are sequenced in order of execution.
  • Operand evaluation is generally sequenced before operation execution, but the order among operands may be unspecified.
  • Increment operators (++) sequence value computation before modifying the operand.
  • The comma operator introduces a sequence point, sequencing operations on its left before evaluating its right.
  • In the conditional operator, the condition is evaluated and sequenced before either operand.
  • After a return statement in a function, all side effects of the expression being returned are completed before proceeding.

Sequence Points Leading to Undefined Behavior

Undefined behavior can occur when the order of sequence points is violated means if the value of any object in a memory location is modified more than once between two sequence points, then the behavior is undefined because the order of evaluation of expressions is not specified so, an expression could be evaluated in any order unless sequence points are used to enforce a particular order.

Following are some situations where the sequence points may lead to undefined behaviors:

1. Modifying a Variable Twice Between two Sequence Points

In the following example, the variable x is modified twice between sequence point, which leads to undefined behavior. The order of evaluation of x++ and ++x is unspecified, and the result of y is not determinable until the program is executed.

int x = 5;
int y = x++ + ++x; // Undefined behavior

2. Modifying an Object Through Two or More Pointers.

In the following example, two pointer points to the same object, the object x is modified using both the pointers between the sequence points which leads to undefined behavior.

int x = 5;
int *ptr1 = &x;
int *ptr2 = &x;
*ptr1 += *ptr2++; // Undefined behavior

3. Calling Functions that Modifies the Same Object

In the following example two functions are called on the same object at the same time, the order of evaluation of the functions is not fixed which leads to undefined behaviours.

int x = 5;
int increment(int& n) { return ++n; }
int decrement(int& n) { return --n; }

int y = increment(x) + decrement(x); // Undefined behavior

4. Calling a Function Through a Null Pointer.

In the following example, a function is called through a null pointer. Calling a function through a null pointer results in undefined behavior, even if the function is defined. This is because the evaluation of the function call expression involves a sequence point where the function address is expected to be valid.

void foo() { }

void (*p)() = nullptr;
p(); // Undefined behavior

C++ Program to Demonstrate Undefined Behavior Due to Voilation of Sequence Point Rules

The below program illustrates a scenario that can lead to undefined behavior due to the violation of sequence point rules.

C++
// C++ Program to demonstrate Undefined Behaviour due to
// violation of order of sequrnce points

#include <iostream>
using namespace std;

int main()
{

    int a = 2;
    int b;
    b = ++a + a++ + a++;
    cout << b << endl;
    return 0;
}

Output
11

Time Complexity: O(1)
Auxiliary Space: O(1)

Explanation: In the above example, the value of a is modified between two sequence points more than once using post-increment and pre-increment operator. This code violates the standards of C++, the value of b is unpredictable. Hence, this is undefined behavior.

To avoid this undefined behavior related to sequence points, we can separate the operations into distinct statements to ensure that each operation on the variable is completed before the next one begins. Consider the below example to resolve the issue:

C++
#include <iostream>
using namespace std;

int main()
{
    int a = 2;
    int b;

    // Separate the operations on 'a' into distinct
    // statements
    ++a; // First, pre-increment 'a'
    b = a; // Assign the current value of 'a' to 'b'
    a++; // Then, post-increment 'a'
    b += a; // Add the new value of 'a' to 'b'
    a++; // Finally, post-increment 'a' again
    b += a; // Add the new value of 'a' to 'b' again

    cout << b << endl;

    return 0;
}

Output
12

Explanation: In this version, each increment operation (++a and a++) is performed in a separate statement, ensuring that the side effects (i.e., the modification of a) are completed before a is used in the next operation. This removes the ambiguity that leads to undefined behavior and makes the program’s behavior well-defined and predictable.




Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads