user-0 提问: 可变参数的一些讨论

回复:
user: user-0
content: 引用 3 楼 zhao4zhong1 的回复:计算机组成原理→DOS命令→汇编语言→C语言（不包括C++）、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言（包括C++）、架构……  对学习编程者的忠告： 眼过千遍不如手过一遍！ 书看千行不如手敲一行！ 手敲千行不如单步一行！ 单步源代码千行不如单步对应汇编一行！  VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化，这样过一遍不就啥都明白了吗。 对VC来说，所谓‘调试时’就是编译连接通过以后，按F10或F11键单步执行一步以后的时候，或者在某行按F9设了断点后按F5执行停在该断点处的时候。 （Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。） 看第一遍 还以为是广告什么的呢，我们最终是通过汇编跟踪看到真相的。不过没按你说的步骤。谢谢。

user: user-0
content: 引用 7 楼 worldy 的回复:再说了，你还可能传入的是0值本身，所以，可变参数函数绝对不能像你那样处理的 已经换了其他方式处理，跟同事一起研究了半天，其实看下va_arg的说明就行的。主要是之前看的一篇帖子里面说通过va_arg获取到的参数是不是NULL就可以判断参数是不是结束了。而且在做简单的测试时证明了那个说法是对的。 但是遇到复杂的调用环境的话这个说法就完全不对了（简单测试中可以用NULL判断只是因为，在那种情况下内存中恰好在找到的位置正好是0，而如果有多层调用关系的时候，那个位置的内存则指向了上一级的返回地址了），所以最后的结论是：NULL并没办法用来判断变参的传入情况，需要通过专用的标识去处理。

user: user-0
content: 引用 5 楼 lm_whales 的回复:&pHandle+4  和handle 是何关系？？？？？  pHandle的存储地址后面紧接着就是下一个参数的地址。也就是说，如果后面有其他参数的话，&pHandle+4的位置就是它的内存地址。

user: user-1
content: C,C++ 最大的不同是，缺省调用约定不同，而不是两种语言的差异。

user: user-1
content: 另外，编译器连C 调用约定  -- cdecl --多用了一条 ADD esp, N 都不能容忍。 C++直接改为,标准调用约定了 --- stdcall --- 改用 ret N 清栈。  绝对不会，故意另外添加一个参数NULL，表示 参数表到此为止的。 而且这种方法也不通用。

user: user-1
content: &pHandle+4 64位机器，怎么办   引用 9 楼 sec_hus 的回复:[quote=引用 7 楼 worldy 的回复:] 再说了，你还可能传入的是0值本身，所以，可变参数函数绝对不能像你那样处理的 已经换了其他方式处理，跟同事一起研究了半天，其实看下va_arg的说明就行的。主要是之前看的一篇帖子里面说通过va_arg获取到的参数是不是NULL就可以判断参数是不是结束了。而且在做简单的测试时证明了那个说法是对的。 但是遇到复杂的调用环境的话这个说法就完全不对了（简单测试中可以用NULL判断只是因为，在那种情况下内存中恰好在找到的位置正好是0，而如果有多层调用关系的时候，那个位置的内存则指向了上一级的返回地址了），所以最后的结论是：NULL并没办法用来判断变参的传入情况，需要通过专用的标识去处理。[/quote]  有这个功能吗？？ 好像没有吧。  参数严格按照从右到左入栈，va_arg 找到最后一个参数，下一个数据，应该是函数调用前，入栈的数据，可能就是调用前寄存器的值，调用前要保存现场，防止函数使用了寄存器，破坏寄存器原有的值。  而参数中，第一个参数的下一个数据，就是第二个参数的值，这看它需要多少内存而定。 不会有NULL的问题。  PS: 除非调用时，直接传入NULL这个数据，参数中，绝对不会，自动添加，多余的NULL在里面。 这是函数调用约定决定的，不是可以随便改动的。

user: user-2
content: 再说了，你还可能传入的是0值本身，所以，可变参数函数绝对不能像你那样处理的

user: user-2
content: /* * 调用正常 */... void *handle = NULL;   void main() {     int rv = 0;       {         rv = A(&handle);/*这种方式在函数A中跟踪到内存(&pHandle+4)的地方值是 //说明不了问题，那是个随机值，可变参数函数，你的函数必须能清楚解释传入的数据类型，参数结束  //可以像printf 第一个参数是格式串，该串可以用于对每个后续参数进行解释，直到最后一个 //或者传递，相同类型的参数，你可以使用第一个参数指明参数个数，或者最后一个参数使用特殊值指明参数表结束 //或者使用变体变量，最后一个参数设置为NULL 0*/        ...     }       {         rv = A(&handle,"config.ini");         ...     }       {         rv = A(&handle,"127.0.0.1",8000);         ...     }       return; }

user: user-1
content: &pHandle+4  和handle 是何关系？？？？？

user: user-3
content: 引用 1 楼 sec_hus 的回复:唉，好像不能这么用啊，参数里至少需要有能够标识变参个数的地方才行。 就是这么用的可以这么用问题是你函数里面怎么处理的，随便从内核中拉了个例子出来  static void ad1843_read_multi(struct snd_ad1843 *ad1843, int argcount, ...) { 	va_list ap; 	const struct ad1843_bitfield *fp; 	int w = 0, mask, *value, reg = -1;  	va_start(ap, argcount); 	while (--argcount >= 0) { 		fp = va_arg(ap, const struct ad1843_bitfield *); 		value = va_arg(ap, int *); 		if (reg == -1) { 			reg = fp->reg; 			w = ad1843->read(ad1843->chip, reg); 		}  		mask = (1 << fp->nbits) - 1; 		*value = w >> fp->lo_bit & mask; 	} 	va_end(ap); }   void va_start( va_list arg_ptr, prev_param );  用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.  　type va_arg( va_list arg_ptr, type );  用va_arg返回可变的参数,并返回. va_arg的第二个参数是你要返回的参数的类型,这里是int型. 　然后你就可以在函数里使用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获取各个参数.   void va_end( va_list arg_ptr ); va_end宏结束可变参数的获取.

user: user-4
content: 计算机组成原理→DOS命令→汇编语言→C语言（不包括C++）、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言（包括C++）、架构……  对学习编程者的忠告： 眼过千遍不如手过一遍！ 书看千行不如手敲一行！ 手敲千行不如单步一行！ 单步源代码千行不如单步对应汇编一行！  VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化，这样过一遍不就啥都明白了吗。 对VC来说，所谓‘调试时’就是编译连接通过以后，按F10或F11键单步执行一步以后的时候，或者在某行按F9设了断点后按F5执行停在该断点处的时候。 （Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。）

user: user-0
content: 现在我想知道的是如果不要求调用者传入变参数量的信息的话，有没有办法去判断变参的数量呢？

user: user-0
content: 唉，好像不能这么用啊，参数里至少需要有能够标识变参个数的地方才行。