求指导C语言中可变参数宏的知识

__Cm_duck 2014-01-07 06:51:05
void va_test(char* a, char* b, char* c, …)//省略了一些代码
va_start(ap, c);//此时ap应该指向c后面的第一个可变参数,为什么我用printf("%s", ap);输出不了,ap不是已经指向那个可变参数了吗?求详解!!!
#define va_start(ap, v) (ap = (va_list)&(v) + _INTSIZEOF(v))
&v对这个函数而言不就是二级指针吗?为什么用到二级指针,直接去v的地址加上v的内存大小不照样得到v后面一个参数的地址吗?而用二级指针的话我调试了还不是那个地址,疑惑!
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 这句前面*(t *)有什么意义?先强制转成二级指针再转为一级指针的作用是什么?
...全文
168 11 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
lm_whales 2014-01-08
  • 打赏
  • 举报
回复
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 这句前面*(t *)有什么意义?先强制转成二级指针再转为一级指针的作用是什么? 这么没有那么多道道。 1)va_list 就是char *
typedef char *  va_list;
所以ap 是个 char 类型的指针 2)char 类型的指针,加上_INTSIZEOF(t)后,就指向下一个参数了。 注意这里是 +=,所以ap变了
 ap += _INTSIZEOF(t)
//让 ap跳过当前参数,指向下一个参数。 3)再减去_INTSIZEOF(t),回到当前参数的位置,不过ap现在,指向下一个参数。 注意这里是 - 所以ap不变。
(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))
//跳过当前参数后, //表达式
(ap += _INTSIZEOF(t)) - _INTSIZEOF(t)
的值,为
ap - _INTSIZEOF(t)
//
ap-_INTSIZEOF(t)
是当前参数的地址,类型为
char *
(t *)(ap-_INTSIZEOF(t))
char *
强制转换为
 t *
4)指针的强制类型转换,和指针解引用
 *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))
强制类型转换后的得到的 一个t* (t 类型的指针), 指向的数据类型为 t ,解引用得到一个t类型的参数,是个左值。 5)总结,这里分几个步骤 5.1) 步骤1,前进到下一个参数ap指向下一个参数。
ap += _INTSIZEOF(t) //让ap,加上认定的参数长度(t 类型的参数,在传递时候的长度),
5.2)步骤2,返回到当前参数ap 不变,依然指向下一个参数。
(t*)(ap-_INTSIZEOF(t)) //这就是当前参数地址,这里看作t类型的参数地址,
5.3)步骤3,强制类型转换,并解引用,最外面的括号,是为了防止宏扩展错误。
 (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
PS: C 式的,带有不定类型和个数参数的, 函数的参数表中的, 省略号表示的,不确定参数表部分, 参数类型信息,无法直接传递, 只能,从已经确定的参数里,获取类型信息, 比如 scanf,printf 这两个函数,从第一个参数里,提取其他参数的类型信息。 或者,直接认定,是某种类型的参数。 比如,你做一个累加和的函数,第一个参数为整型,是参数个数 默认,其他所有参数的类型,都是整型,或者浮点类型。
www_adintr_com 2014-01-08
  • 打赏
  • 举报
回复
引用 楼主 sozenvN 的回复:
va_start(ap, c);//此时ap应该指向c后面的第一个可变参数,为什么我用printf("%s", ap);输出不了,ap不是已经指向那个可变参数了吗?
不是的, va_start 之后, ap 是指向第一个可变参数的地址,而不是第一个可变参数! 它需要解引用一次后才是参数的值。所以, printf("%s", *(char**)ap) 才对,即使 printf("%s", *(int*)ap) 也行,总之需要解引用一次。
引用 楼主 sozenvN 的回复:
&v对这个函数而言不就是二级指针吗?为什么用到二级指针,直接去v的地址加上v的内存大小不照样得到v后面一个参数的地址吗?而用二级指针的话我调试了还不是那个地址,疑惑!
之所以会是二级指针只是因为你的参数本身是个指针而已,这几个宏是通用的宏,当你的参数不是指针时,它就只是个指针,不是二级指针了。 这里所做的事情正是 “去v的地址加上v的内存大小不照样得到v后面一个参数的地址”,v 本身是个指针,取它的地址就是二级指针。
引用 楼主 sozenvN 的回复:
这句前面*(t *)有什么意义?先强制转成二级指针再转为一级指针的作用是什么?
这其实就是进行一次解引用,并转换成 t 的类型, 不要再纠结几级指针了,先把 t 写成 int 类型来推导一下过程吧。
赵4老师 2014-01-08
  • 打赏
  • 举报
回复
计算机组成原理→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对应的汇编并单步执行观察相应内存和寄存器变化。)
__Cm_duck 2014-01-08
  • 打赏
  • 举报
