Как переменные аргументы реализованы в gcc?

int max(int n, ...)

Я использую соглашение о вызове cdecl, когда вызывающий объект очищает переменную после возвращения вызываемого абонента.

Мне интересно узнать, как работают макросы va_end, va_start и va_arg?

Пропускает ли вызывающий объект в адресе массива аргументов как второй аргумент max?

Ответ 1

Если вы посмотрите, как язык C хранит параметры в стеке, способ работы макросов должен стать ясным: -

Higher memory address    Last parameter
                         Penultimate parameter
                         ....
                         Second parameter
Lower memory address     First parameter
       StackPointer  ->  Return address

(обратите внимание, что в зависимости от аппаратного обеспечения указатель стека может содержать одну строку вниз, а верхнюю и нижнюю можно заменить)

Аргументы всегда сохраняются как 1 даже без типа параметра ....

Макрос va_start просто устанавливает указатель на первый параметр функции, например: -

 void func (int a, ...)
 { 
   // va_start
   char *p = (char *) &a + sizeof a;
 }

который указывает, что p указывает на второй параметр. Макрос va_arg делает следующее: -

 void func (int a, ...)
 { 
   // va_start
   char *p = (char *) &a + sizeof a;

   // va_arg
   int i1 = *((int *)p);
   p += sizeof (int);

   // va_arg
   int i2 = *((int *)p);
   p += sizeof (int);

   // va_arg
   long i2 = *((long *)p);
   p += sizeof (long);
 }

Макрос va_end просто устанавливает значение p в NULL.

ПРИМЕЧАНИЯ:

  • Оптимизация компиляторов и некоторых RISC-процессоров хранят параметры в регистрах, а не используют стек. Наличие параметра ... отключит эту способность и для компилятора использовать стек.

Ответ 2

Как аргументы передаются в стеке, функции va_ "(они большую часть времени реализованы как макросы) просто манипулируют указателем частного стека. Этот частный указатель стека хранится из аргумента, переданного в va_start, а затем va_arg" выдает "аргументы из" стека", когда он выполняет итерацию параметров.

Предположим, вы вызываете функцию max с тремя параметрами, например:

max(a, b, c);

Внутри функции max стек в основном выглядит следующим образом:

      +-----+
      |  c  |
      |  b  |
      |  a  |
      | ret |
SP -> +-----+

SP - это реальный указатель на стек, и это не действительно a, b и c, что в стеке, но их значения. ret - это адрес возврата, куда нужно перейти к выполнению функции.

Что va_start(ap, n) does принимает адрес аргумента (n в прототипе функции), и из него вычисляется позиция следующего аргумента, поэтому мы получаем новый указатель частного стека:

      +-----+
      |  c  |
ap -> |  b  |
      |  a  |
      | ret |
SP -> +-----+

Когда вы используете va_arg(ap, int), он возвращает то, на что указывает указатель частного стека, а затем "всплывает" его, изменяя указатель частного стека, чтобы теперь указывать на следующий аргумент. Стек теперь выглядит следующим образом:

      +-----+
ap -> |  c  |
      |  b  |
      |  a  |
      | ret |
SP -> +-----+

Это описание, конечно, упрощено, но показывает принцип.

Ответ 3

int max(int n, const char *msg,...)
{
va_list args;
char buffer[1024];
va_start(args, msg);
nb_char_written = vsnprintf(buffer, 1024, msg, args);
va_end(args);
printf("(%d):%s\n",n,buffer);
}