Сопоставление типов va_list между компиляторами

У меня есть проект, состоящий из набора динамически загружаемых модулей. Первоначально все всегда собиралось с помощью MSVC 2003, но в последнее время я работаю над тем, чтобы заставить его работать с GCC. Все прошло довольно гладко, за исключением одной проблемы. Для 64-битного кода GCC и MSVC не согласны с тем, что такое va_list. Для 32-битной все выглядит нормально. Проблема, вызванная 64-битным несоответствием, заключается в том, что модуль, созданный одним компилятором, имеет общедоступную функцию с параметром va_list, и эта функция вызывается из модуля, созданного другим компилятором.

Спецификация ничего не говорит о том, что такое va_list, кроме Раздел 7.15 Переменные аргументы <stdarg.h>, параграф 3:

Объявленный тип

va_list

который является типом объекта, подходящим для хранения информации, необходимой для макросов va_start, va_arg, va_end и va_copy.

Этот абзац просто означает, что все это зависит от компилятора - так есть ли способ заставить эти два компилятора согласовать содержимое 64-битного va_list? Для наименьшего воздействия на мою систему лучше всего сделать так, чтобы GCC соответствовал MSVC va_list, но я приму любое решение, которое смогу найти.

Спасибо за помощь!

Редактировать:

Я провел некоторое 32-битное тестирование, и у меня тоже есть проблемы, что меня удивило, так как якобы нет различий в ABI между любыми 32-битными платформами Intel. Кодовая база MSVC, которую я использую, определяет все макросы функций с переменным числом переменных следующим образом:

typedef char *va_list;
#define intsizeof(n)    ((sizeof(n) + sizeof(int) - 1) &~(sizeof(int) - 1))
#define va_start(ap, v) (ap = (va_list)&(v) + intsizeof(v))
#define va_arg(ap, t)   (*(t *) ((ap += intsizeof(t)) - intsizeof(t)))
#define va_end(ap)      (ap = (va_list)0)

Я немного упростил реальный проект, но это код, который я использовал для своего теста. С GCC этот код определенно неправильно получает мои аргументы. Может быть, это просто ошибка, как предполагает Зак ниже?

Редактировать снова:

Я получаю рабочие результаты для следующего 32-битного тестового приложения с -O0, -O0 и -O2, но не с -O3, -Os и -Oz:

typedef char *va_list;
#define intsizeof(n)    ((sizeof(n) + sizeof(int) - 1) &~(sizeof(int) - 1))
#define va_start(ap, v) (ap = (va_list)&(v) + intsizeof(v))
#define va_arg(ap, t)   (*(t *) ((ap += intsizeof(t)) - intsizeof(t)))
#define va_end(ap)      (ap = (va_list)0)

int printf(const char *format, ...);

int f(int n, ...)
{
  int r = 0;
  va_list ap;

  va_start(ap, n);  
  while (n--)
    r = va_arg(ap, int);
  va_end(ap);

  return r;
}

int main(int argc, char **argv)
{
  int r;

  r = f(1, 1, 2, 3, 4, 5);
  printf("%x\n", r);

  r = f(2, 1, 2, 3, 4, 5);
  printf("%x\n", r);

  r = f(3, 1, 2, 3, 4, 5);
  printf("%x\n", r);

  r = f(4, 1, 2, 3, 4, 5);
  printf("%x\n", r);

  r = f(5, 1, 2, 3, 4, 5);
  printf("%x\n", r);

  return 0;
}

person Carl Norum    schedule 29.09.2010    source источник
comment
Эм-м-м. Вы скопировали определения va_* из <stdarg.h> MSVC в файл, который затем скомпилировали с помощью GCC, не так ли? Потому что это точно не сработает и ничего полезного вам не скажет. Вы обязательно должны использовать <stdarg.h> GCC для определения функций с переменным числом аргументов, скомпилированных с помощью GCC (и MSVC для MSVC), иначе ваш код будет скомпилирован неправильно.   -  person zwol    schedule 30.09.2010
comment
Что вам нужно сделать для этого теста, так это переместить f в его собственный файл, заменить все определения рук va_* на #include <stdarg.h>, поместить extern int f(int n, ...); выше main в этом файле, скомпилировать один с помощью GCC, а другой с помощью MSVC и связать два объектных файла. . Это должно работать в любом направлении (MSVC вызывает GCC или GCC вызывает MSVC) либо на x32, либо на x64.   -  person zwol    schedule 30.09.2010
comment
Да, он отлично работал при отключении встраивания или при помещении его в отдельный модуль компиляции. В любом случае, это совершенно отдельная проблема от несоответствия типов va_list, что, как вы говорите, является ошибкой компилятора.   -  person Carl Norum    schedule 30.09.2010


