Как переменные аргументы реализованы в 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);
}