Undefined Behavior in C and C++

I believe you have read the statement “Undefined Behaviour” or “UB” in many programming books and blogs. Many new programmers and newbies are not able to understand the meaning of this statement. If you are one of them then this blog post is for you because, in this blog post, you will learn the meaning of undefined behavior in C and C++ programming languages.

So without wasting time let’s get started.

During the development definitely, you would have faced such a scenario in which your code would not have behaved as per its implementation. Or if you are fresher then in the future might you will face the same. Such undefined behavior issues are hard to find and become a nightmare for the developer.

Undefined behavior means anything can happen your program may fail to compile, or it may execute incorrectly (either crashing or silently generating incorrect results), or it may fortuitously do exactly what the programmer intended. That means whenever the result of a program is unpredictable, it is said to have undefined behavior.

It is the responsibility of the programmer to understand the scenario when the code show undefined behavior. Especially when you are a C/C++ programmer.

Let’s see some piece of C code that results give undefined behavior.

Some Undefined behavior in C and C++:

The behavior is undefined in the following circumstances:

1. Access array out of bounds:

Accessing an array out of its boundary yields undefined behavior (UB).

#include<stdio.h>

//a lookup table
int lookupTable[5] = {0};

int readLookupTable(int index)
{
    const int value = lookupTable[index];
    return value;
}

int main()
{
    /*
      Undefined behavior for index 5 
      because it is out of array bounds
    */
    readLookupTable(5);

    return 0;
}

 

2. An object is referred to outside of its lifetime:

Accessing an object outside of its lifetime results in undefined behavior. See the below code.

#include<stdio.h>

int *foo()
{
    //Local variable
    int var = 5;

    //Returning address of the local variable
    return &var;
}

int main()
{
    int *ptr = foo();

    //undefined behavior.
    printf("%d", *ptr);

    return 0;
}

 

3. The value of a pointer to an object whose lifetime has ended is used:

If a pointer value is used in an evaluation after the object the pointer points to (or just past) reaches
the end of its lifetime, the behavior is undefined.

#include<stdio.h>
#include<stdlib.h>


int main()
{
    //allocate dynamic memory
    int *ptr = malloc(sizeof(int)* 5);
    if(ptr == NULL)
    {
        return -1;
    }
    //free the allocated memory
    free(ptr);

    //Undefined behavior
    *ptr = 2;

    return 0;
}

 

4. The program attempts to modify a string literal:

If the program attempts to modify a string literals, the behavior is undefined. Consider the below code,

char* ptr = "aticleworld";


ptr[2] = 'I'; // Undefined behavior

 

5. Signed integer overflow:

Overflow of signed integer overflow leads to undefined behavior. Consider the below example, everything will ok till the ‘data’ is not  INT_MAX.

int foo(int data)
{
    // either true or UB due to signed overflow
    return data+1 > data; 
}

 

6.Uninitialized local object:

The value of the local object (automatic storage duration) is indeterminate if it is not initialized explicitly (or no assignment to it has been performed prior to use). If this object is used in the code the behavior will be undefined.

int main()
{
    int  p; // uninitialized local variable
    if(p) // UB access to uninitialized variable
    {
        printf("Hi\n");
    }
    else
    {
        printf("Bye\n");
    }

    return 0;
}

 

7. The value of the second operand of the / or % operator is zero:

In C and C++ binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second. If the second operand of / or %  is 0 (zero) the behavior is undefined.

int data = 1;


return (data / 0); // undefined behavior

 

8. Dereference of null pointer:

Dereferencing the null pointers yield undefined behavior which means the behavior of the unary * operator is undefined on null pointers.

int foo1(int* ptr)
{
    int data = *ptr;
    if(!ptr)
    {
        return data; // Either UB above or this branch is never taken
    }
    else
    {
        //expression
    }
    return 0;
}



int foo2()
{
    int* ptr = NULL;

    return *ptr; // Unconditional UB
}

 

9. Access to pointer passed to realloc:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *ptr1 = (int*)malloc(sizeof(int));
    int *ptr2 = (int*)realloc(ptr1, sizeof(int));
    *ptr1 = 1; // UB access to a pointer that was passed to realloc
    *ptr2 = 2;
    if (ptr1 == ptr2) // UB access to a pointer that was passed to realloc
    {
        printf("%d%d\n", *ptr1, *ptr2);
    }
    return 0;
}

 

 

Recommended Post: