运行gcc4.4.6生成的程序时,运行到第23行时,输出Illegal instruction,程序退出。查看了一下gcc4.4.6生成的汇编代码,发现没有为read_args_from_va_bad()生成有效的代码。
astrol@astrol:~/c$ gdb va_arg -q
Reading symbols from /home/astrol/c/va_arg...done.
(gdb) run
Starting program: /home/astrol/c/va_arg
c 0 1.100000
c
0
1.100000
Program received signal SIGILL, Illegal instruction.
0x08048452 in read_args_from_va_bad (i=0) at va_arg.c:44
44 va_start(arg_ptr, i);
(gdb) x/i $pc
=> 0x8048452 <read_args_from_va_bad+12>: ud2
(gdb)
UD2是一种让CPU产生invalid opcode exception的软件指令. 内核发现CPU出现这个异常, 会立即停止运行
在VC中运行的结果是不正确的:
以下摘自《C陷阱与缺陷》 下载见
这里有一个陷阱需要避免:
va_arg宏的第2个参数不能被指定为char、short或者float类型。
因为char和short类型的参数会被转换为int类型,而float类型的参数会被转换为double类型 ……
例如,这样写肯定是不对的:
c = va_arg(ap,char);
因为我们无法传递一个char类型参数,如果传递了,它将会被自动转化为int类型。上面的式子应该写成:
c = va_arg(ap,int);
——《C陷阱与缺陷》p164
可能有人会问,VC中的三个宏不是已经实现了自动int对齐了吗? 如下:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
下面是linux 2.6.22中的实现,其实是一样的意思
#define _AUPBND (sizeof (acpi_native_int) - 1)
#define _ADNBND (sizeof (acpi_native_int) - 1)
/*
* Variable argument list macro definitions
*/
#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap) (void) 0
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))
不过我想说的是:
① C标准对默认实际参数提升规则有明确规定。
也就是说, 带有可变长参数列表的函数, 绝对不会接受到char类型的实际参数。
② C标准对va_arg是否自动对齐没有任何说明。
也就是说自动对齐工作,编译器可做可不做。
在所有C实现上,能保证第①点,不能保证第②点,所以尽管编译器实现了自动对齐,也要按标准来。