Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variable arguments list and null pointer

Tags:

c

pointers

Consider the following code:

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

void foo(const char *arg, ...) {
    va_list args_list;

    va_start(args_list, arg);

    for (const char *str = arg;
         str != NULL;
         str = va_arg(args_list, const char *)) {
        printf("%s\n", str);
    }

    va_end(args_list);
}

int main(int argc, char **argv) {
    foo("Some", "arguments", "for", "foo", NULL);
    foo("Some", "arguments", "for", "foo", 0);
    return 0;
}

As we can see, foo() uses variable arguments list to get a list of strings and then print them all. It's supposed that the last argument is null pointer, so arguments list is processed until NULL is detected.

Function foo() is called from main() in two different ways, with NULL and 0 as the last argument.

My question is: is the second call with 0 as the last argument is correct?

I suppose, that we shouldn't call foo() with 0. The reason is that in this case, for example, compiler can't guess from the context that 0 should be treated as null pointer. So it processes it as a usual integer. Then foo() deals with 0 and cast it to const char*. The magic begins when null pointer has internal representation different from 0. As I can understand it leads to the failure in check str != NULL (because str will be equal to 0 casted to const char* which differs from null pointer in our situation) and wrong program behavior.

Are my thoughts right? Any good explanation is appreciated.

like image 986
Edgar Rokjān Avatar asked Jan 30 '16 20:01

Edgar Rokjān


People also ask

What is true about the variable argument list?

Variable Argument (Varargs): The varrags allows the method to accept zero or muliple arguments. Before varargs either we use overloaded method or take an array as the method parameter but it was not considered good because it leads to the maintenance problem.

What is variable arguments in C?

Variable length argument is a feature that allows a function to receive any number of arguments. There are situations where we want a function to handle variable number of arguments according to requirement.

Can we pass null as argument in C?

In some programming languages, like python for one, it is possible to pass a NULL parameter as an argument, but in C I always thought this would result in Undefined Behaviour.

When can a variable argument list be passed to a function?

If you've converted the variable arguments to a va_list , you can pass the va_list to another function that only takes a va_list , but that function (or one that it calls) must have some way of knowing what's in the va_list .


Video Answer


1 Answers

Both calls are incorrect, in general.

The call with bare 0 is certainly incorrect, but not for the reason that you state. When compiling the call to the function foo() which has variable arguments, the compiler has no way to know what type foo() is expecting.

If it were to cast 0 to a const char *, that would be fine; even if the null pointer has internal representation different from all-bits-zero, the language guarantees that using the value 0 in a pointer context results in a null pointer. (This may require the compiler to actually generate some non-trivial code for the typecast, but if so, it's required to do that.)

But it has no reason to think 0 is intended to be a pointer at all. What will happen instead is that it will pass 0 as an int. And this can cause a problem if int has a different size from a pointer, or if for any other reason the int 0 has a different representation than a null pointer, or if this system passes pointer arguments in a different way from integers.

So this is undefined behavior: foo uses va_arg to get an argument of type const char * that was actually passed as type int.

What about using NULL? According to this answer and references therein, the C standard allows the macro NULL to be defined as simply 0 or any other "integer constant expression with the value 0". Contrary to popular belief, it doesn't have to be (void *)0, though it might be.

So it is not safe to pass a bare NULL, because you might be on a platform where it is defined as 0. And then your code could fail for the same reason as above.

To be safe and portable, you can write either of:

 foo("Some", "arguments", "to", "foo", (const char *)0);

or

 foo("Some", "arguments", "to", "foo", (const char *)NULL);

But you can't leave off the cast.

like image 70
Nate Eldredge Avatar answered Oct 04 '22 18:10

Nate Eldredge