用库函数stdarg.h实现函数参数的可变

来源:互联网 发布:淘宝旺铺导航条 编辑:程序博客网 时间:2024/05/01 20:10
#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 )

        va_list args;
           char buf[1024];
        .........
        va_start(args, msg);
        int len=_vsnprintf(buf+TIMEBUF_LENGTH, 1000, msg, args);
        va_end(args);

  va_list arg_ptr:定义一个指向个数可变的参数列表指针;
  
  va_start(arg_ptr, argN):使参数列表指针arg_ptr指向函数参数列表中的第一个可选参数,说明:argN是位于第一个可选参数之前的固定参数,(或者说,最后一个固定参数;…之前的一个参数),函数参数列表中参数在内存中的顺序与函数声明时的顺序是一致的。如果有一va函数的声明是void va_test(char a, char b, char c, …),则它的固定参数依次是a,b,c,最后一个固定参数argN为c,因此就是va_start(arg_ptr, c)。
  
  va_arg(arg_ptr, type):返回参数列表中指针arg_ptr所指的参数,返回类型为type,并使指针arg_ptr指向参数列表中下一个参数。
  
  va_copy(dest, src):dest,src的类型都是va_list,va_copy()用于复制参数列表指针,将dest初始化为src。
  
  va_end(arg_ptr):清空参数列表,并置参数指针arg_ptr无效。说明:指针arg_ptr被置无效后,可以通过调用 va_start()、va_copy()恢复arg_ptr。每次调用va_start() / va_copy()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() … va_end()之内。
  
  
  
  ◎用法:
  func( Type para1, Type para2, Type para3, ... )
  {
   /****** Step 1 ******/
   va_list ap;
   va_start( ap, para3 ); //一定要“...”之前的那个参数
  
   /****** Step 2 ******/
   //此时ap指向第一个可变参数
   //调用va_arg取得里面的值
  
   Type xx = va_arg( ap, Type );
  
   //Type一定要相同,如:
   //char *p = va_arg( ap, char *);
   //int i = va_arg( ap, int );
  
   //如果有多个参数继续调用va_arg
  
   /****** Step 3 ******/
   va_end(ap); //For robust!
  }
  
  ◎研究:
  typedef char * va_list;
  
  #define va_start _crt_va_start
  #define va_arg _crt_va_arg
  #define va_end _crt_va_end
  
  #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 )
  va_list argptr;
  C语言的函数是从右向左压入堆栈的,调用va_start后,
  按定义的宏运算,_ADDRESSOF得到v所在的地址,然后这个
  地址加上v的大小,则使ap指向第一个可变参数如图:
  
   栈底 高地址
   | .......
   | 函数返回地址
   | .......
   | 函数最后一个参数
   | ....
   | 函数第一个可变参数 <--va_start后ap指向
   | 函数最后一个固定参数
   | 函数第一个固定参数
   栈顶 低地址
  
  
  然后,用va_arg()取得类型t的可变参数值, 先是让ap指向下一个参数:
  ap += _INTSIZEOF(t),然后在减去_INTSIZEOF(t),使得表达式结果为
  ap之前的值,即当前需要得到的参数的地址,强制转换成指向此参数的
  类型的指针,然后用*取值
  
  最后,用va_end(ap),给ap初始化,保持健壮性。
 
  一般的用法是这样(个人理解)
  va_list args; //声明变量
  va_start(args, fmt); //开始解析。args指向fmt后面的参数
  TYPE var = va_arg(args, TYPE); //取下一个参数并返回。args指向下一个参数
  va_end(args); //结束解析

///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////


我想大概很多學C 的人第一個會用的的標準庫存函數是printf跟scanf ,
可是大家一定發現printf與scanf 的arguments 可以不定數量, 那如果自
己想寫, 該怎麼做呢? 在K&R 的課本的附錄B 其實可以找到答案, 我就整
理放到這邊來, 不過說實在話, 除了我自己沒有真的用過以外, 個人覺得
用到的機率也不高, 不過知道一下也不錯:

首先有關含數宣告部份:
  data_type func(int ap, ...)    以...表示不定引數數量,
需要使用的header file:
  stdarg.h
相關之macros (定義在stdarg.h):
  va_start, va_arg, va_end
說明
  1.首先在函數中宣告一型別為va_list 之variable, 如:
    va_list ap;
  2.然後在要取得沒有參數名稱與之對應的arguments 前先呼叫va_start, 如:
    va_start (va_list, lastarg);
  3.再呼叫va_arg便可以依次取得各 (無參數名對應) 引數, 如:
    type va_arg (va_list, type);
  4.最後呼叫va_end結束, 如:
    void va_end (va_list ap);
