va_args Conformance

S

Shao Miller

If the following program strictly conforming?

#include <stddef.h>
#include <stdarg.h>

static void test(int, ...);

int main(void) {
test(0, 42);

return 0;
}

static void test(int x, ...) {
typedef unsigned char one_char[1];
/* Valid declaration on the next line */
one_char * unused;
va_list varargs;
unsigned char * byte;

va_start(varargs, x);
if (x)
byte = va_arg(varargs, one_char);
va_end(varargs);

return;
}
 
B

Ben Bacarisse

Shao Miller said:
If the following program strictly conforming?

#include <stddef.h>
#include <stdarg.h>

static void test(int, ...);

int main(void) {
test(0, 42);

return 0;
}

static void test(int x, ...) {
typedef unsigned char one_char[1];
/* Valid declaration on the next line */
one_char * unused;
va_list varargs;
unsigned char * byte;

va_start(varargs, x);
if (x)
byte = va_arg(varargs, one_char);
va_end(varargs);

return;
}

It looks fine to me. Does some implementation reject it?
 
S

Shao Miller

Shao Miller said:
If the following program strictly conforming?

#include<stddef.h>
#include<stdarg.h>

static void test(int, ...);

int main(void) {
test(0, 42);

return 0;
}

static void test(int x, ...) {
typedef unsigned char one_char[1];
/* Valid declaration on the next line */
one_char * unused;
va_list varargs;
unsigned char * byte;

va_start(varargs, x);
if (x)
byte = va_arg(varargs, one_char);
va_end(varargs);

return;
}

It looks fine to me. Does some implementation reject it?

Yes. At least two of them, and in different ways that I didn't quite
expect.

GCC 4.3.3 says:

test.c: In function 'test':
test.c:21: error: invalid use of non-lvalue array

Where 'va_arg' expands to '__builtin_va_arg'.

clang 2.9 says:

test.c:21:16: error: assigning to 'unsigned char *' from incompatible
type 'one_char' (aka 'unsigned char [1]')
byte = va_arg(varargs, one_char);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

Where once again, 'va_arg' expands to '__builtin_va_arg'.

My reading of N1256's 7.15.1.1 has me pondering these.
 
J

James Kuyper

Shao Miller said:
If the following program strictly conforming?

#include <stddef.h>
#include <stdarg.h>

static void test(int, ...);

int main(void) {
test(0, 42);

return 0;
}

static void test(int x, ...) {
typedef unsigned char one_char[1];
/* Valid declaration on the next line */
one_char * unused;
va_list varargs;
unsigned char * byte;

va_start(varargs, x);
if (x)
byte = va_arg(varargs, one_char);
va_end(varargs);

return;
}

It looks fine to me. Does some implementation reject it?

The second argument of his call to test() has a type of int. He calls
va_arg() with a type of one_char, which is a typedef for unsigned char.
"if type is not compatible with the type of the actual next argument (as
promoted according to the default argument promotions), the behavior is
undefined ..." (7.15.1.1p2). The promoted type of "int" is "int".
"unsigned char" is not compatible with "int". That clause has some
exceptions, but none of them are applicable.
 
B

Ben Bacarisse

James Kuyper said:
Shao Miller said:
If the following program strictly conforming?

#include <stddef.h>
#include <stdarg.h>

static void test(int, ...);

int main(void) {
test(0, 42);

return 0;
}

static void test(int x, ...) {
typedef unsigned char one_char[1];
/* Valid declaration on the next line */
one_char * unused;
va_list varargs;
unsigned char * byte;

va_start(varargs, x);
if (x)
byte = va_arg(varargs, one_char);
va_end(varargs);

return;
}

It looks fine to me. Does some implementation reject it?

The second argument of his call to test() has a type of int. He calls
va_arg() with a type of one_char, which is a typedef for unsigned
char.

I though his code did not do this. Was that not the point of the
if (x) with x == 0?
 
S

Shao Miller

James Kuyper said:
If the following program strictly conforming?

#include<stddef.h>
#include<stdarg.h>