回复
[quote=引用 10 楼 zhao4zhong1 的回复:] 谢谢老师的回复,这段源码确实有很多疑惑。
赵4老师 2014-01-08
  • 打赏
  • 举报
回复
计算机组成原理→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对应的汇编并单步执行观察相应内存和寄存器变化。) 想要从本质上理解C指针,必须学习汇编以及C和汇编的对应关系。 从汇编的角度理解和学习C语言的指针,原本看似复杂的东西就会变得非常简单! 指针即地址。“地址又是啥?”“只能从汇编语言和计算机组成原理的角度去解释了。” 但我又不得不承认: 有那么些人喜欢或者适合用“先具体再抽象”的方法学习和理解复杂事物; 而另一些人喜欢或者适合用“先抽象再具体”的方法学习和理解复杂事物。 而我本人属前者。 不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质, 而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。 这辈子不看内存地址和内存值;只画链表、指针示意图,画堆栈示意图,画各种示意图,甚至自己没画过而只看过书上的图……能从本质上理解指针、理解函数参数传递吗?本人深表怀疑! 这辈子不种麦不收麦不将麦粒拿去磨面;只吃馒头、吃面条、吃面包、……甚至从没看过别人怎么蒸馒头,压面条,烤面包,……能从本质上理解面粉、理解面食吗?本人深表怀疑!! 提醒: “学习用汇编语言写程序” 和 “VC调试(TC或BC用TD调试)时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化,这样过一遍不就啥都明白了吗。 (Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。) 想要从本质上理解C指针,必须学习C和汇编的对应关系。” 不是一回事! 不要迷信书、考题、老师、回帖; 要迷信CPU、编译器、调试器、运行结果。 并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。 任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实! 有人说一套做一套,你相信他说的还是相信他做的? 其实严格来说这个世界上古往今来所有人都是说一套做一套,不是吗? 不要写连自己也预测不了结果的代码! 电脑内存或文件内容只是一个一维二进制字节数组及其对应的二进制地址; 人脑才将电脑内存或文件内容中的这个一维二进制字节数组及其对应的二进制地址的某些部分看成是整数、有符号数/无符号数、浮点数、复数、英文字母、阿拉伯数字、中文/韩文/法文……字符/字符串、汇编指令、函数、函数参数、堆、栈、数组、指针、数组指针、指针数组、数组的数组、指针的指针、二维数组、字符点阵、字符笔画的坐标、黑白二值图片、灰度图片、彩色图片、录音、视频、指纹信息、身份证信息…… 十字链表交换任意两个节点C源代码(C指针应用终极挑战)http://download.csdn.net/detail/zhao4zhong1/5532495
赵4老师 2014-01-08
  • 打赏
  • 举报
回复
编译选项加/EP /P,重新编译,查看宏展开后对应的.i文件。gcc加-E
__Cm_duck 2014-01-08
  • 打赏
  • 举报
