указател към va_list в amd64 ABI

Имам притеснения относно различни функции под Linux amd64 (x86_64).

Моят пример компилира и работи добре на linux i386 (ia32), но когато е създаден за linux amd64, GCC създава такива грешки:

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 **’

Ето примера:

#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;
}

Според C11 чернова (ISO/IEC 9899:2011)

Обектът ap може да бъде предаден като аргумент на друга функция; ако тази функция извиква макроса va_arg с параметър ap, стойността на ap в извикващата функция е неопределена и трябва да бъде предадена на макроса va_end преди всяка по-нататъшна препратка към ap.

Но последното добавяне

Разрешено е да се създаде указател към va_list и да се предаде този указател на друга функция, в който случай оригиналната функция може да използва допълнително оригиналния списък, след като другата функция се върне.

Не ми е ясно дали AMD 64 ABI е погрешно тук по отношение на стандарта.

Промяната на функцията vtest() да използва указател при първо извикване коригира проблема, но се чувства погрешно нещо да не работи във вътрешните функции, а всъщност да работи във външната функция.

@@ -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);

Ако някой може да намери някъде дали поведението на AMD64 ABI съответства на стандарта. Допълнителни точки за хора, които ми предоставят други ABI със (същия) проблем при използването на stdarg.

за разбирането


person Yann Droneaud    schedule 20.03.2012    source източник
comment
възможен дубликат на неправилно боравене ли е с GCC указател към va_list, предаден на функция?   -  person Michael Burr    schedule 20.03.2012
comment
@MichaelBurr да, това е негов дубликат. Всъщност stackoverflow.com/a/8048892/611560 е отговорът на моя въпрос.   -  person Yann Droneaud    schedule 21.03.2012


Отговори (1)


Поведението е напълно съвместимо, защото въпреки че аргументът за vtest е написан като va_list ap, ap няма тип va_list, а по-скоро какъвто и да е тип указател va_list, в който се разпада. Това е съвместимо, защото va_list е позволено от стандарта да бъде тип масив. Решението на този проблем е да използвате va_copy за копиране на ap в локален va_list:

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

Тъй като дефиницията и типът на ap2 са под ваш контрол, &ap2 има правилния тип за предаване на vptest.

person R.. GitHub STOP HELPING ICE    schedule 20.03.2012
comment
Трудно е да се повярва, че ap няма тип va_list, когато е аргумент. - person Yann Droneaud; 21.03.2012
comment
Връзката от @michael-burr е интересна в това отношение stackoverflow.com/a/8047513/611560 - person Yann Droneaud; 21.03.2012
comment
използването на va_copy() по този начин вероятно разчита на неопределено поведение (т.е. дали е внедрено като макрос или функция)... - person Christoph; 21.03.2012
comment
@ydroneaud: Същото се случва, ако typedef char foo[1]; и използвате foo като тип аргумент. Случва се и с jmp_buf, но вие очаквате това, тъй като jmp_buf е дефиниран като тип масив. - person R.. GitHub STOP HELPING ICE; 21.03.2012
comment
@R..: va_copy очаква вторият аргумент да бъде от тип va_list, което не е така, ако ap е параметър поради корекции на типа; va_copy се влияе по същия начин като оригиналния код; както е посочено, stdarg.h не се справя елегантно с va_list с тип масив; Подозирам, че използването на va_copy ще работи на практика (очевидният начин за внедряване на va_copy ще работи както с реален, така и с коригиран тип), но за да бъдете напълно преносими, имате нужда от сонда за време за конфигуриране и условно компилиран код на HAVE_VA_LIST_AS_ARRAY; - person Christoph; 21.03.2012
comment
Подозирам, че не е имало общи реализации, където va_list има тип масив, когато тази част от стандарта е формулирана; общата практика да се използват масиви с дължина 1 за автоматично преминаване по указател очевидно беше известна по това време (jmp_buf идва на ум), но не и в този контекст -- докато спецификацията изрично поддържа разпределена памет (което дори се споменава в C99 обосновка и причината, поради която пропускането на va_end може да доведе до изтичане на памет), то е дефектно в случай на типове масиви... - person Christoph; 21.03.2012
comment
Ако va_list има някакво състояние извън това да бъде един указател на стека, е необходимо или да се предаде структура по стойност (вероятно много скъпо), или да се дефинира va_list като тип масив. Със сигурност комисията е обмислила възможността това да е тип масив (ето защо е недефинирано достъпът до va_list в повикващия, след като извикваният го е използвал; при нормални реализации ще бъде или в първоначалното състояние, или в крайното състояние в зависимост от това дали това е тип масив). - person R.. GitHub STOP HELPING ICE; 21.03.2012
comment
Освен това няма несъответствие на типа при предаване на ap към функция, независимо дали va_list е тип масив, тъй като типът масив, до който се разпада, е точно типа масив, който функцията очаква. Проблемният случай е предаване на &ap на функция, която очаква va_list *. Това не е ситуацията с va_copy, което отнема va_list, а не va_list *. - person R.. GitHub STOP HELPING ICE; 21.03.2012
comment
@R..: случаят, който беше разгледан от комисията (документиран в обосновката), беше, че va_list сочи към разпределена памет, което има същите последици по отношение на състоянието като използването на тип масив, но допълнително изисква va_end за освобождаване на паметта; може да се твърди, че фактът, че va_copy трябва да приема както реален, така и коригиран тип, се подразбира, но подозирам, че просто не е взето предвид; тъй като обикновен #define va_copy(DEST, SRC) (*(DEST) = *(SRC)) работи с изрази както от тип масив, така и от тип указател, това не е проблем на практика, но спецификацията е съмнителна... - person Christoph; 21.03.2012
comment
нека да продължим тази дискусия в чата - person Christoph; 21.03.2012