ISO/IEC 9899:1999 (E) § 6.7.5.3.7

this is a rant.

i have never found a misfeature in the core c language before. i’ve found many lacking features and many quirky things about how library functions work, but when it came to the core language i was always pretty happy that everything had been done reasonably.

two days ago this changed. i’ve found a bug in c.

imagine we have two function prototypes, thus:

void takes_evil_ptr (evil *x);

void takes_evil (evil x);

where evil is defined by some typedef to have some (complete) type.

now, of course, if we wanted to call these functions from another function that provides an instance of evil then it would look something like this:

void
provides_evil (void)
{
  evil x;

  takes_evil_ptr (&x);
  takes_evil (x);
}

everything is good.

now, let’s say we want to implement takes_evil() as a simple wrapper around takes_evil_ptr(). to make it easier, let’s say that we’re not even concerned about the state that the argument is left in after the call finishes. how should we do this?

the naïve approach would be to write this function:

void
takes_evil (evil x)
{
  takes_evil_ptr (&x);
}

clearly this takes a pointer to the copy of x that was passed as the argument to takes_evil and passes that pointer along to takes_evil_ptr().

wrong.

i said above that evil merely has to be some complete type.

imagine we did the following:

typedef int evil[1];

and consider the declaration

void takes_evil (evil x);

in light of iso/iec 9899:1999 (e) § 6.7.5.3 which states

  1. A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

so this declaration really reads:

void takes_evil (int *x);

and the code

void
takes_evil (int *x)
{
  takes_evil_ptr (&x);
}

is very clearly in error (since x is already a pointer).

of course, this wouldn’t be a problem in most sane situations. normally we would know if the evil type that we are dealing with is typedef’ed as a scalar or an array type.

the “evil” type, of course, is va_list.

§ 6.7.5.3.7 is just stupid, too. it prevents the user from passing an array by value even if that is what they intended to do. if the user really wanted to pass a pointer then they could just declare the function as taking a pointer type. consider that structures are passed by value and that structures can even contain arrays!

i have functions in dvalue that take va_list * and functions in gsettings which take va_list and call into the dvalue functions. ouch. the best workaround i can think to do is to make an autoconf-defined macro that either adds a & or not depending on if your va_list implementation is detected as being array-based.

another solution would be to never allow the passing of va_list and use the parameter type va_list *. on systems that implement va_list as an array this would effectively do nothing and on systems that have it as a scalar type it would only be one extra dereference. of course, this parts with convention (functions that take va_list are everywhere).

((ps: one good thing is that § 7.15 says “It is permitted to create a pointer to a va_list and pass that pointer to another function, in which case the original function may make further use of the original list after the other function returns.” this is the part that i was worried about, but it seems to be ok.))

10 thoughts on “ISO/IEC 9899:1999 (E) § 6.7.5.3.7”

  1. The reason you can’t pass arrays as values is that since parameters size has to be fixed (va_args being the notable exception) you’d have to call the function with an array with a fixed size. Your function definition would have to be something like:

    foo(int bar[10])

    and you’d always have to pass an array of size 10. Passing structures by value works exactly because structures have a fixed size. They can contain arrays but only fixed sized ones.

    The only reason this works with va_args is that the variable part is always the last arguments and its up to the function to know when to stop reading the stack by reading the arguments. That’s why you can’t have va_args functions that take 0 or more arguments since you need at least one to know if there’s any more (printf is a good example of this).

    You could argue that you should be able to pass fixed sized arrays as arguments but you can easily trick the compiler into letting you. Just define a structure with only the array in it and you can then use C’s weak typing to pass an actual array instead of the structure. You might need some ugly casting to shut up the compiler though.

  2. The original blog posting is right; the non-orthogonal handling of arrays is a wart. They had their reasons; passing general arrays by value would have required some heavyweight mechanism, and there are advantages to being able to convert an array to a pointer to its first element, but it means you can’t treat an array like other types.

    You can embed an array in a struct:

    typedef struct {
    int data[4];
    } four_ints;

  3. This isn’t a bug in C or a misfeature, really. It’s (to put bluntly) you misusing typedefs. Typedefs do not define new types. They merely provide an alias. For the most part (not entirely, of course) the two following lines of C code are equivalent:

    typedef foo bar;

    #define bar foo

  4. Sean: the typedef Ryan is complaining about was not defined by him: va_list is defined by the C standard, but its type is not. Further more, it actually is defined differently on different platforms. It could be a pointer, array or something else.

    If the aim is to copy a va_list and you’re using glib, G_VA_COPY() is the best option.

  5. I’m not convinced that the “obvious” solution is wrong. “the code is very clearly in error (since x is already a pointer).” There’s nothing wrong with using the address-of operator on a pointer. It seems to me that the type would even work out, because takes_evil_ptr would expect evil *, i.e. int **, no?

  6. evil *x is int (*x)[1].

    int (*x)[1] is a pointer to an array (ie: the pointer points at the first element of the array).

    int **x is a pointer to a pointer to an array (ie: the pointer points at a pointer that points at the first element of the array).

  7. Another “misfeature” in the Core language is this:

    int main()
    {
    int i = 0;
    {
    int i = i + 1;
    printf(“%d\n”, i);
    }
    return 0;
    }

    Does another language have such broken scoping rules?

Comments are closed.