回复
引用 4 楼 adlay 的回复:
[quote=引用 楼主 sozenvN 的回复:] va_start(ap, c);//此时ap应该指向c后面的第一个可变参数,为什么我用printf("%s", ap);输出不了,ap不是已经指向那个可变参数了吗?
不是的, va_start 之后, ap 是指向第一个可变参数的地址,而不是第一个可变参数! 它需要解引用一次后才是参数的值。所以, printf("%s", *(char**)ap) 才对,即使 printf("%s", *(int*)ap) 也行,总之需要解引用一次。
引用 楼主 sozenvN 的回复:
&v对这个函数而言不就是二级指针吗?为什么用到二级指针,直接去v的地址加上v的内存大小不照样得到v后面一个参数的地址吗?而用二级指针的话我调试了还不是那个地址,疑惑!
之所以会是二级指针只是因为你的参数本身是个指针而已,这几个宏是通用的宏,当你的参数不是指针时,它就只是个指针,不是二级指针了。 这里所做的事情正是 “去v的地址加上v的内存大小不照样得到v后面一个参数的地址”,v 本身是个指针,取它的地址就是二级指针。
引用 楼主 sozenvN 的回复:
这句前面*(t *)有什么意义?先强制转成二级指针再转为一级指针的作用是什么?
这其实就是进行一次解引用,并转换成 t 的类型, 不要再纠结几级指针了,先把 t 写成 int 类型来推导一下过程吧。[/quote] #define va_start(ap, v) (ap = (va_list)&(v) + _INTSIZEOF(v)) 如果按这个例子 void va_test(char* a, char* b, char* c, …) va_start(ap, c)宏展开 ap = (va_list)&c + _INTSIZEOF(c) 这句怎么理解? 此时c是char* 类型,&c是指向char*类型的二级指针,(va_list)强制转换为char*类型指针,然后加上c指针的大小, 我的疑惑是,如果不强制转换的话 &(c) 和 (va_list)&(c) 加上_INISIZEOF(c)后指向的结果有什么不同?
__Cm_duck 2014-01-08
  • 打赏
  • 举报
回复
#define va_start(ap, v) (ap = (va_list)&(v) + _INTSIZEOF(v)) 如果按这个例子 void va_test(char* a, char* b, char* c, …) va_start(ap, c)宏展开 ap = (va_list)&c + _INTSIZEOF(c) 这句怎么理解? 此时c是char* 类型,&c是指向char*类型的二级指针,(va_list)强制转换为char*类型指针,然后加上c指针的大小, 我的疑惑是,如果不强制转换的话 &(c) 和 (va_list)&(c) 加上_INISIZEOF(c)后指向的结果有什么不同?
赵4老师 2014-01-08
  • 打赏
  • 举报