各定義與使用參閱所使用之C Compiler的手冊, 以下之stdarg.h取自FreeBSD 4.8,
請參考.

/*-
 * Copyright (c) 1991, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *      @(#)stdarg.h    8.1 (Berkeley) 6/10/93
 * $FreeBSD: src/sys/i386/include/stdarg.h,v 1.10 1999/08/28 00:44:26 peter Exp$
 */

#ifndef _STDARG_H_
#define _STDARG_H_

typedef char *va_list;

#define __va_size(type) /
        (((sizeof(type) + sizeof(int) - 1) / sizeof(int)) * sizeof(int))

#ifdef __GNUC__
#define va_start(ap, last) /
        ((ap) = (va_list)__builtin_next_arg(last))
#else
#define va_start(ap, last) /
        ((ap) = (va_list)&(last) + __va_size(last))
#endif

#define va_arg(ap, type) /
        (*(type *)((ap) += __va_size(type), (ap) - __va_size(type)))

#define va_end(ap)

#endif /* !_STDARG_H_ */
/////////////////////////////////////////////////////
/////////////////////////////////////////////////////

宏--不定参数

(zz from 88, 原作者IceVolcano,还没看完,先rob过来:)

在实现不定参数函数的时候,要用到va_size,va_start,va_arg三个宏。
在stdarg.h里有对这三个宏的实现。
此头文件如下,略去了开头的一大段注释:
#ifndef _MACHINE_STDARG_H_
#define _MACHINE_STDARG_H_
#include <sys/cdefs.h>
#include <sys/_types.h>
#ifndef _VA_LIST_DECLARED
#define _VA_LIST_DECLARED
typedef __va_list   va_list;
#endif
#if (defined(__GNUC__) && (__GNUC__ == 2 && __GNUC_MINOR__ > 95 || __GNUC__ >=
3) && !defined(__INTEL_COMPILER))
#define va_start(ap, last) /
    __builtin_stdarg_start((ap), (last))
#define va_arg(ap, type) /
    __builtin_va_arg((ap), type)
#if __ISO_C_VISIBLE >= 1999
#define va_copy(dest, src) /
    __builtin_va_copy((dest), (src))
#endif
#define va_end(ap) /
    __builtin_va_end(ap)
#else   /* ! (__GNUC__ post GCC 2.95 || __INTEL_COMPILER) */
#define __va_size(type) /
    (((sizeof(type) + sizeof(int) - 1) / sizeof(int)) * sizeof(int))
#if defined(__GNUC__) && !defined(__INTEL_COMPILER)
#define va_start(ap, last) /
    ((ap) = (va_list)__builtin_next_arg(last))
#else   /* non-GNU compiler */
#define va_start(ap, last) /
    ((ap) = (va_list)&(last) + __va_size(last))
#endif  /* __GNUC__ */
#define va_arg(ap, type) /
    (*(type *)((ap) += __va_size(type), (ap) - __va_size(type)))
#if __ISO_C_VISIBLE >= 1999
#define va_copy(dest, src) /
    ((dest) = (src))
#endif
#define va_end(ap)
#endif /* __GNUC__ post GCC 2.95 */
#endif /* !_MACHINE_STDARG_H_ */
 

开始是一些基本的头文件保护和一些基本的define宏,以备后用,跳过。
然后碰到一个小兵:va_list。
va_list是__va_list,追根揭底是char *,已解决(小兵就是小兵,唉)。
然后关注三个关键宏va_size,va_start,va_arg,三元猛将。
然而进一步考察发现,他们是双胞胎,根据是否用gcc决定是哥哥还是弟弟出征。
先考虑不用gcc的情况,暂且称之为哥哥。
第一个宏__va_size:
#define __va_size(type) /
        (((sizeof(type) + sizeof(int) - 1) / sizeof(int)) * sizeof(int))
这个宏并不在“应用层”(套用一下tcp/ip的说法)出现,我们用不定参数的时候并不会接
触到这个宏。他是为了另外两个存在的。确切的说,他是秘书。但我们还得了解它,不然编
译器会报错,实际运行会core dump。
咋一眼看来,是一个关于类型大小的映射。
映射结果如下:
1   4
2   4
3   4
4   4
5   8
6   8
7   8
8   8
9   12
10  12
11  12
12  12
.   .
.   .
.   .
看起来像“对齐”。估计这就是做这样映射的原因。具体情况待下面给出示例代码后再说。
第二个宏va_start:
#define va_start(ap, last) /
        ((ap) = (va_list)&(last) + __va_size(last))
