Undefined behaviours in C

In C programming, undefined behaviours (UB) present perhaps one of the biggest challenges to beginners. As an occasional practitioner, I am still baffled by them–which happened recently with the code that I wrote in an Arduino sketch.

The function get_input() is generic. Besides parameter String prompt, it takes two generic function pointer parameters (*parse_func)(void* const) and (*validate_func)(const void* const) to read user input and validate user input, respectively. They are generic because they have parameters of type void (albeit with different constraints, to which we will come back).

Below is the function parse_int() passed in place of the parameter (*parse_func)(void* const).

There are two things to note here. First, the function signature matches exactly the function pointer declaration: it takes a parameter of type void* const–or, const pointer to void–and does not return any result. Second, it casts the parameter to the desired type before dereferencing the memory location where the input value is written.

And below, the function validate_positive_int() passed in place of the parameter (*validate_func)(const void* const).

Again, this matches exactly the function pointer declaration. It takes a parameter of type const void* const–or, const pointer to const void–and returns a result of type int. Also, the parameter is cast to the desired type–in this case, const pointer to const int–before the logical operation.

In the course of debugging the sketch, I inadvertently modified this last function as follows.

At some point, I realised my mistake and began to wonder why it had not triggered a compilation error and why my code was running normally with the value 1234 as user input. After all, my intention was to have a const pointer to const int; that is, I did not want the argument being passed to be modifiable.

I joined ##c (IRC) to seek answers.

The answer was, an undefined behaviour arises when a const object is modified through a non-const pointer.

It took me some time to understand–or rather, accept–this. As a programmer of “safer” languages, I found it difficult to take this explanation at face value and so embarked on further exploration with the goal of getting the compiler to throw the error that my mistake deserved.

Eventually, I found that if I cast the argument to exactly what was declared in the function signature, like below, the error would be thrown.

Success in displaying the compilation error helped me understand this undefined behaviour: the constraints in the function declaration, just like much of C, are merely promises that the function will interpret arguments in a certain way, but they do not offer any guarantee that it will fulfil its promises.

Undefined behaviours are still stumbling blocks to those who are not expert C programmers, but understanding–and accepting–that they are part of the language makes coding in C much  easier. I– for one–am going to embrace undefined behaviours from now on.

Leave a Reply

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

%d bloggers like this: