Search code examples
c++nullvariadic-functionsnullptr

Ellipsis NULL, nullptr and undefined behaviour


I have function taking a variable and NULL terminated list of pointer arguments using the elipsis. I know about variable length template argument lists. It is about legacy code. Will the following two calls lead to undefined behaviour because the terminator is interpreted as Serializable* by va_arg? What are the differences between the two calls?

void serialize(Serializable* first, ...) {
    va_list vl;

    va_start(vl, first);

    while(1)
    {
        Serializable* arg = va_arg(vl, Serializable*);

        if(arg == NULL)
            break;

        /* serialize arg here */
    }
}

serialize(obj1, obj2, obj3, NULL);
serialize(obj1, obj2, obj3, nullptr);

Solution

  • No, I don't think so.

    Quoting cppreference.com on va_arg:

    If the type of the next argument in ap (after promotions) is not compatible with T, the behavior is undefined, unless:

    • one type is a signed integer type, the other type is the corresponding unsigned integer type, and the value is representable in both types; or
    • one type is pointer to void and the other is a pointer to a character type (char, signed char, or unsigned char).

    (This very closely matches the actual C11 wording; remember, va_arg is defined by C, not C++.)

    Now, C11's definition of "compatible types" is summed up by another cppreference, which teaches us that for your NULL to have a type compatible with Serializable*, the pointee-type of NULL would have to be compatible with Serializable.

    Now, NULL has an implementation-defined type so you can't know what it is, but it's certainly not going to be one that's compatible with Serializable, unless that is simply a type alias for void or int and you get lucky.

    With the nullptr you get void*, but then again see above.