可见,他是跳过last段,让栈指针指向下一个参数地址。
这里无端端的以跳过一个参数号称start,是不是犯傻?给出示例代码后再说。
刚刚开始就扔下两个问题,搞得人心里痒痒,确有不负责任之嫌。但其实,我写这个东西也
蛮头疼的,我知道看的人也很不爽,说实话如果我看也很不爽,总是有个疙瘩在心里,不免
心里会骂作者,“死没良心的,居然调人胃口,好,老子看你有何能耐”。
上面两个问题的完全解答依赖对示例代码的分析,而对示例代码的分析依赖于对va_start的
分析,所以这里只好先提出这么一个问题。因为不提的话,后来的对示例代码的分析会放松
对va_start的警戒,以为没什么名堂,可有可无,或者机械的套用。
其实很多书都这样,开头写的东西不能太深入,因为只有说道后面的知识才能深入下去,而
后面的知识是在前面的知识的基础上。这样只能靠读者去反复阅读,其意自显。而且有的书
,在我细读前一两章的东西后,再看后面,居然发现自己已经得出了结论。这说明整个书所
述的知识是一个
体系,相互依赖。而好的书,在于作者能够把握这个体系,能够把整个知识当作一个网络来
讲述……
唉!说着说着就入了无数个栈,还好我是学过的,自认对栈的管理还是可以,现在就退栈,
接着第三个宏。

第三个宏va_arg:
#define va_arg(ap, type) /
        (*(type *)((ap) += __va_size(type), (ap) - __va_size(type)))
这个宏有三个部分,他做了2件事
1、返回为type所标明的类型的值,就是取值。
   如:int i = va_arg(format, int);
2、让栈指针移到下一个参数的头位置。
三个部分的解释:
*(type *):返回为type所标明的类型的值,非地址。
(ap) += __va_size(type):ap移到后一个参数。
(ap) - __va_size(type):给出此宏所取的参数的地址。
这里,用了一个逗号句法,实现移动栈指针和返回参数地址。
哥哥被考察完了,用它来实现点东西,示例代码如下:
#undef __GNUC__
#include <stdio.h>
#include <stdarg.h>
//#define GOINT
void func(char * list, ...)
{
    va_list l;
    int i;
    char c;
    printf("list:     0x%08x/n", &list);
    va_start(l,list);
    printf("va_start: 0x%08x/n", l);
#ifdef GOINT
    i = va_arg(l, int);
    printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
    i = va_arg(l, int);
    printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
    i = va_arg(l, int);
    printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
    i = va_arg(l, int);
    printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
#else
    c = va_arg(l, char);
    printf("va_arg:   0x%08x", l); printf(" - %c/n", c);
    c = va_arg(l, char);
    printf("va_arg:   0x%08x", l); printf(" - %c/n", c);
    c = va_arg(l, char);
    printf("va_arg:   0x%08x", l); printf(" - %c/n", c);
    c = va_arg(l, char);
    printf("va_arg:   0x%08x", l); printf(" - %c/n", c);
#endif
}
int main(void)
{
    int i = 1;
    char c = 'a';
#ifdef GOINT
    func("ABCDEF",i,i,i,i);
#else
    func("ABCDEF",c,c,c,c);
#endif
    printf("%d/n", sizeof("ABCDEF"));
    return 0;
}
 
这里有几点值得关注:
1、第一行的#undef __GNUC__是被逼的
   否则就会依照gnu c complier的方法把__builtin_xxx编进去
2、#define GOINT的结果:
   list:     0xbfbfecd0
   va_start: 0xbfbfecd4
   va_arg:   0xbfbfecd8 - 1
   va_arg:   0xbfbfecdc - 1
   va_arg:   0xbfbfece0 - 1
   va_arg:   0xbfbfece4 - 1
   没有#define GOINT的结果:
   list:     0xbfbfecd0
   va_start: 0xbfbfecd4
   va_arg:   0xbfbfecd8 - a
   va_arg:   0xbfbfecdc - a
   va_arg:   0xbfbfece0 - a
   va_arg:   0xbfbfece4 - a
   两者一样,char和int在栈里的所占空间一样大。
   这可以解释为什么要提供一个va_size而不直接用sizeof。
   看来“对齐”是无处不在。