static void test(int, ...);

int main(void) {
test(0, 42);

return 0;
}

static void test(int x, ...) {
typedef unsigned char one_char[1];
/* Valid declaration on the next line */
one_char * unused;
va_list varargs;
unsigned char * byte;

va_start(varargs, x);
if (x)
byte = va_arg(varargs, one_char);
va_end(varargs);

return;
}

It looks fine to me. Does some implementation reject it?

The second argument of his call to test() has a type of int. He calls
va_arg() with a type of one_char, which is a typedef for unsigned
char.

I though his code did not do this. Was that not the point of the
if (x) with x == 0?

Yes, that was the point, to keep the test-case fairly simple. I gather
that 'printf' [theoretically] uses the passed format string to determine
what kind of 'va_arg' to do each other additional parameter in a loosely
similar fashion, for example.
 
J

James Kuyper

James Kuyper said:
If the following program strictly conforming?

#include <stddef.h>
#include <stdarg.h>

static void test(int, ...);

int main(void) {
test(0, 42);

return 0;
}

static void test(int x, ...) {
typedef unsigned char one_char[1];
/* Valid declaration on the next line */
one_char * unused;
va_list varargs;
unsigned char * byte;

va_start(varargs, x);
if (x)
byte = va_arg(varargs, one_char);
va_end(varargs);

return;
}

It looks fine to me. Does some implementation reject it?

The second argument of his call to test() has a type of int. He calls
va_arg() with a type of one_char, which is a typedef for unsigned
char.

I though his code did not do this. Was that not the point of the
if (x) with x == 0?

You're right - I didn't pay attention to that. Sorry!
 
B

Ben Bacarisse

Shao Miller said:
Shao Miller said:
If the following program strictly conforming?

#include<stddef.h>
#include<stdarg.h>

static void test(int, ...);

int main(void) {
test(0, 42);

return 0;
}

static void test(int x, ...) {
typedef unsigned char one_char[1];
/* Valid declaration on the next line */
one_char * unused;
va_list varargs;
unsigned char * byte;

va_start(varargs, x);
if (x)
byte = va_arg(varargs, one_char);
va_end(varargs);

return;
}

It looks fine to me. Does some implementation reject it?

Yes. At least two of them, and in different ways that I didn't quite
expect.

GCC 4.3.3 says:

test.c: In function 'test':
test.c:21: error: invalid use of non-lvalue array

Where 'va_arg' expands to '__builtin_va_arg'.

gcc 4.6.1 is happy with it...
clang 2.9 says:

test.c:21:16: error: assigning to 'unsigned char *' from
incompatible type 'one_char' (aka 'unsigned char [1]')
byte = va_arg(varargs, one_char);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

.... and my clang version 2.9 (tags/RELEASE_29/final) is also happy with
it.
Where once again, 'va_arg' expands to '__builtin_va_arg'.

My reading of N1256's 7.15.1.1 has me pondering these.

It seems OK to me. va_arg(varargs, one_char) must be an expression of
type one_char. An expression with type "array of T" gets converted to
one of type "pointer to T" so the assignment should type-check. (You
can't execute the line without UB, but presumably that's why you put
the if (x) check there.)

I was briefly concerned that maybe the array-to-pointer conversion might
only apply to lvalue expressions, but it does not -- it applies to any
expression with an array type (other than a string literal or one that
is the operand of sizeof or unary &).
 
S

Shao Miller

Shao Miller said:
If the following program strictly conforming?

#include<stddef.h>
#include<stdarg.h>

static void test(int, ...);

int main(void) {
test(0, 42);

return 0;
}

static void test(int x, ...) {
typedef unsigned char one_char[1];
/* Valid declaration on the next line */
one_char * unused;
va_list varargs;
unsigned char * byte;

va_start(varargs, x);
if (x)
byte = va_arg(varargs, one_char);
va_end(varargs);

return;
}

It looks fine to me. Does some implementation reject it?

Yes. At least two of them, and in different ways that I didn't quite
expect.

GCC 4.3.3 says:

