Open In App

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

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:

Rules of Sequence Points

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++ 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:

#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.


Article Tags :