我之前曾询问a question有关C函数的信息,这些函数采用了未指定数量的参数,例如void foo() { /* code here */ }
,可以使用未指定类型的未指定类型的参数调用。
当我询问像void foo() { /* code here */ }
这样的函数是否可以获取调用它的参数时,例如foo(42, "random")
有人说:
您唯一能做的就是使用您正在运行的体系结构的调用约定和知识,并直接从堆栈中获取参数。 source
我的问题是:
如果我有这个功能
void foo()
{
// get the parameters here
};
我称之为:foo("dummy1", "dummy2")
是否可以直接从堆栈中获取foo
函数中的2个参数?
如果是,怎么样?是否可以访问完整堆栈?例如,如果我以递归方式调用函数,是否可以以某种方式访问每个函数状态?
如果没有,那么具有未指定数量的参数的功能有什么意义?这是C编程语言中的错误吗?在哪些情况下,任何人都希望foo("dummy1", "dummy2")
能够编译并运行标题为void foo()
的函数?
答案 0 :(得分:1)
是的,您可以直接通过堆栈访问传递的参数。但不,你不能使用旧式函数定义来创建具有可变数量和参数类型的函数。以下代码显示了如何通过堆栈指针访问参数。它完全取决于平台,所以我不知道它是否可以在你的机器上工作,但你可以得到这个想法
long foo();
int main(void)
{
printf( "%lu",foo(7));
}
long foo(x)
long x;
{
register void* sp asm("rsp");
printf("rsp = %p rsp_ value = %lx\n",sp+8, *((long*)(sp + 8)));
return *((long*)(sp + 8)) + 12;
}
偏移是问题,因为它依赖于编译器,操作系统,谁知道还有什么。 对于这个例子,我在调试器中检查了它,但如果它对你真的很重要,我想你可以为你的机器解决方案带来一些“通用”。
答案 1 :(得分:1)
很多'if's:
然后一个函数的激活记录是可预测的(即你看看生成的程序集并假设它总是相同的):返回地址将在某处和保存的bp(基指针,如果你的体系结构有一个) ),并且参数的顺序将是相同的。那你怎么知道实际的参数传递了什么?你必须对它们进行编码(它们的大小,偏移量),大概是在第一个参数中,就像printf那样。
递归(即在递归调用中没有区别)每个实例都有其激活记录(我说你必须说服你的编译器永远不会优化尾调用吗?),但是在C中,与Pascal不同,你没有因为没有嵌套的函数声明,所以有一个向后调用调用者激活记录的链接(即局部变量)。获取完整堆栈的访问权限,即当前实例之前的所有激活记录都非常繁琐,容易出错,而且对于想要操纵返回地址的恶意代码编写者而言,这些记录最为感兴趣。
因此,基本上没有任何麻烦和假设。
答案 2 :(得分:0)
如果您声明void foo()
,那么您将收到foo("dummy1", "dummy2")
的编译错误。
您可以声明一个带有未指定数量参数的函数,如下所示(例如):
int func(char x,...);
如您所见,必须指定至少一个参数。这样,在函数内部,您将能够访问 last 指定参数后面的所有参数。
假设您有以下呼叫:
short y = 1000;
int sum = func(1,y,5000,"abc");
以下是如何实现func
并访问每个未指定的参数:
int func(char x,...)
{
short y = (short)((int*)&x+1)[0]; // y = 1000
int z = (int )((int*)&x+2)[0]; // z = 5000
char* s = (char*)((int*)&x+3)[0]; // s[0...2] = "abc"
return x+y+z+s[0]; // 1+1000+5000+'a' = 6098
}
正如您所看到的,这里的问题是每个参数的类型和参数的总数是未知的。因此,使用“不适当的”参数列表调用func
可能(并且可能会)导致运行时异常。
因此,通常,第一个参数是一个字符串(const char*
),它指示以下每个参数的类型,以及参数的总数。此外,还有标准宏用于提取未指定的参数 - va_start
和va_end
。
例如,以下是如何实现与printf
的行为类似的函数:
void log_printf(const char* data,...)
{
static char str[256] = {0};
va_list args;
va_start(args,data);
vsnprintf(str,sizeof(str),data,args);
va_end(args);
fprintf(global_fp,str);
printf(str);
}
P.S。:上面的示例是不线程安全,并且仅在此处给出作为示例 ......