указатель на 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