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.