在C++中的函数当中,C++ Sum函数可以使用SUM来进行任何求和,但无法使用任何名称访问其他的几个不定参数,但此时由于栈上其他的几个参数实际恰好依序排列在参数SUM的高地址方向。
因此可以很简单地通过num的地址计算出其他参数的地址。sum函数的实现如下:
- int sum(unsigned num, ...)
- {
- int* p = &num + 1;
- int ret = 0;
- while (num--)
- ret += *p++;
- return ret;
- }
在这里我们可以观察到两个事实:
(1)C++ Sum函数获取参数的量仅取决于num参数的值,因此,如果num参数的值不等于实际传递的不定参数的数量,那么C++ Sum函数可能取到错误的或不足的参数。
(2)cdecl调用惯例保证了参数的正确清除。我们知道有些调用惯例(如stdcall)是由被调用方负责清除堆栈的参数,然而,被调用方在这里其实根本不知道有多少参数被传递进来,所以没有办法清除堆栈。而cdecl恰好是调用方负责清除堆栈,因此没有这个问题。
printf的不定参数比sum要复杂得多,因为printf的参数不仅数量不定,而且类型也不定。所以printf需要在格式字符串中注明参数的类型,例如用%d表明是一个整数。printf里的格式字符串如果将类型描述错误,因为不同参数的大小不同,不仅可能导致这个参数的输出错误,还有可能导致其后的一系列参数错误。
- #define va_list char*
- #define va_start(ap,arg) (ap=(va_list)&arg+sizeof(arg))
- #define va_arg(ap,t) (*(t*)((ap+=sizeof(t))-sizeof(t)))
- #define va_end(ap) (ap=(va_list)0)
- printf的狂乱输出
- #include
- int main()
- {
- printf("%lf\t%d\t%c\n", 1, 666, 'a');
- }
在这个程序里,printf的第一个输出参数是一个int(4字节),而我们告诉printf它是一个double(8字节以上),因此C++ Sum函数的输出会错误,由于printf在读取double的时候实际造成了越界,因此后面几个参数的输出也会失败。
在很多时候我们希望在定义宏的时候也能够像print一样可以使用变长参数,即宏的参数可以是任意个,这个功能可以由编译器的变长参数宏实现。在GCC编译器下,变长参数宏可以使用“##”宏字符串连接操作实现。
【编辑推荐】