2013-03-29 3 views
1

Я пытаюсь выяснить, что стоит за макросами va_start(), va_arg(). Код ниже работает хорошо.c переменные функции путаницы

#include <iostream> 
#include <cstdarg> 

void f(double a, double b, ...) 
{ 
    va_list arg; 
    va_start(arg, b); 
    double d; 
    while((d = va_arg(arg, double)) != 0) 
     { 
     std::cout << d << '\n'; 
     } 
} 

int main(int argc, char *argv[]) 
{ 
    f(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0); 
    return 0; 
} 

Как я ожидал, что он дал такой результат: 3 4 5 6 7 8 9. Тогда я нашел определение, что макросы (в Интернете, потому что мой заголовок stdarg.h загадочно - он определяет макросы va_arg (v, l), как _builtin_va_arg (v, l), последнее не определено в нем, а stdarg.h ничего не содержит, так же как в некоторой библиотеке ???). Тем не менее, вместо "cstdarg" я писал:

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) 

Выход стал странно, такие как 1 -0,0409377 -0,0409377 4.88084e-270 4.85706e-270 9. Я думал, что вариационные параметры расположены рядом с последним объявленным параметром, но, по-видимому, существует более сложная ситуация. Я был бы очень доволен, если кто-нибудь представит, где я ошибаюсь или что на самом деле происходит там.

+0

Я не упоминал об этом раньше, но рукописные макросы отлично работают с типом int, то есть все параметры помещаются как ожидалось относительно последнего заданного аргумента. В случае двойного типа параметры также находятся в стеке, но нечетным образом, а именно я мог бы получить правильный результат, добавляя начальные шесть к указателю на последний аргумент. Это какая-то прокладка? Более того, я обнаружил, что определенные аргументы существуют дважды.Вы можете видеть, что он ставит указатель на первый аргумент. Вы получите их, затем четыре цифры, а затем все параметры по одному. – Nephew

+0

Я понимаю, что это далеко от полезности, чтобы это знать, но все же любопытно. Благодарю. – Nephew

ответ

1

va_start, va_arg и не забывайте, что va_end являются специфическими для компилятора. Вы не можете просто взять их из другого места и ожидать, что они будут работать. Вам лучше следовать странице руководства, используя их, и только попытайтесь понять их внутреннюю работу, если вы являетесь инженером-компилятором.

P.S: О, и их определения обычно очень таинственны, используя тонкие трюки, чтобы заставить его работать.

P.S2: В ответ на ваш вопрос, где определен _builtin_va_arg: он известен компилятору, так называемому builtin. Вы найдете его в источниках компилятора;)

+1

Чтобы быть понятным, вы, вероятно, не найдете функцию * с именем '_builtin_va_arg' в источниках компилятора. То, что вы найдете, это код, который распознал ключевое слово, определяемое реализацией, и делает с ним соответствующую вещь. Он встроен в компилятор почти так же, как и операторы '+' и 'sizeof'. –

+0

Да, я не могу найти это действительно. Спасибо, что намек на это. – Nephew

1

Ваши рукописные макросы будут работать на машине, которая всегда передает все аргументы в стеке и накладывает меньшие типы на sizeof (int). Однако многие машины (большинство из этих дней?) Не передают аргументы в стеке - они вместо этого передают их в регистры и используют стек только в том случае, если в регистрах слишком много.

Итак, чтобы иметь дело с va_args, компилятор должен знать ABI и какие аргументы будут размещены там, где в каких обстоятельствах. Обычно делается то, что va_list содержит несколько массивов (достаточно для хранения всех регистров, которые могут содержать аргументы) и нескольких указателей (как правило, для каждого типа регистра и для стека. Va_start сбрасывает все аргумент регистрируется в массивах и инициализирует указатели, а затем va_arg определяет, какой тип регистра будет передан в этот тип аргумента, и вытащит значение из соответствующего места. Таким образом, для гипотетического процессора с 8 регистрами для целых/указательных аргументов и 8 регс для поплавков/двойных аргументов, вы могли бы иметь что-то вроде:

typedef struct { 
    intptr_t iregs[8], *iptr; 
    double fregs[8], *fptr; 
    char  *spptr; 
} va_list; 

inline void _builtin_va_start(va_list &ap, arg) { 
    // dump the registers might be used to pass args into ap->iregs and ap-fregs, 
    // setup iptr and fptr to point into iregs and fregs after the arguments that 
    // correspond to 'arg' and those before it. spptr points into the stack space 
    // used for arguments after regs run out 
} 
inline _builtin_va_arg(va_list &ap, type) { 
    if (type is integer or pointer) { 
     if (ap->iptr == ap->iregs+8) { 
      rv = *(type *)ap->spptr; 
      ap->spptr += sizeof(type); 
     } else { 
      rv = *ap->iptr++; 
     } 
    } else if (type is float or double) { 
     if (ap->fptr == ap->fregs+8) { 
      rv = *(type *)ap->spptr; 
      ap->spptr += sizeof(type); 
     } else { 
      rv = *ap->fptr++; 
     } 
    } else { 
     // some other type (struct?) deal with it 
    } 
} 

Обратите внимание, что ни один из этих _builtin_va функций может быть написано на C, они должны быть встроены в компилятор

+0

Спасибо за ваш ответ (к сожалению, я еще не могу проголосовать)! – Nephew

Смежные вопросы