Ответы (5)


Поскольку MSVC определяет ABI Win64, вы обнаружили ошибку в GCC. Сообщите об этом в GCC bugzilla.

person zwol    schedule 29.09.2010
comment
va_list не является частью ABI, не так ли? В любом случае, мой долгосрочный план состоит в том, чтобы использовать clang/llvm, поэтому я думаю, что мне следует проверить это раньше, чем позже. Вполне возможно, что с GCC все в порядке в более поздних версиях - в этом случае я застрял на своего рода «особом» старом GCC. - person Carl Norum; 30.09.2010
comment
Положительно va_list является частью ABI. Цель ABI — гарантировать, что все компиляторы для данного целевого ЦП+ОС будут генерировать совместимый код, включая функции с переменным числом аргументов. ... Я видел кучу исправлений поддержки Win64 для GCC, выпущенных совсем недавно, так что это вполне может работать с текущим выпуском или, по крайней мере, с деревом разработки. - person zwol; 30.09.2010
comment
@ Зак, функции с переменным числом аргументов работают нормально - просто передача самого va_list не работает. Спасибо за проверку работоспособности - я проведу еще несколько тестов и посмотрю, что происходит. - person Carl Norum; 30.09.2010
comment
Это тоже должно работать - рассмотрим vfprintf и подобные, но есть некоторые подводные камни. Попробуйте взять адрес объекта va_list и передать его вместо передачи самого объекта. (Короткая версия подводных камней: после того, как вы передаете объект va_list по значению в другую функцию, он находится в неопределенном состоянии в вызывающей программе, и единственная переносимая вещь, которую можно сделать с ним, — это немедленно вызвать для него va_end. Это не так. если вместо этого вы передадите указатель на объект.) - person zwol; 30.09.2010
comment
Спасибо @Зак. Я только что сделал несколько 32-битных тестов, и там тоже есть несоответствие. Я много читал ABI и документацию по соглашению о вызовах, и я нигде не вижу ничего явного о va_list. Что касается MSVC, va_list в любом случае является типом указателя, поэтому он все равно передается по ссылке, верно? - person Carl Norum; 30.09.2010
comment
va_list действительно является указателем во всех ABI x86-32, но я не знаю, есть ли он еще в Win64 (его нет в x86-64/ELF). 32-битные тесты должны были работать. - person zwol; 30.09.2010
comment
О, и... передача указателя указателю - это совсем не то же самое, что передача указателя. - person zwol; 30.09.2010
comment
@ Зак - о, теперь я понимаю, что ты имеешь в виду. Я постараюсь придумать меньший тестовый пример, чтобы попробовать это. - person Carl Norum; 30.09.2010
comment
@Zack - спасибо за помощь и извините за этот обход IA32. Я попробовал clang/llvm, и все работает нормально, так что это определенно указывает на ошибку в компиляторе. Я подам документы соответствующим людям. - person Carl Norum; 30.09.2010

Поскольку для va_list не существует ABI (или, по крайней мере, MSVC и GCC не согласны с ABI), вам, вероятно, придется маршалировать эти параметры самостоятельно.

Самый простой способ, который я могу придумать, чтобы обойти эту проблему, — это маршалировать ваши переменные параметры в динамически выделяемом блоке памяти и передавать указатель на этот блок.

Конечно, недостатком этого является полное изменение интерфейса функций, которые в настоящее время используют va_args.

person Michael Burr    schedule 29.09.2010
comment
Но мне нужно знать, сколько параметров есть, чтобы вставить их в динамический блок. Это означает, что каждая функция, которая хочет выполнить va_start, а затем вызвать другую функцию с va_list, должна знать, как их идентифицировать. состоит в том, чтобы объединить весь анализ этой строки формата в одной функции (например, vsprintf). - person Carl Norum; 30.09.2010