回复
va_arg, va_end, va_start Access variable-argument lists. type va_arg( va_list arg_ptr, type ); void va_end( va_list arg_ptr ); void va_start( va_list arg_ptr ); (UNIX version) void va_start( va_list arg_ptr, prev_param ); (ANSI version) Routine Required Header Optional Headers Compatibility va_arg <stdio.h> and <stdarg.h> <varargs.h>1 ANSI, Win 95, Win NT va_end <stdio.h> and <stdarg.h> <varargs.h>1 ANSI, Win 95, Win NT va_start <stdio.h> and <stdarg.h> <varargs.h>1 ANSI, Win 95, Win NT 1 Required for UNIX V compatibility. For additional compatibility information, see Compatibility in the Introduction. Libraries LIBC.LIB Single thread static library, retail version LIBCMT.LIB Multithread static library, retail version MSVCRT.LIB Import library for MSVCRT.DLL, retail version Return Value va_arg returns the current argument; va_start and va_end do not return values. Parameters type Type of argument to be retrieved arg_ptr Pointer to list of arguments prev_param Parameter preceding first optional argument (ANSI only) Remarks The va_arg, va_end, and va_start macros provide a portable way to access the arguments to a function when the function takes a variable number of arguments. Two versions of the macros are available: The macros defined in STDARG.H conform to the ANSI C standard, and the macros defined in VARARGS.H are compatible with the UNIX System V definition. The macros are: va_alist Name of parameter to called function (UNIX version only) va_arg Macro to retrieve current argument va_dcl Declaration of va_alist (UNIX version only) va_end Macro to reset arg_ptr va_list typedef for pointer to list of arguments defined in STDIO.H va_start Macro to set arg_ptr to beginning of list of optional arguments (UNIX version only) Both versions of the macros assume that the function takes a fixed number of required arguments, followed by a variable number of optional arguments. The required arguments are declared as ordinary parameters to the function and can be accessed through the parameter names. The optional arguments are accessed through the macros in STDARG.H or VARARGS.H, which set a pointer to the first optional argument in the argument list, retrieve arguments from the list, and reset the pointer when argument processing is completed. The ANSI C standard macros, defined in STDARG.H, are used as follows: All required arguments to the function are declared as parameters in the usual way. va_dcl is not used with the STDARG.H macros. va_start sets arg_ptr to the first optional argument in the list of arguments passed to the function. The argument arg_ptr must have va_list type. The argument prev_param is the name of the required parameter immediately preceding the first optional argument in the argument list. If prev_param is declared with the register storage class, the macro’s behavior is undefined. va_start must be used before va_arg is used for the first time. va_arg retrieves a value of type from the location given by arg_ptr and increments arg_ptr to point to the next argument in the list, using the size of type to determine where the next argument starts. va_arg can be used any number of times within the function to retrieve arguments from the list. After all arguments have been retrieved, va_end resets the pointer to NULL. The UNIX System V macros, defined in VARARGS.H, operate somewhat differently: Any required arguments to the function can be declared as parameters in the usual way. The last (or only) parameter to the function represents the list of optional arguments. This parameter must be named va_alist (not to be confused with va_list, which is defined as the type of va_alist). va_dcl appears after the function definition and before the opening left brace of the function. This macro is defined as a complete declaration of the va_alist parameter, including the terminating semicolon; therefore, no semicolon should follow va_dcl. Within the function, va_start sets arg_ptr to the beginning of the list of optional arguments passed to the function. va_start must be used before va_arg is used for the first time. The argument arg_ptr must have va_list type. va_arg retrieves a value of type from the location given by arg_ptr and increments arg_ptr to point to the next argument in the list, using the size of type to determine where the next argument starts. va_arg can be used any number of times within the function to retrieve the arguments from the list. After all arguments have been retrieved, va_end resets the pointer to NULL. Example
/* VA.C: The program below illustrates passing a variable
 * number of arguments using the following macros:
 *      va_start            va_arg              va_end
 *      va_list             va_dcl (UNIX only)
 */

#include <stdio.h>
#define ANSI            /* Comment out for UNIX version     */
#ifdef ANSI             /* ANSI compatible version          */
#include <stdarg.h>
int average( int first, ... );
#else                   /* UNIX compatible version          */
#include <varargs.h>
int average( va_list );
#endif

void main( void )
{
   /* Call with 3 integers (-1 is used as terminator). */
   printf( "Average is: %d\n", average( 2, 3, 4, -1 ) );

   /* Call with 4 integers. */
   printf( "Average is: %d\n", average( 5, 7, 9, 11, -1 ) );

   /* Call with just -1 terminator. */
   printf( "Average is: %d\n", average( -1 ) );
}

/* Returns the average of a variable list of integers. */
#ifdef ANSI             /* ANSI compatible version    */
int average( int first, ... )
{
   int count = 0, sum = 0, i = first;
   va_list marker;

   va_start( marker, first );     /* Initialize variable arguments. */
   while( i != -1 )
   {
      sum += i;
      count++;
      i = va_arg( marker, int);
   }
   va_end( marker );              /* Reset variable arguments.      */
   return( sum ? (sum / count) : 0 );
}
#else       /* UNIX compatible version must use old-style definition.  */
int average( va_alist )
va_dcl
{
   int i, count, sum;
   va_list marker;

   va_start( marker );            /* Initialize variable arguments. */
   for( sum = count = 0; (i = va_arg( marker, int)) != -1; count++ )
      sum += i;
   va_end( marker );              /* Reset variable arguments.      */
   return( sum ? (sum / count) : 0 );
}
#endif