栈结构如下:
#define GOINT的结果:
|d0    |d4    |d8    |dc    |e0    |
+------+------+------+------+------+
|char *|     1|     1|     1|     1|
+------+------+------+------+------+
|4bytes|4bytes|4bytes|4bytes|4bytes|
没有#define GOINT的结果:
|d0    |d4    |d8    |dc    |e0    |
+------+------+------+------+------+
|char *|     1|     1|     1|     1|
+------+------+------+------+------+
|4bytes|4bytes|4bytes|4bytes|4bytes|
其中char *指向ABCDEF/n在内存里的位置,所以,如下的话:
printf("l=list:   0x%08x/n", l);
打印出:
l=list:     0x0804875c
这里有一点,C的进栈是从右到左,也就是说char *至最后进入的。
结合第1、2个宏,可以看出,为了使栈内参数顺序和数据位存放顺序一致,栈向小的那个方
向增长,最后指向最小的那个位置。

代码流程大致如下:
1、用va_start得到栈“开头”地址,开头打引号。
   因为牺牲了第一个参数。
2、用va_arg得到设计的变量类型的值,即传入的形参值。
3、重复2,直至所有参数取完。
实际应用中第3步由传入函数的一个参数保证,以printf为例,他的第一个参数是字符串。
函数在得到这个字符串后,对之分析,提取%d%x等。可见,第一个参数一般担当决定参数个
数和类型的任务。
但是这个任务是由程序员控制的,所以这里就可以做点文章。喜欢搞鬼的家伙们就爱这些由
程序员实际控制的代码。他绕过了编译器,或者说利用了编译器,或者说玩了一把编译器,
结果得到了一个不受束缚的指针,一般就会忍不住指向root shell。

接下来理应分析弟弟了,可是,上面说到的#undef __GNUC__让我想各位致歉。近期我没能
力也没太有时间和兴趣深入研究gcc。就算man以下,也是搜索字符串看重点。
起先我的示例代码里并没有#undef __GNUC__。用gcc -E main.c | less一看,结果是
 i = __builtin_va_arg((l), int);
 printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
 i = __builtin_va_arg((l), int);
 printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
 i = __builtin_va_arg((l), int);
 printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
 i = __builtin_va_arg((l), int);
 printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
预编译器并没有解释 __builtin_va_arg,我想到一定是定义了__GNUC__。于是加上#undef
__GNUC__,结果对了
 i = (*(int *)((l) += (((sizeof(int) + sizeof(int) - 1) / sizeof(int)) *
sizeof(int)), (l) - (((sizeof(int) + sizeof(int) - 1) / sizeof(int)) *
sizeof(int))));
 printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
 i = (*(int *)((l) += (((sizeof(int) + sizeof(int) - 1) / sizeof(int)) *
sizeof(int)), (l) - (((sizeof(int) + sizeof(int) - 1) / sizeof(int)) *
sizeof(int))));
 printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
 i = (*(int *)((l) += (((sizeof(int) + sizeof(int) - 1) / sizeof(int)) *
sizeof(int)), (l) - (((sizeof(int) + sizeof(int) - 1) / sizeof(int)) *
sizeof(int))));
 printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
 i = (*(int *)((l) += (((sizeof(int) + sizeof(int) - 1) / sizeof(int)) *
sizeof(int)), (l) - (((sizeof(int) + sizeof(int) - 1) / sizeof(int)) *
sizeof(int))));
 printf("va_arg:   0x%08x", l); printf(" - %d/n", i);
