[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
来编译。

具体还是看汇编比较清楚。
Global site tag (gtag.js) - Google Analytics