test.c: In function 'test':
test.c:21: error: invalid use of non-lvalue array

Where 'va_arg' expands to '__builtin_va_arg'.

gcc 4.6.1 is happy with it...

Aha. Thanks a lot for checking that!
clang 2.9 says:

test.c:21:16: error: assigning to 'unsigned char *' from
incompatible type 'one_char' (aka 'unsigned char [1]')
byte = va_arg(varargs, one_char);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

... and my clang version 2.9 (tags/RELEASE_29/final) is also happy with
it.

That's odd. I was also using "tags/RELEASE_29/final". I was using
'-ansi -pedantic' as well, so maybe there's a C90 vs. C99 difference in
treatment?
It seems OK to me. va_arg(varargs, one_char) must be an expression of
type one_char. An expression with type "array of T" gets converted to
one of type "pointer to T" so the assignment should type-check. (You
can't execute the line without UB, but presumably that's why you put
the if (x) check there.)

I had the same reasoning, so thank you very much for that. Yes, I
believe the undefined behaviour would be from the 'va_arg' result not
being addressable, so the address of the first element would be
undefined and hence the would-be-resulting pointer value.

Beyond all of that business, I don't know of a way to pass an array to
any function, so the 'one_char' type could never be compatible with any
parameter. But that wasn't the point of the original question, of course.
I was briefly concerned that maybe the array-to-pointer conversion might
only apply to lvalue expressions, but it does not -- it applies to any
expression with an array type (other than a string literal or one that
is the operand of sizeof or unary&).

I had some moments where I shared this same concern and drew the same
conclusion. This seems to be related to something like:

typedef unsigned char one_char[1];
register one_char foo;

where there are now restrictions on the use of 'foo'; 'sizeof' and
'_AlignOf' only, presumably.
 
S

Shao Miller

I had some moments where I shared this same concern and drew the same
conclusion. This seems to be related to something like:

typedef unsigned char one_char[1];
register one_char foo;

where there are now restrictions on the use of 'foo'; 'sizeof' and
'_AlignOf' only, presumably.

Uh. I meant 'alignof'. Oops.
 
S

Shao Miller

I had some moments where I shared this same concern and drew the same
conclusion. This seems to be related to something like:

typedef unsigned char one_char[1];
register one_char foo;

where there are now restrictions on the use of 'foo'; 'sizeof' and
'_AlignOf' only, presumably.

Uh. I meant 'alignof'. Oops.

And I'm wrong, anyway. [Albeit outdated] N1494 doesn't have 'alignof'
applying to identifiers. I forgot about the gripe I had about that in
comp.std.c some time ago.
 
H

Harald van Dijk