Поскольку не существует стандарта того, как должны обрабатываться va_args, если вам нужно, чтобы эта функциональность была согласованной с кросс-компилируемой платформой, вам, вероятно, лучше создать свою собственную версию. Мы этого не делали и в последнее время несколько раз сгорали из-за поддержки дополнительных целей для нашей кодовой базы. Я хотел бы ошибаться, если у других есть лучшее решение :)

person Michael Dorgan    schedule 29.09.2010
comment
Хорошо, а как мне запустить свою собственную версию? Я написал один, который работает для IA32, но gcc, похоже, не раскрывает аргументы так, как я хочу, для Win64. - person Carl Norum; 29.09.2010

Попробуйте запустить его через отладчик уровня сборки, такой как ollydbg, так как ваша проблема может быть не с va_args, а скорее с тем, как компилятор ожидает передачи аргументов (gcc может ожидать их в формате linux, где как msvc использует win64 __fastcall), во всяком случае, это прояснит ситуацию. Другой, довольно хакерский подход — попробовать поиграть с определениями, используемыми для 64-битных аргументов, например, импортировать макросы msvc в заголовок gcc (конечно, используйте локальную копию проекта), посмотрите, исправит ли это что-нибудь.

person Necrolis    schedule 29.09.2010
comment
Вариативные вызовы функций между модулями, созданными с помощью разных компиляторов, поэтому ABI совпадают. Это просто фактическая внутренняя реализация va_list, которая меня кусает. - person Carl Norum; 30.09.2010

Какие типы вы используете? Строго говоря, WinXX определяет поведение только символов, строк, указателей и целых чисел для varargs (см. документацию по wsprintf в user32.dll). Таким образом, если вы передаете значения или структуры с плавающей запятой, результаты технически не определены для платформы Windows.

person MSN    schedule 29.09.2010
comment
В этом проекте нет плавающей запятой, так что все должно быть в порядке. Я был бы удивлен, если бы структуры разбрасывались, но я могу разобраться в этом. Как правило, печатаются только целые типы и строки. - person Carl Norum; 30.09.2010
comment
Я думаю, что этот ответ просто неверен, кстати... что user32.dll версия wsprintf не упоминает %f и друзей в своей документации не означает, что вы не можете передавать числа с плавающей запятой через varargs на вин32 вообще. И стандарт C требует для работы передачи как плавающей запятой, так и структур через varargs. - person zwol; 30.09.2010
comment
@ Зак, я обновил свой ответ, чтобы отразить, что это не указано для платформы Windows. Это означает, что вы не должны передавать эти типы в wsprintf(...), которая является функцией user32.dll. Это ничего не значит для языка, реализованного поверх Windows, такого как C или C++. Поэтому, если вам нужна совместимость между компиляторами, вы должны придерживаться типов, которые платформа заставляет вас поддерживать. В этом случае вам повезло, что wsprintf был экспортирован из user32.dll. - person MSN; 30.09.2010
comment
@MSN: WinXX не может претендовать на то, чтобы быть соответствующей реализацией C (и Microsoft определенно делает это заявление), если она не поддерживает передачу типов и структур с плавающей запятой через varargs. И, если где-то нет другой реализации wsprintf, которую все получают вместо этой, эта, вероятно, действительно поддерживает плавающую точку, и вы основываете все свои утверждения на упущении в документации. Я отправил запрос по этому поводу в их адрес для обратной связи с документацией. - person zwol; 30.09.2010
comment
@Zack, WinXX не является соответствующей реализацией C. Существуют привязки для языка C, семантика которых определяется платформой WinXX. wsprintf — это одна из тех функций, которые используют varargs, которые указаны для приема только указателей, строк и целых чисел. Если вы погуглите wsprintf windows с плавающей запятой, вы увидите, что он не поддерживает значения с плавающей запятой. По сути, это устаревшая функция, но она неявно указывает ABI для varargs для целых чисел и указателей. Стандарт C не определяет конкретный ABI для varargs на любой платформе, если вам интересно. - person MSN; 30.09.2010
comment
@ Зак, теперь я понимаю, что ты имеешь в виду. x64 ABI технически определен здесь (msdn.microsoft.com/en-us/ library/dd2wa36c.aspx), хотя мне не удалось найти канонический ABI для Windows x64. Я полагаю, что Visual C++ является каноническим. - person MSN; 30.09.2010