Yann Droneaud
Yann Droneaud

Reputation: 5493

pointer to a va_list in amd64 ABI

I have a concern about variadic functions under Linux amd64 (x86_64).

My example build and work fine on linux i386 (ia32), but when built for linux amd64, GCC produces such errors:

stdarg.c: In function ‘vtest’:
stdarg.c:21:5: attention : passing argument 2 of ‘vptest’ from incompatible pointer type [enabled by default]
stdarg.c:5:1: note: expected ‘struct __va_list_tag (*)[1]’ but argument is of type ‘struct __va_list_tag **’

Here the example:

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

static int
vptest(int count, va_list *a)
{
  printf("%8s:   a = %p\n", __func__, a);
  printf("%8s:   %d: %d\n", __func__, count, va_arg(*a, int));
  return 0;
}

static int
vtest(int count, va_list ap)
{
  printf("%8s: &ap = %p\n", __func__, &ap);

  /* passing a pointer to ap allows ap to be used again in the calling function */
  for(; count > 1; count --) {
    vptest(count, &ap);
  }
  if (count) {
    printf("%8s:   %d: %d\n", __func__, count, va_arg(ap, int));
  }
  return 0;
}

static
int test(int count, ...)
{
  va_list ap;

  va_start(ap, count);
  printf("%8s: &ap = %p\n", __func__, &ap);

  /* after passing ap to subfunction, this function must not use ap again without calling va_start */
  vtest(count, ap);

  va_end(ap);

  return 0;
}

int
main(void)
{
  test(4,
       1, 2, 3, 4);

  return 0;
}

According to a C11 draft (ISO/IEC 9899:2011)

The object ap may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the value of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap.

But latter add

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.

It's not clear to me if the AMD 64 ABI is wrong here regarded to the standard.

Changing the function vtest() to use a pointer at first call fixes the problem, but it feels wrong to have something not working in inner functions actually works in outer function.

@@ -12,16 +12,16 @@
 }

 static int
-vtest(int count, va_list ap)
+vtest(int count, va_list *a)
 {
-  printf("%8s: &ap = %p\n", __func__, &ap);
+  printf("%8s:   a = %p\n", __func__, a);

   /* passing a pointer to ap allows ap to be used again in the calling function */
   for(; count > 1; count --) {
-    vptest(count, &ap);
+    vptest(count, a);
   }
   if (count) {
-    printf("%8s:   %d: %d\n", __func__, count, va_arg(ap, int));
+    printf("%8s:   %d: %d\n", __func__, count, va_arg(*a, int));
   }

   return 0;
@@ -37,7 +37,7 @@
   printf("%8s: &ap = %p\n", __func__, &ap);

   /* after passing ap to subfunction, this function must not use ap again without calling va_start */
-  vtest(count, ap);
+  vtest(count, &ap);

   va_end(ap);

If someone could find somewhere if AMD64 ABI behavor is matching the standard. Additional points for people who provide me others ABI with the (same) problem on stdarg usage.

Regards

Upvotes: 4

Views: 1866

Answers (1)

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215627

The behavior is perfectly conformant, because despite the argument to vtest being written as va_list ap, ap does not have type va_list but rather whatever pointer type va_list decays into. This is conformant because va_list is allowed by the standard to be an array type. The solution to this problem is to use va_copy to copy ap into a local va_list:

va_list ap2;
va_copy(ap2, ap);
// ...
vptest(count, &ap2);
// ...
va_end(ap2);

Since the definition and type of ap2 are under your control, &ap2 has the correct type to pass to vptest.

Upvotes: 3

Related Questions