而且#undef __GNUC__要加在第一行,看起来是gcc默认了__GNUC__。如果-E后的结果不再预
处理,直接送入编译器,那么看起来是编译器认识__builtin_xxx了,就没有头文件可以看
了。没办法,i服了u。
不过这里倒是引出一个问题。
我先写的示例代码是没有#undef __GNUC__,也就是以__builtin_xxx编译的。这个时候
c = va_arg(l, char);
printf("va_arg:   0x%08x", l); printf(" - %c/n", c);
c = va_arg(l, char);
printf("va_arg:   0x%08x", l); printf(" - %c/n", c);
c = va_arg(l, char);
printf("va_arg:   0x%08x", l); printf(" - %c/n", c);
c = va_arg(l, char);
printf("va_arg:   0x%08x", l); printf(" - %c/n", c);
这个段是warning的,(既然是编译器给出的warning,似乎是由编译器解释__builtin_xxx
了)
main.c: In function `func':
main.c:24: warning: `char' is promoted to `int' when passed through `...'
main.c:24: warning: (so you should pass `int' not `char' to `va_arg')
main.c:24: note: if this code is reached, the program will abort
main.c:26: warning: `char' is promoted to `int' when passed through `...'
main.c:26: note: if this code is reached, the program will abort
main.c:28: warning: `char' is promoted to `int' when passed through `...'
main.c:28: note: if this code is reached, the program will abort
main.c:30: warning: `char' is promoted to `int' when passed through `...'
main.c:30: note: if this code is reached, the program will abort
实际的运行也导致了core dump,即使是配合func("ABCDEF",i,i,i,i);的传入,也照样
dump。
 

这里对QuoVadis的帖子再作分析,他的给出代码如下:
float Test(float first,...)
{
    float sum = 0, i = first;
    va_list marker;
    va_start(marker, first);
    for (int j = 0; j < 3; ++j)
    {
        sum += i;
        i = va_arg(marker, float);
    }
    va_end( marker );
    return sum;
}
float x = Test(1.0, 2.0, 3.0);
不出我所料
main.c: In function `Test':
main.c:42: error: 'for' loop initial declaration used outside C99 mode
main.c:45: warning: `float' is promoted to `double' when passed through `...'
main.c:45: warning: (so you should pass `double' not `float' to `va_arg')
main.c:45: note: if this code is reached, the program will abort
*** Error code 1
Stop in /home/ycheng/programs/c.
修改关于j,编译通过,但是运行dump。
加上#undef __GNUC__,编译ok
#undef __GNUC__
#include <stdio.h>
#include <stdarg.h>
float Test(float first,...)
{
    float sum = 0, i = first;
    int j;
    va_list marker;
    va_start(marker, first);
    for(j = 0; j < 3; ++j)
    {
        sum += i;
        i = va_arg(marker, float);
    }
    va_end(marker);
    return sum;
}
int main(void)
{
    float x = Test(1.0, 2.0, 3.0);
    printf("%f/n", x);
    return 0;
}
运行结果3.000000。
原因显然是1.0, 2.0, 3.0默认是double所致。
但还有一个问题,就是i = first一句。
我看了好久,i的值在for循环里的i = va_arg(marker, float);后分别是0 2 0,怎么会出
来3?
原因就在这一句,在sum += i;的配合下,sum的值在整个过程里分别为
0   //float sum = 0
1   //1st for(), i = first; sum += 1;
1   //2nd for(), sum += 0
3   //3rd for(), sum += 2
|  4bytes   |  4bytes   |  4bytes   |  4bytes   |  4bytes   |
+-----------+-----------+-----------+-----------+-----------+
|00|00|80|3F|00|00|00|00|00|00|00|40|00|00|00|00|00|00|80|40|
+-----------+-----------+-----------+-----------+-----------+
|   1.0f    |          2.0          |           3.0         |
 ^
 |
栈指针
这里左面地址小右面大,结合进栈顺序,3.0 2.0 1.0f的进站顺序为3.0->2.0->1.0f。
3.0先进,占据最高8bytes,然后2.0,再8bytes,最后1.0f,只有4bytes。栈指针指向1.0
的最低位
这个很要紧,关系到va_arg(marker, float);取哪4个bytes
结合代码:
float sum = 0, i = first;
//sum=0, i=1,这个1是由于类型转换,根栈里数据存关系不大,有还是有的
int j;
va_list marker;
va_start(marker, first);
//marker跳过了开始的4bytes,也就是跳过了1.0f
{
    sum += i;
    //sum+=1 sum=1
    i = va_arg(marker, float);
    //取出2.0的高4位当成float,结果是0,marker走
}
{
    sum += i;
    //sum+=2 sum=3
    i = va_arg(marker, float);
    //取出2.0的低4位当成float,结果是2,marker走
}
{
    sum += i;
    //sum+=0 sum=3
    i = va_arg(marker, float);
    //取出3.0的高4位当成float,结果是0,marker走
}
va_end(marker);
return sum;
//不用再说了
由此还可得一点,第一个参数虽然是double,但是函数声明里是float。
C里的栈是归调用者管的,这里是main管理到底给Test什么东西,多大的空间。
由于Test的声明里有float first,main还是能认出来。
所以main在这里把1.0降成1.0f入栈,first是一个的的确确的4bytes的float。
而此后,main实在无力知晓了,只能把double入栈,这也非main的错。
还有一个问题,就是addplus提出来的。
无论是float还是double,入栈的均为8bytes的double。
我试了一下还真的有这回事,再试了一下printf("%f %f", 1.0f, 2.0);
真能正确的打印出来。
看来这里还有名堂,这涉及编译器对类型的做法,只能在写不定参数函数的时候自己多试试
了。
 
宏是简单的,简单到会出错;
指针是复杂的,复杂到会出错。
这就是C
文章至此,应该结束了。
Wish you enjoy!
不知什么时候能再对宏进行分析。套用一句经典台词结束本文
I will be back.