If the following program strictly conforming?

   #include <stddef.h>
   #include <stdarg.h>

   static void test(int, ...);

   int main(void) {
       test(0, 42);

       return 0;
     }

   static void test(int x, ...) {
       typedef unsigned char one_char[1];
       /* Valid declaration on the next line */
       one_char * unused;
       va_list varargs;
       unsigned char * byte;

       va_start(varargs, x);
       if (x)
           byte = va_arg(varargs, one_char);

In C90, this is invalid. As your compilers' error messages state, the
standard does not require va_arg's result to be an lvalue, and a non-
lvalue array cannot be used in any way except to discard it, to take
its size, or to uselessly pass it to another variadic function. In
particular, it cannot be converted to a pointer, and its members
cannot be accessed.

In C99 (and presumably C11), this use of va_arg unconditionally
invokes undefined behaviour: there is no longer any way to pass an
array to a variadic function without it getting converted to a pointer
to its first element. A line that unconditionally invokes undefined
behaviour at runtime is technically allowed in a strictly conforming
program if the line is never executed, but this would not be the first
example where compilers divert from the standard in that regard.
 
B

Ben Bacarisse

Shao Miller said:
Shao Miller<[email protected]> writes:
clang 2.9 says:

test.c:21:16: error: assigning to 'unsigned char *' from
incompatible type 'one_char' (aka 'unsigned char [1]')
byte = va_arg(varargs, one_char);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

... and my clang version 2.9 (tags/RELEASE_29/final) is also happy with
it.

That's odd. I was also using "tags/RELEASE_29/final". I was using
-ansi -pedantic' as well, so maybe there's a C90 vs. C99 difference in
treatment?

So it would seem. Error with -std=C90, fine with -C99. The C90 wording
is:

"The va_arg macro expands to an expression that has the type and value
of the next argument in the call."

which I find very strange!

<snip>
 
S

Shao Miller

If the following program strictly conforming?

#include<stddef.h>
#include<stdarg.h>

static void test(int, ...);

int main(void) {
test(0, 42);

return 0;
}

static void test(int x, ...) {
typedef unsigned char one_char[1];
/* Valid declaration on the next line */
one_char * unused;
va_list varargs;
unsigned char * byte;

va_start(varargs, x);
if (x)
byte = va_arg(varargs, one_char);

In C90, this is invalid. As your compilers' error messages state, the
standard does not require va_arg's result to be an lvalue, and a non-
lvalue array cannot be used in any way except to discard it, to take
its size, or to uselessly pass it to another variadic function. In
particular, it cannot be converted to a pointer, and its members
cannot be accessed.

Aha! Thanks very much. Indeed, the result is a "value" rather than
designating an "object."

That such a value can be passed to another function is interesting,
though... I suppose it could be passed to a non-variadic function whose
parameters aren't specified, too.
In C99 (and presumably C11), this use of va_arg unconditionally
invokes undefined behaviour: there is no longer any way to pass an
array to a variadic function without it getting converted to a pointer
to its first element.

Out of curiosity, how could you pass an array pre-C99? Or do you mean
for the strict case where the expression is an array "value" without
being an lvalue, that you described above?

The program doesn't attempt to pass an array to the 'test' function, by
the way. So I think by "this use," you were referring to a construct
which didn't appear in the program, but which might be associated with
it. Or did you really mean a construct in the program?
A line that unconditionally invokes undefined
behaviour at runtime is technically allowed in a strictly conforming
program if the line is never executed, but this would not be the first
example where compilers divert from the standard in that regard.

I see. Well thanks again!
 
H

Harald van Dijk

Out of curiosity, how could you pass an array pre-C99?  Or do you mean
for the strict case where the expression is an array "value" without
being an lvalue, that you described above?

Right, this is a program that I believe to strictly conform to the C90
standard:

#include <stdarg.h>
typedef char array[10];
struct S { array array; };
struct S foo(void) { static struct S s; return s; }
int bar(int x, ...)
{
int result;
va_list ap;
va_start(ap, x);
va_arg(ap, array);
result = va_arg(ap, int);
va_end(ap);
return result;
}
int main(void)
{
return bar(0, foo().array, 0);
}

It's useless, but it's valid, and C90's va_arg is (strictly speaking:
for practical purposes nobody really cares) required to skip over the
array to correctly read the next passed argument.
The program doesn't attempt to pass an array to the 'test' function, by
the way.  So I think by "this use," you were referring to a construct
which didn't appear in the program, but which might be associated with
it.  Or did you really mean a construct in the program?

I meant the use of va_arg: if its second argument has array type, it
cannot possibly be compatible with the type of the passed argument in
C99, no matter how you call the function. If you pass an array, even
an array rvalue, it gets converted to a pointer, so va_arg's second
argument should be a pointer type.
 
H

Harald van Dijk

Aha!  Thanks very much.  Indeed, the result is a "value" rather than
designating an "object."

That such a value can be passed to another function is interesting,
though...

But be careful: the standard doesn't require va_arg's result to be an
lvalue, but does allow it. If it is an lvalue, then it will get
converted to a pointer if you pass it on to another function!
 I suppose it could be passed to a non-variadic function whose
parameters aren't specified, too.

Perhaps, but a non-variadic function cannot have a definition allowing
arguments of array type (parameters of array type are converted to
pointers too), so again, the behaviour would again always be
undefined, no matter how the function is defined.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top