Еще до стандартизации методы использования переменных аргументов были распространены. Стандарт C89 дал ... стандартизированный интерфейс, основанный на существующих практиках. Остатки этой практики все еще существуют на месте, например, <varargs.h>
все еще присутствовал в Unix98: http://www.opengroup.org/onlinepubs/007908799/xsh/varargs.h.html (но больше не присутствует в текущей версии)
Реализация макросов всегда сильно зависела от системы (есть стеки, растущие в обоих направлениях, есть даже системы, использующие связанный список в качестве стека, положение различных вещей в кадре стека зависит от процессора и общего соглашения, на некоторых процессорах, - говорит Sparc, - сначала нужно сохранить регистры, требования к выравниванию могут вызвать проблемы, ...)
Если вы хотите знать, как будет выглядеть простая реализация, вот одна, в зависимости от предположений, вероятно, ложных (они не пытаются получить правильное выравнивание), а также, конечно, неудачных некоторых угловых случаев, даже когда предположения выполнены:
typedef void* va_list;
#define va_start(va, arg) va = (void*)((&arg)+1)
#define va_arg(va, type) (va = (void*)(((type*)va) + 1), *((type*)va -1)
#define va_end(va)