Использование списка переменных аргументов является стандартной функцией языка 'C', и поэтому должно применяться на любом компьютере, для которого существует компилятор C.
Когда мы говорим о любой машине, мы имеем в виду, что независимо от способа, используемого для передачи параметров, регистров, стека или того и другого, у нас должна быть функция.
По сути, для реализации функциональности действительно требуется детерминистическая природа процесса . Это не имеет значения, если параметры передаются в стеке, регистре, обоими или другими пользовательскими способами MCU, важно то, что способ, которым это делается, четко определено и всегда одинаково .
Если это свойство соблюдается, мы уверены, что всегда сможем просмотреть список параметров и получить доступ к каждому из них.
Фактически метод, используемый для передачи параметров для каждой машины или системы, указан в ABI ( A , B inary I nterface, см. https://en.wikipedia.org/wiki/Application_binary_interface), следуя правилам, в обратном порядке вы всегда можете вернуться к параметрам.
В любом случае, в некоторой системе, в подавляющем большинстве, простой реверс-инжиниринг ABI недостаточен для восстановления параметров, то есть размеров параметров, отличающихся от стандартного размера регистра / стека ЦП, в этом случае вам нужно больше информации о параметре, который вы ищем: размер операнда .
Давайте рассмотрим обработку параметров переменной в C. Сначала вы объявляете функцию, имеющую один параметр целочисленного типа, содержащий количество параметров, переданных как аргументы переменной, и 3 точки для переменной части:
int foo(int cnt, ...);
Для обычного доступа к переменным аргументам вы используете определения в заголовке <stdarg.h>
следующим образом:
int foo(int cnt, ...)
{
va_list ap; //pointer used to iterate through parameters
int i, val;
va_start(ap, cnt); //Initialize pointer to the last known parameter
for (i = 0; i > cnt; i++)
{
val = va_arg(ap, int); //Retrieve next parameter using pointer and size
printf("%d ", val); // Print parameter, an integer
}
va_end(ap); //Release pointer. Normally do_nothing
putchar('\n');
}
На стековой машине (т.е. x86-32bit), где параметры передаются последовательно, приведенный выше код работает более или менее следующим образом:
int foo(int cnt, ...)
{
char *ap; //pointer used to iterate through parameters
int i, val;
ap = &cnt; //Initialize pointer to the last known parameter
for (i = 0; i > cnt; i++)
{
/*
* We are going to update pointer to next parameter on the stack.
* Please note that here we simply add int size to pointer because
* normally the stack word size is the same of natural integer for
* that machine, but if we are using different type we **must**
* adjust pointer to the correct stack bound by rounding to the
* larger multiply size.
*/
ap = (ap + sizeof(int));
val = *((int *)ap); //Retrieve next parameter using pointer and size
printf("%d ", val); // Print parameter, an integer
}
putchar('\n');
}
Обратите внимание, что если мы обращаемся к типам, отличным от int
e / o, размер которых отличается от размера слова собственного стека, указатель должен быть отрегулирован так, чтобы всегда увеличиваться кратным размеру слова стека .
Теперь рассмотрим машину, которая использует регистры для передачи параметров, для простоты мы считаем, что ни один операнд не может быть больше, чем размер регистра, и что распределение выполняется с использованием регистров последовательно (также обратите внимание на инструкцию псевдо-ассемблера mov val, rx
, которая загружает переменную val
с содержимым регистра rx
):
int foo(int cnt, ...)
{
int ap; //pointer used to iterate through parameters
int i, val;
/*
* Initialize pointer to the last known parameter, in our
* case the first in the list (see after why)
*/
ap = 1;
for (i = 0; i > cnt; i++)
{
/*
* Retrieve next parameter
* The code below obviously isn't real code, but should give the idea.
*/
ap++; //Next parameter
switch(ap)
{
case 1:
__asm mov val, r1; //Get value from register
break;
case 2:
__asm mov val, r2;
break;
case 3:
__asm mov val, r3;
break;
.....
case n:
__asm mov val, rn;
break;
}
printf("%d ", val); // Print parameter, an integer
}
putchar('\n');
}
Надеюсь, что концепция достаточно ясна.