Output

Average is: 3
Average is: 8
Average is: 0
Argument Access Routines See Also vfprintf
lm_whales 2014-01-07
  • 打赏
  • 举报
回复
更正以下 //2)初始化va_list指针,(不定参数指针,让他指向最后一个确定参数的下一个参数) va_start(vlist,c);其实就是让 vlist指向堆栈中,c后面一个参数。
lm_whales 2014-01-07
  • 打赏
  • 举报
回复
void va_test(char* a, char* b, char* c, …){
//1) //定义va_list指针(不定参数列表指针)
va_list vlist;
//2)//初始化va_list指针,(不定参数指针,让他指向最后一个确定参数) 
va_start(vlist,c);
//3)//读取后面的各个函数参数,格式为,type x =  va_arg(vlist,type) ; 例如:
int x = 
      va_arg(vlist,int);

//4) 结束使用不定参数函数参数
     va_end(vlist);
}
PS: 这4个步骤,缺一不可。。。。哦,va_end();也许作用不大。
1. C 语言的指针和内存泄漏 5 2. C语言难点分析整理 10 3. C语言难点 18 4. C/C++实现冒泡排序算法 32 5. C++指针和引用的区别 35 6. const char*, char const*, char*const的区别 36 7. C可变参数函数实现 38 8. C程序内存组成部分 41 9. C编程拾粹 42 10. C语言实现数组的动态增长 44 11. C语言的位运算 46 12. 浮点数的存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. C语言复杂表达式的执行步骤 66 16. C语言字符串函数大全 68 17. C语言定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与return的差别 141 30. extern与static存储空间矛盾 145 31. PC-Lint与C\C++代码质量 147 32. spirntf函数使用大全 158 33. 二叉树的数据结构 167 34. 位运算应用口诀和实例 170 35. 内存对齐与ANSI Cstruct内存布局 173 36. 冒泡和选择排序实现 180 37. 函数指针数组与返回数组指针的函数 186 38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针 224 46. 标准C字符串分割的方法 228 47. 汉诺塔源码 231 48. 洗牌算法 234 49. 深入理解C语言指针的奥秘 236 50. 游戏外挂的编写原理 254 51. 程序实例分析-为什么会陷入死循环 258 52. 空指针究竟指向了内存的哪个地方 260 53. 算术表达式的计算 265 54. 结构体对齐的具体含义 269 55. 连连看AI算法 274 56. 连连看寻路算法的思路 283 57. 重新认识:指向函数的指针 288 58. 链表的源码 291 59. 高质量的子程序 295 60. 高级C语言程序员测试必过的十六道最佳题目+答案详解 297 61. C语言常见错误 320 62. 超强的指针学习笔记 325 63. 程序员之路──关于代码风格 343 64. 指针、结构体、联合体的安全规范 346 65. C指针讲解 352 66. 关于指向指针的指针 368 67. C/C++ 误区一:void main() 373 68. C/C++ 误区二:fflush(stdin) 376 69. C/C++ 误区三:强制转换 malloc() 的返回值 380 70. C/C++ 误区四:char c = getchar(); 381 71. C/C++ 误区五:检查 new 的返回值 383 72. C 是 C++ 的子集吗? 384 73. C和C++的区别是什么? 387 74. 无条件循环 388 75. 产生随机数的方法 389 76. 顺序表及其操作 390 77. 单链表的实现及其操作 391 78. 双向链表 395 79. 程序员数据结构笔记 399 80. Hashtable和HashMap的区别 408 81. hash 表学习笔记 410 82. C程序设计常用算法源代码 412 83. C语言有头结点链表的经典实现 419 84. C语言惠通面试题 428 85. C语言常用定义 450

70,020

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