C_and_CPP 板


LINE

在C/C++中,有所谓的variant argument(变动引数)这东西。讲白一点,就是可以让函数 使用数量不固定的引数。这东西也许不是每个人都知道,但我想每个人都用过。因为 printf()家族就是使用这个东西的典型函数。 定义一个函数的prototype(原型)时,若是将参数列以"..."代入,就指述了这个函数即将 使用variant argument。如: void func(...); 这样子便可让编绎器不检验传入这种函数里的引数型态和数量,编出来的程式码在呼叫端 就能够尽可能地把各式各样引数传入。如: void func(0, 1, 2, 3, 0.4, 0.5 "6789"); 那麽,在如此的程式里,要怎麽存取variant argument呢?因为缺少引数变数,所以我们 不可能像一般程式一样直接存取它们,而是要改用stdarg.h里面所提供的三个巨集与一个 型别,分别是: va_list,宣告一个指标,让它指向引数串列。 va_start,初始化这个指标,让它真正指向正确的引数串列开头。 va_arg,来取得va_list中的资料。 va_end,清除这个指标,把它设为NULL。范例如下: void func(int n, ...) { va_list args; va_start(args, n); while(n>0) { printf("%d\n", va_arg(args, int)); n--; } va_end(args); } 以上程式是假设所有参数都会是int,并且有n个,把每个参数都印出来。 我们可以看看这三个家伙都是在做些什麽事,所以把它们展开来: In vadefs.h... typedef char * va_list; #define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) ) #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) #define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define _crt_va_end(ap) ( ap = (va_list)0 ) In stdarg.h... #define va_start _crt_va_start #define va_arg _crt_va_arg #define va_end _crt_va_end 从这里可以看出va_list其实只是一个char*,它将整串引数当成一个位元阵列;而 va_start与va_arg可能比较神奇一点,但是仔细观查的话,可以发现va_start是参考实际 引数(Actual argument)t,把它的记忆体位址(也许你对_ADDRESSOF巨集不解,也从没看 过reinterpret_cast<>,其实这就是C++的static casting,当做(const char *)&v便是。 )加上另一个巨集_INTSIZEOF(n)做为偏移量,以真正地指向串列的开头。 那麽,为什麽要大费周张地用神秘巨集_INTSIZEOF(n)呢。就合理的推断来说,应该只要加 上sizeof(t)不就好了吗?这是因为,以C/C++编绎出来的各个参数是会对齐sizeof(int)的 。也就是说,今天若你传入了char做为variant argument的前一个参数,那麽想像中,整 个参数的配置应该是1+n个byte(char, ...)。但实际上,对於那个char,编绎出来的码 会是4+n byte才是。(要注意的是,这麽做很可能只有在X86/VC上是如此,若是换成其它 CPU或编绎器,可能不会这麽做。)也就是因为这种要五毛给一块的行为,让我们不能简 单地直接加上sizeof(t)。 再看看va_arg,你应该也发现它只是把ap往下继续延伸,很简单。 所以再看看我们的func函数,因为它的引数是(int n, ...),所以做过扩展後得到的就是 char *args; //arg_list args=((const char *)&n)+4; //va_start *(int *)((args+=4)-4); //va_arg 又va_start总是需要variant argument的前一个变数当做参考,所以我们一开始写的 func(...)便是永远不可能实用化的,除了一个例外。 考虑一个我们想实作的printf,它有Escape char,当%1时输出字串;%2时输出数字: int my_printf(const char *fmt, ...); 可以写成这样子: void my_printf_helper(const char *fmt, va_list args) { char *ptr=(char *)fmt; while(*ptr) { switch(*ptr) { case '\\': if(*++ptr) ptr++; continue; case '%': switch(*++ptr) { case NULL: continue; case '1': printf("%s", va_arg(args, char *)); break; case '2': printf("%d", va_arg(args, int)); break; } ptr++; default: putchar(*ptr++); } } } void my_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); my_printf_helper(fmt, args); va_end(args); } 好了,我们现在做出一个自己的printf了,就是这麽简单。接着,我们谈谈另一个应用。 应该有不少人会遇到一些函数其argument是void *吧。这是不定型指标,也就是说当 compiler遇到它时,不会检查它的型别。换句话说,若是需要传入一个复杂资料结构时, 便需要很恶心的一长串casting。又或是想传入多种资料时,不仅要casting,还得要配置 记忆体。这实在很令人难过,如_beginthreadex或CreateThread就是需要这样的一种函数 做为引数,好做为thread的主体: unsigned int __stdcall my_func(void *args) {...} ... _beginthreadex(NULL, 0, my_func, ptr, 0, NULL); ... 但若考虑va_list的特性,我们可以做出比较乾净的程式码: unsigned int __stdcall my_func(void *_args) { va_list args; while(1) { args=(va_list)_args; printf("%d\n", va_arg(args, int)); printf("%s\n", va_arg(args, char *)); Sleep(500); } return 0; } void start_thread(...) { va_list args; __asm lea eax, [ebp+8] //because we don't have previous argument of the __asm mov args, eax //variant argument... So we can't use va_start... WaitForSingleObject((HANDLE)_beginthreadex(NULL, 0, my_func, args, 0, NULL), INFINITE); } ... start_thread(100, "abcdef"); 这样看起来就完美许多,这是因为variant argument事实上就是将stack视为byte array ,所以可以利用一点小技巧骗过compiler,并且让OS帮我们把资料捆成一包,送给thread 。 嗯,室友开始用Foxy抓该死的A片了,所以文章打到这里为止,欢迎讨论。  -- --



※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 59.121.125.159
1F:推 csihcs:赞耶~~推一个^^ 01/14 00:16
2F:推 PRAM:辛苦啦 推~~~ 01/14 00:18
※ 编辑: ccbruce 来自: 59.121.125.159 (01/14 00:28)
3F:推 wixter:有看有推 感恩~~ 01/14 02:18
4F:推 doomleika:有看有推 01/14 03:11
5F:推 UNARYvvv:看到 ebp+8 就懂了 推~ 01/14 05:34
6F:推 wulawu:推 01/14 15:33
7F:推 duidae:推! 01/14 17:44
8F:推 avhacker:搞thread还是boost好用. bind一个function就可以开thread 01/14 18:56
9F:推 rogerliu0916:无私的分享最令人敬佩了.... 01/15 00:02
10F:推 JLong:清楚明了 推 01/15 00:36
11F:推 vip82:有看有推 好文 01/15 13:32
12F:推 chung928:先推再看~ 04/12 16:43







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:iOS站内搜寻

TOP