Undefined behaviours in C

Undefined behaviours in the C language confuse many beginners. As an occasional C programmer I am also baffled when I encounter them—as happened with this code that I wrote in an Arduino sketch.

static void
get_input(String prompt, void* const input, void (*parse_func)(void* const), int (*validate_func)(const void* const))
{
  while (!validate_func((const void* const)input)) {
    Serial.println(prompt);
    while (Serial.available() == 0);
    parse_func(input);
  }
}

void
loop()
{
  get_input("Enter the number of blinks: ",
        &(led->blinks),
        *parse_int,
        *validate_positive_int);
}

get_input() is a generic function. It takes a parameter String prompt and two function pointer parameters (*parse_func)(void* const) and (*validate_func)(const void* const). These generic function pointers take parameters of type void, which can be cast to any other type.

At runtime I passed the function validate_positive_int() as argument for parameter (*validate_func)(const void* const). It converts its argument to an integer value and tests if the result is greater than zero.

static int
validate_positive_int(const void* const val)
{
  return *(const int* const)val > 0;
}

As I was debugging the sketch, I modified the function as follows

static int
validate_positive_int(const void* const val)
{
  *(int*)val = 1234; // <--- modification 
  return *(const int* const)val > 0;
}

Although I assigned a new value to the parameter of type const void* const – that is, a constant – the code compiled successfully, and the program executed without any error.

But when I tried to change the value of the parameter by casting it to the same type as declared for the parameter, the compiler reported an error—as it should.

static int
validate_positive_int(const void* const val)
{
  *(const int* const)val = 1234; // <--- modification 
  return *(const int* const)val > 0;
}
test.c: In function ‘validate_positive_int’:
test.c:15:28: error: assignment of read-only location ‘*(const int *)val’
     *(const int* const)val = 1234;

This was puzzling because I expected the code to not compile in both cases.

Going through the C documentation, I found that an undefined behaviour arises when a const object (that is, const void* const) is modified through a non-const pointer (that is, int*).

Even if I was reluctant to accept this explanation because of my familiarity with safer language compilers that enforce parameter declarations strictly – in a case similar to the above, that the value of a const parameter is immutable – I eventually had to accept that C is different in how a parameter declaration is not enough to cause a compilation error when an argument is treated contradictorily to the declaration.

When a programmer declares a parameter for a function, they ask future users of that function to call it with arguments of the declared type. However, they do not guarantee that their function will not treat the argument however they want. In the example above, although the function tells the caller that it must be passed a const, it can still modify the argument.

Leave a comment

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.