[RabbitOS] c可变参数函数
NeuronR
2008-06-24
最近对C可变长参数研究有点心得。C可变长参数函数在调用时是有很多讲究的,比如说声明这样一个函数
: void func(int a, ...); 在调用时传入这样的参数: func(1,2); 后一个参数2可以被(程序员或者阅读该程序的人)认为是一个整数,但是也可以被认为是一个长整数 (long long),甚至是字符或者浮点型数。当然编译器作为一个信奉决定论的程序,只可能在编译之后 产生一种结果。事实上可变长参数里面是不允许解析char型参数的,它必须被扩宽为整型,比如: ch = va_arg(args, char); 类似的语句gcc会报错,它必须被改成: ch = (char)va_arg(args, int); 此外,short int必须扩宽成int,float必须扩宽成double 有趣的是,这一点在固定参数函数中也会有体现,比如下面的例子: #include<stdio.h> void test(long double llf0, int i1, void* p2, double d3, long long ll4, void* p5) { int addr0 = (int)&llf0, addr1 = (int)&i1, addr2 = (int)&p2, addr3 = (int)&d3, addr4 = (int)&ll4, addr5 = (int)&p5; printf("%d\n", addr1 - addr0); printf("%d\n", addr2 - addr1); printf("%d\n", addr3 - addr2); printf("%d\n", addr4 - addr3); printf("%d\n", addr5 - addr4); } int main() { test(0,1,2,0,0,0); return 0; } 在win环境下编译运行(linux下应该是一样的吧)得到的结果如下: 12 4 4 8 8 也就是说这些参数的首地址的差值与对应类型的sizeof是吻合的(long double这个类型似乎很少有人用 到?) 然而对于这样一个函数,事情变得复杂起来: -3 20 然而我扔给一个同学编译运行的结果是 -4 40 (《三体》里的智子入侵内存了?) 有约没有研究过源代码,我对gcc如何组织这些类型的参数毫无概念,因此以下的讨论均限于int等比较安 全的类型。 为了弄清楚可变参数函数中的参数布置,必须不择手段地进行查看内存,然而我尚未在任何一款debug程 序中看到这个功能,因此这里printf还是必需的。 #include<stdio.h> void test(int integer, ...) { void* scanner = 1 + &integer; int i; char ch; for(i = 0; i < sizeof(int) * 3; ++i) { ch = *(i + (char*)scanner); printf("%2u ", (unsigned char)ch); if( 3 == (i & 3) ) { puts(""); } } } int main() { test(0,1,65536,0x16171819); return 0; } 经过对内存的强制访问,现在情况已经很明了了,整数被顺序压入参数栈中。这样一来,就可以自己为可 变参数函数编制宏了: #include<stdio.h> typedef void* va_list; #define va_start(lister, last_param) ( lister = 1 + &last_param ) #define va_arg(lister, type)\ ( lister += sizeof(type), *(type*)(lister - sizeof(type) ) ) void test(int integer, ...) { int i,j; va_list scanner; va_start(scanner, integer); j = va_arg(scanner, int); printf("%u\n", j); j = va_arg(scanner, int); printf("%u\n", j); j = va_arg(scanner, int); printf("%x\n", j); } int main() { test(0,1,65535,0x1a1b1c1d); return 0; } 至于va_end,也许是做函数退栈的清理工作,我觉得这一点已经不是纯c能搞定的了。 |
|
crackcell
2008-06-25
支持。
kernel/printk.c 里面就用了这个。 |
|
lin_llx
2008-06-28
然而我扔给一个同学编译运行的结果是 ……
其实这个同学是我…… |
|
liu-wei
2008-06-28
前面的“奇怪”现象和gcc对栈边界的处理有关。
gcc -mpreferred-stack-boundary=2 来编译。 具体还是看汇编比较清楚。 |