精华内容
下载资源
问答
  • va_list使用方法 万次阅读 多人点赞
    2017-02-09 10:44:17

    转载自:http://blog.csdn.net/ID314846818/article/details/51074283

    VA_LIST 是在C语言中解决变参问题的一组宏,变参问题是指参数的个数不定,可以是传入一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。

    va_list 用法示例

    #include <stdarg.h> 
    
    int AveInt(int,...);
    
     void main()
    {
       printf("%d/t",AveInt(2,2,3));
       printf("%d/t",AveInt(4,2,4,6,8));
    
       return;
    }
    
    int AveInt(int v,...)
    {
       int ReturnValue=0;
       int i=v;
    
       va_list ap ;
       va_start(ap,v);
    
       while(i>0)
       {
           ReturnValue+=va_arg(ap,int) ;
           i--;
       }
       va_end(ap); 
       return ReturnValue/=v;
    }
    

    VA_LIST的用法: 
    (1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针; 
    (2)然后用VA_START宏初始化变量刚定义的VA_LIST变量; 
    (3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数); 
    (4)最后用VA_END宏结束可变参数的获取。

    上面是va_list的具体用法,下面讲解一下va_list各个语句含义(如上示例黑体部分)和va_list的实现。

         可变参数是由宏实现的,但是由于硬件平台的不同,编译器的不同,宏的定义也不相同,下面是VC6.0中x86平台的定义 :
    
          typedef char * va_list;     // TC中定义为void*
          #define _INTSIZEOF(n)    ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) //为了满足需要内存对齐的系统
          #define va_start(ap,v)    ( ap = (va_list)&v + _INTSIZEOF(v) )     //ap指向第一个变参的位置,即将第一个变参的地址赋予ap
          #define va_arg(ap,t)       ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )   /*获取变参的具体内容,t为变参的类型,如有多个参数,则通过移动ap的指针来获得变参的地址,从而获得内容*/
          #define va_end(ap) ( ap = (va_list)0 )   //清空va_list,即结束变参的获取
    

    va_list ap ; 定义一个va_list变量ap 
    va_start(ap,v) ;执行ap = (va_list)&v + _INTSIZEOF(v),ap指向参数v之后的那个参数的地址,即 ap指向第一个可变参数在堆栈的地址。 
    va_arg(ap,t) , ( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )取出当前ap指针所指的值,并使ap指向下一个参数。 ap+= sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型指针,这正是第一个可变参数在堆栈里的地址。然后 用取得这个地址的内容。 
    va_end(ap) ; 清空va_list ap。

    使用VA_LIST应该注意的问题: 
    (1)因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. 也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的. 
    (2)另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码。 
    (3)由于参数的地址用于VA_START宏,所以参数不能声明为寄存器变量,或作为函数或数组类型

    更多相关内容
  • C标准库-va_list

    2021-11-28 14:56:03
    va_listC标准库<stdarg.h>的一个可变参数列表,常用于函数传入不定数量的参数,使用时只能放置于参数的最后面(即最后一个参数)。 补充说明: 如果需要详细理解va_list需要明白一个原理,也就是函数参数在...

    目录

    说明:

    补充说明:

    原型

    原型说明

    原理举例说明

    实际使用

    理解printf


    说明:

    va_list是C标准库<stdarg.h>的一个可变参数列表,常用于函数传入不定数量的参数,使用时只能放置于参数的最后面(即最后一个参数)。

    补充说明:

    如果需要详细理解va_list需要明白一个原理,也就是函数参数在栈区是如何存储的,下面我们来说明一下在调用一个函数时各个参数是在栈区是如何存储的:

    1、首先明确一个知识,C中,堆是自下向上增长的,栈是自顶向下增长的。

    2、然后明确,C中参数入栈方式是自右向左依次入栈的。

    在知道以上两点后,给定一个函数:

    void tst(int arg1, int arg2, int arg3, int arg4){
        //code
    }

    调用时我们便可以得到以下存储结构:

     

     请记住该存储结构。

    原型

    ​
    #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 )
    
    ​

    原型说明

    /**
    关于源码这部分,能看到的版本也挺多的,主要区别都是做了很多平台处理,套了很多层,就找一个比较好理解的,原理都是一样的。**/
    
    #define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
    这个宏就理解成4字节对齐,换句话说也就是保证1字节对齐。
    
    #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) 
    这个宏,用于初始化参数列表ap,指定第一个参数的地址,完成初始化。其中ap初始化后的起始地址也就是v以后的地址。
    
    #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
    这个宏,用于先改变ap的指向地址,即指向下一个地址,然后在返回当前地址,并将当前地址做t类型强转、
    
    #define va_end(ap) ( ap = (va_list)0 )
    这个宏,用于释放ap指针使用,防止野指针。

    原理举例说明

    /**
    注意:以下代码仅仅是说明该原理使用,请结合原型以及函数参数在栈区内存分布来理解。
    实际不会这样使用,事实上还涉及一些其他问题,以下代码在MinGw中编译不通过,原因
    是使用va_list,必须要有可变参数...,才可以;在VS 2017中正常编译,原理正常。
    具体原因,是什么就不深究了,因为也不是主要使用方式,仅仅理解而已,实际上没必要
    在非可变参数中使用可变参数列表获取参数。
    */
    void tst(int arg1, int arg2, int arg3, int arg4) {
    	va_list va;
    	va_start(va, arg2);
    	printf("%d", va_arg(va, int));
    	va_end(va);
    }
    
    int main()
    {
    	tst(1,2,3,4);
    }
    //结合上述所述原理以及规则,可以得到输出结果是arg3的。
    
    //输出是:3

    实际使用

    实际上,可变参数列表在函数中使用范围似乎不是那么多,经常在调试中printf使用,或者自定义调试输出时使用,一般用“...”表示可变参数列表,如:

    void tst(int arg1, ...)

     比如我们用可变参数列表来就多个数的平均值(主要表达用法,实际用有点傻了)

    void tst(int num, ...){
        int sum = 0;
        va_list va;
        va_start(va, num);
        while (num--){
            sum += va_arg(va, int);
        }
        va_end(va);
        printf("sum:%d", sum);
    }
    
    int main() {
        tst(3,2,3,8);
    }
    
    //sum:13

    理解printf

    好了,知道可变参数列表的原理后,就理解以下printf("%s%s", "hello", "world");原理了。

    自定义一个调试输出

    #define debugError(level, format, arg...) { \
    if(level > 2) {printf("error:\r\n");\
    printf("in-->file:%s, line:%d, func:%s\r\n@", __FILE__, __LINE__, __func__ ); \
    printf(format, ##arg);}}

    展开全文
  • va_list 头文件: <stdarg.h> 作用: 保存va_start,va_arg,va_end和va_copy(typedef)所需的信息 va_start 允许访问可变参数函数参数 va_arg 访问下一个可变参数函数参数 va_copy 制作可...

    理论

    变常参数的宏定义以及__VA_ARGS

    变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号,而预定义宏__VA_ARGS则可以在宏定义的实现部分替换省略号所代表的字符串,比如:

    #define PR(...) printf(__VA_ARGS__)
    

    就可以定义一个printf的别名PR。实际上,变长参数宏与printf经常搭配使用:

    #include <stdio.h>
    
    #define LOG(...){ \
        fprintf(stderr, "%s: Line %d:\t", __FILE__, __LINE__);\
        fprintf(stderr, __VA_ARGS__);\
        fprintf(stderr, "\n");\
    }
    
    int main()
    {
        LOG("test : %d", 3);
        return 0;
    }
    
    

    在这里插入图片描述

    va_list

    头文件:

    <stdarg.h>
    

    作用:

    保存va_start,va_arg,va_end和va_copy(typedef)所需的信息

    va_start允许访问可变参数函数参数
    va_arg访问下一个可变参数函数参数
    va_copy制作可变参数函数参数(函数宏)的副本
    va_end结束可变参数函数参数(函数宏)的遍历

    当你的函数的参数个数不确定时,就可以使用上述宏进行动态处理,这无疑为你的程序增加了灵活性。

    va_list的使用方法:

    • 首先在函数中定义一个具有va_list型的变量,这个变量是指向参数的指针。
    • 然后用va_start宏初始化变量刚定义的va_list变量,使其指向第一个可变参数的地址。
    • 然后va_arg返回可变参数,va_arg的第二个参数是你要返回的参数的类型(如果多个可变参数,依次调用va_arg获取各个参数)。
    • 最后使用va_end宏结束可变参数的获取。

    在使用va_list是应该注意一下问题:

    • 可变参数的类型和个数完全由代码控制,它并不能智能地识别不同参数的个数和类型。
    • 如果我们不需要一一详解每个参数,只需要将可变列表拷贝到某个缓冲区,可以用vsprintf函数。
    • 因为编译器对可变参数的函数原型检查不够严格,对编程查错不利,不利于我们写出高质量的代码。

    原理

    typedef char *va_list;
    

    va_start宏,获取可变参数列表的第一个参数的地址(list是类型为va_list的指针,param1是可变参数最左边的参数):

    #define va_start(list,param1)   ( list = (va_list)&param1+ sizeof(param1) )
    

    va_arg宏,获取可变参数的当前参数,返回指定类型并将指针指向下一参数(mode参数描述了当前参数的类型):

    #define va_arg(list,mode)   ( (mode *) ( list += sizeof(mode) ) )[-1]
    

    va_end宏,清空va_list可变参数列表:

    #define va_end(list) ( list = (va_list)0 )
    

    假设有这样一个函数

    void test(char *para1,char *param2,char *param3, char *param4)
    {
          va_list list;
          ......
          return;
    }
    

    在c语言中,函数参数是存储在栈中的,函数参数从右到左依次入栈。又Linux中,栈地址从高到低生长。因此,调用test函数时,其参数入栈情况如下:
    在这里插入图片描述
    调用va_start(list, param1)时,list指针指向情况如下:
    在这里插入图片描述
    最复杂的宏是va_arg。它必须返回一个由va_list所指向的恰当的类型的数值,同时递增va_list,使它指向参数列表中的一个参数(即递增的大小等于与va_arg宏所返回的数值具有相同类型的对象的长度)。因为类型转换的结果不能作为赋值运算的目标,所以va_arg宏首先使用sizeof来确定需要递增的大小,然后把它直接加到va_list上,这样得到的指针再被转换为要求的类型。因为该指针现在指向的位置"过"了一个类型单位的大小,所以我们使用了下标-1来存取正确的返回参数。

    实践

    基本用法

    #include <stdio.h>
    #include <stdarg.h>
    
    
    void simple_printf(const char *fmt, ...){
        va_list  args;   //  //定义一个具有va_list型的变量,这个变量是指向参数的指针。
        va_start(args, fmt);  // 第一个参数指向可变列表的地址,地址自动增加
    
        while (*fmt != '\0'){
            if (*fmt == 'd'){
                int i = va_arg(args, int );  //  访问下一个可变参数函数参数
                printf("%d\n", i);
            } else if (*fmt == 'c'){
                int c = va_arg(args, int);
                printf("%c\n", c);
            }else if (*fmt == 'f') {
                double d = va_arg(args, double);
                printf("%f\n", d);
            }
            ++fmt;
        }
    }
    
    int main(){
        simple_printf("dcff", 3, 'a', 1.99, 425.5);
    }
    

    在这里插入图片描述

    #include <stdio.h>
    #include <stdarg.h>
    #include <cmath>
    
    
    double stddev(int count, ...){
        double sum = 0;
        double sum_sq = 0;
        va_list  args;
        va_start(args, count);
        for (int i = 0; i < count; ++i) {
            double num = va_arg(args, double );
            sum += num;
            sum_sq += num * num;
        }
        va_end(args);
        return  sqrt(sum_sq/count - (sum/count)*(sum/count));
    }
    
    int main(){
        printf("%f\n", stddev(4, 25.0, 27.3, 26.9, 25.7));
    }
    

    在这里插入图片描述

    #include <stdio.h>
    #include <stdarg.h>
    
    
    double stddev(int count, ...){
        double sum = 0;
        va_list  args1, args2;
        va_start(args1, count);
        va_copy(args2, args1);
        for (int i = 0; i < count; ++i) {
            double num = va_arg(args1, double);
            sum += num;
        }
        for (int i = 0; i < count; ++i) {
            double num = va_arg(args2, double);
            sum += num;
        }
    
    
        va_end(args1);
        return sum;
    }
    
    int main(){
        printf("%f\n", stddev(4, 25.0, 27.3, 26.9, 25.7));
    }
    

    在这里插入图片描述

    #include  <stdio.h>
    #include  <stdarg.h>
    
    void error(char *format, ...)
    {
        va_list ap;
        va_start(ap, format);
        fprintf(stderr, "Error: ");
        vfprintf(stderr, format, ap);
        va_end(ap);
        fprintf(stderr, "\n");
        return;    
    }
    

    ·

    #include <stdio.h>
    #include <stdarg.h>
    #include <string.h>
    void acl_msg_printf(const char *fmt,...){
        char buf[2048];
        va_list ap;
    
        va_start (ap, fmt);
        vsnprintf(buf, sizeof(buf), fmt, ap);
        printf("%s\r\n", buf);
    
    
        va_end(ap);
    }
    
    int main(){
        acl_msg_printf("%s %d %s", "Failed", 100, "times");
    }
    
    

    在这里插入图片描述

    #include <stdio.h>
    #include <stdarg.h>
    #include <string.h>
    void acl_msg_error(char *format, ...)
    {
        va_list ap;
        va_start(ap, format);
        fprintf(stderr, "Error: ");
        vfprintf(stderr, format, ap);
        va_end(ap);
        fprintf(stderr, "\n");
        return;
    }
    
    int main(){
        acl_msg_error("%s %d %s", "Failed", 100, "times");
    }
    

    在这里插入图片描述

    include <cstdio>
    #include <cstdarg>
    #include <cstring>
    #include <memory>
    #include <string>
     
    std::string str_format(const char *fmt, ...)
    {
        int old_size = strlen(fmt);
        std::unique_ptr<char[]> buf(new char[old_size]);
        va_list ap;
         
        va_start(ap, fmt);
        int new_size = vsnprintf(buf.get(), old_size, fmt, ap);
        va_end(ap);
        if (new_size < 0)
            return "";
     
        buf.reset(new char[new_size + 1]);
        va_start(ap, fmt);
        new_size = vsnprintf(buf.get(), new_size + 1, fmt, ap);
        va_end(ap);
        if (new_size < 0)
            return "";
     
        return std::string(buf.get());
    }
     
    int main()
    {
        auto ret = str_format("%d %lf %s", 1, 3.14, "hello world");
        printf("%s\n", ret.c_str());  // 1 3.140000 hello world
        return 0;
    }
    
    展开全文
  • 变长参数va_list va_start va_arg va_end

    千次阅读 2020-06-29 16:34:16
    这种变长参数,需要使用va_list va_start va_end va_arg来访问参数。 下面是一个tutorialspoint 的一个使用demo,示范如何使用这几个接口 #include<stdarg.h> #include<stdio.h> int sum(int num_args, ...

    对于int printf(const char *format, ...);这种变长参数,需要使用va_list va_start va_end va_arg来访问参数。
    下面是一个tutorialspoint 的一个使用demo,示范如何使用这几个接口

    #include<stdarg.h>
    #include<stdio.h>
    int sum(int num_args, ...) {
       int val = 0, i;
       va_list ap;
       va_start(ap, num_args);
       for(i = 0; i < num_args; i++) {
          val += va_arg(ap, int);
       }
       va_end(ap);
       return val;
    }
    int main(void) {
       printf("Sum of 10, 20 and 30 = %d\n",  sum(3, 10, 20, 30) );
       printf("Sum of 4, 20, 25 and 30 = %d\n",  sum(4, 4, 20, 25, 30) );
    
       return 0;
    }
    

    编译运行之后的输出:

    Sum of 10, 20 and 30 = 60
    Sum of 4, 20, 25 and 30 = 79
    

    从上面看,访问变长参数需要遵循特定的接口,步骤如下:

    • 在调用参数表之前,定义一个 va_list 类型的变量ap;
    • 通过 va_start对ap进行初始化,之后ap指向可变参数表里面的第一个参数
    • 通过va_arg遍历参数,需要知道参数的类型,上面demo的参数全部是int型
    • 调用va_end关闭ap,以免发生误使用

    而对于printf这类变长参数接口,没有num_args传递参数的个数,而且变长参数的类型也各不相同,又是如何访问他们的。在glibc printf实现中,需要根据format中限定词Conversion specifiers来决定如何遍历变长参数,例如printf("str:%s num:%d\n", "nihao", a);,处理时遍历根据format中,当发现转义%之后开始解析,对于s说明第一个变长参数是char *类型,对于d说明第二个变长参数是int类型,即format中隐含变长参数的个数和类型。

    实现

    接着分析一下背后的原理是如何能够访问变长参数的。

    reflink:
    https://zh.wikipedia.org/zh-hans/X86%E8%B0%83%E7%94%A8%E7%BA%A6%E5%AE%9A#cdecl
    https://www.codeproject.com/Articles/1388/Calling-Conventions-Demystified
    https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html

    下面是程序员的自我修养-编译和链接中的示例,并且是baidu出的最广泛的结果

          typedef char * va_list;     // TC中定义为void*
          #define _INTSIZEOF(n)    ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) //为了满足需要内存对齐的系统
          #define va_start(ap,v)    ( ap = (va_list)&v + _INTSIZEOF(v) )     //ap指向第一个变参的位置,即将第一个变参的地址赋予ap
          #define va_arg(ap,t)       ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )   /*获取变参的具体内容,t为变参的类型,如有多个参数,则通过移动ap的指针来获得变参的地址,从而获得内容*/
          #define va_end(ap) ( ap = (va_list)0 )   //清空va_list,即结束变参的获取
    

    不过这种只适用于早期的cdecl函数调用方式(不信的可以自己试试结果),在x86平台上更是只能在32bit上使用,在64bit上就算限定使用cdecl约定也会被直接忽略。

    typedef char * va_list;
    #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 )
    
    int __attribute__((__cdecl__)) sum(int num_args, ...) {	//使用__attribute__((__cdecl__))指定使用
    。。。
    }
    

    之后使用gcc -m32编译成32bit应用,这样可以得到我们期望的结果。

    不过目前的调用约定默认都是fastcall了,在x86_64上前6个参数通过rdi, rsi, rdx, rcx, r8, r9寄存器传递,剩余的参数放在栈上传递。

    所以变长参数的访问方式和上面cdecl就不再相同了,下面探索一下现代的fastcall中是如何实现访问变长参数的。

    仍然使用最开始的例子,通过gcc --save-temps保留中间文件,特别是.i文件,看到其中sum的展开,它不再是个宏

    int sum(int num_args, ...) {
       int val = 0;
       va_list ap; 
       int i;
    
       __builtin_va_start(ap,num_args);
       for(i = 0; i < num_args; i++) {
          val += __builtin_va_arg(ap,int);
       }
       __builtin_va_end(ap);                                                                                                                       
    
       return val;
    }
    

    发现很难找到__builtin_va_start,__builtin_va_end的实现,它和va_list是arch相关的,在gcc内部实现的gcc/builtins.c

    /usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h
    #define va_start(v,l)   __builtin_va_start(v,l)                                    
    #define va_end(v)   __builtin_va_end(v)                                            
    #define va_arg(v,l) __builtin_va_arg(v,l)  
    

    在此不打算翻gcc的烂脚布了,后面我直接从汇编上分析这个过程,代码不多可以手动解析,不感兴趣的可以略过后面的汇编注释部分,直接看下面的文字描述

    1. 将所有上一帧的传参寄存器入栈保存到栈上addr处
    2. 构建va_list变量,struct __va_list_tag类型的,初始化其中的成员。reg_save_area指向寄存器传参保存的地址,当参数大于能够直接通过寄存器传递的数量时,保存到caller的栈上,overflow_arg_area指向caller中保存参数的位置。其中gp_offset代表addr中的偏移,fp_offset代表寄存器传参区域的虽大范围,x86_64上允许最多6个参数通过寄存器传参,而第一个rdi寄存器不可能传递的是变长参数,所以做多允许5个变长参数通过寄存器传递过来。
    3. 遍历变长参数列表,如果变长参数少于5个,则只需要访问reg_save_area区域,gp_offset代表偏移,每次遍历,gp_offset每次增加8个字节的偏移以指向下一个参数的地址。如果变长参数大于5个,则剩下的参数开始访问overflow_arg_area区域,每次遍历,overflow_arg_area地址增加8指向一个参数的地址。
      va_list

    究其背后,也就是一种函数调用约定的改变需要一种新的实现方式来访问变长参数,它只是约定的附属产物,如果你看不惯了,也可以自己实现一种,另一篇linux函数调用过程中的寄存器中已经说明了函数调用的约定,以及我们什么时候需要遵守这些约定。

    软件发展的这么快,我们不可能知道所有的知识,但是掌握其背后的原理能够帮助我们迅速掌握其他关联的知识。

    gdb main
    (gdb) ptype va_list	             //看一下va_list的类型,它不再是char *
    type = struct __va_list_tag {
        unsigned int gp_offset;
        unsigned int fp_offset;
        void *overflow_arg_area;
        void *reg_save_area;
    } [1]
    
    sum:
    .LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16 
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $92, %rsp
        movq    %rsi, -168(%rbp)		//寄存器保存到栈上,没有包括rdi寄存器,它不属于变长参数列表中
        movq    %rdx, -160(%rbp)
        movq    %rcx, -152(%rbp)
        movq    %r8, -144(%rbp)
        movq    %r9, -136(%rbp)
        testb   %al, %al 				//直接跳转到L4 label处
        je  .L4 
        movaps  %xmm0, -128(%rbp)	    //这里的浮点处理暂时不关心
        movaps  %xmm1, -112(%rbp)
        movaps  %xmm2, -96(%rbp)
        movaps  %xmm3, -80(%rbp)
        movaps  %xmm4, -64(%rbp)
        movaps  %xmm5, -48(%rbp)
        movaps  %xmm6, -32(%rbp)
        movaps  %xmm7, -16(%rbp)
    .L4:
        movl    %edi, -212(%rbp)    //num_arg保存到这里                                                                                                                  
        movl    $0, -180(%rbp)      //val = 0
        //va_start初始化
        movl    $8, -208(%rbp)      //gp_offset = 8,第一个寄存器肯定不能存储变长参数
        movl    $48, -204(%rbp)     //fp_offset = 48
        leaq    16(%rbp), %rax      //如果参数多于寄存器所能传的最大数量时,入栈保存。在rbp需要跨越16个字节,分别保存了上一个栈帧的rbp和返回地址
        movq    %rax, -200(%rbp)    //overflow_arg_area=上一个栈帧中可能保存参数的位置
        leaq    -176(%rbp), %rax    //参数寄存器保存到栈上的位置,并且加上了一个rdi寄存器的位置
        movq    %rax, -192(%rbp)    //reg_save_area=寄存器参数保存的位置
        movl    $0, -184(%rbp)		//i = 0
        jmp .L5
    .L8:
        movl    -208(%rbp), %eax
        cmpl    $48, %eax			//比较gp_offset和fp_offset,即最多支持5((48-8)/8)个直接参数
        jnb .L6                     //如果变长参数大于6个,到overflow_arg_area中取参数
        movq    -192(%rbp), %rdx    //根据gp_offset和reg_save_area找到参数的地址
        movl    -208(%rbp), %eax
        movl    %eax, %eax			//看不懂O(∩_∩)O~
        addq    %rdx, %rax
        movl    -208(%rbp), %edx
        addl    $8, %edx			//gp_offset += 8,更新gp_offset
        movl    %edx, -208(%rbp)    
        jmp .L7
    .L6:
        movq    -200(%rbp), %rdx	//剩余参数的获取
        movq    %rdx, %rax
        addq    $8, %rdx
        movq    %rdx, -200(%rbp)   //随着遍历参数,更新overflow_arg_area地址
    .L7:
        movl    (%rax), %eax
        addl    %eax, -180(%rbp)	//val+=va_arg(ap, int); 加法
        addl    $1, -184(%rbp)		//i++
    .L5:
        movl    -184(%rbp), %eax   //eax = i
        cmpl    -212(%rbp), %eax   //i < num_args
        jl  .L8
        movl    -180(%rbp), %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc   
    
    展开全文
  • 仔细研究了下va_list的实现原理。通过看对应的汇编代码实现和Intel ABI手册,发现: 在x86平台下,va_list可变传参是通过栈来进行; 在x64平台下,va_list可变传参是是默认的calling convention; 这是Inter官方...
  • #define G_DEFINE_TYPE(TN, t_n, T...#define G_DEFINE_TYPE_EXTENDED(TN, t_n, T_P, _f_, _C_) _G_DEFINE_TYPE_EXTENDED_BEGIN (TN, t_n, T_P, _f_) {_C_;} _G_DEFINE_TYPE_EXTENDED_END() #define _G_DEFINE_TYPE_.
  • include <stdio.h>

    千次阅读 2013-10-18 14:17:51
    #define __VALIST __gnuc_va_list #else #define __VALIST char* #endif #endif /* defined __VALIST */ /*  * The structure underlying the FILE type.  *  * Some believe that nobody in ...
  • C++ stdio.h详解

    千次阅读 2021-05-20 21:29:32
    #include文件的目的就是把多个编译单元(也就是c或者cpp文件)公用的内容,单独放在一个文件里减少整体代码尺寸;或者提供跨工程公共代码。在现行的c++版本中,应用这个头文件应是#include。 中文名跨工程公共代码...
  • g++宏扩展

    2019-07-27 17:23:09
    typedef __builtin_va_list __gnuc_va_list; # 29 "d:\\qtsdk\\mingw\\bin\\../lib/gcc/mingw32/4.4.0/http://www.cnblogs.com/http://www.cnblogs.com/include/stdio.h" 2 3 # 129 "d:\\qtsdk\\mingw\\bin\\../lib/...
  • C 标准库 - <stdio.h>

    2018-02-07 11:21:00
    #include文件的目的就是把多个编译单元(也就是c或者cpp文件)公用的内容,单独放在一个文件里减少整体代码尺寸;或者提供跨工程公共代码。 引用方法 #include<stdio.h> (注:在TC2.0中,允许不...
  • Lex和Yacc应用方法(一).初识Lex

    千次阅读 2011-06-09 20:28:00
    [root@localhost liweitest]cc -o parser lex.yy.c -ll [ 注意:如果不加 -ll 链结选项, cc 编译时会出现以下错误,后面会进一步说明。 ] /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/../../../crt1.o(.text+...
  • #include源代码

    2022-05-06 17:05:20
    _VALIST /* Also similarly, for the va_list type, defined in "stdarg.h" */ # if defined __GNUC__ && __GNUC__ >= 3 # define __need___va_list # include "stdarg.h" # define __VALIST __builtin_va_list # ...
  • stdio.h c头文件

    千次阅读 2014-04-25 08:08:53
    #include文件的目的就是把多个编译单元(也就是c或者cpp文件)公用的内容,单独放在一个文件里减少整体代码尺寸;或者提供跨工程公共代码。 stdio 就是指 “standard input & output"(标准输入输出)所以,源...
  • JNI_编程技术__网文整理(下)

    千次阅读 2012-05-24 11:40:37
    fieldID = (*env)->GetFieldID(env, objectClass, "charField", "C"); if (fieldID == NULL){ fieldID = (*env)->GetFieldID(env, objectClass,"charField", "D"); } return (*env)->GetIntField(env, ...
  • stdio.h的说明

    千次阅读 2015-07-29 09:11:02
    #define__VALIST__gnuc_va_list #else #define__VALISTchar* #endif #endif/*defined__VALIST*/ /* *ThestructureunderlyingtheFILEtype. * *...
  • JNI_编程技术__网文整理

    千次阅读 2010-10-26 15:24:00
    dll(c/c++) 文件的功能 ... 47 Chap9: 如何编写 jni 方法(转载) ... 55 1 、实例一:在 jni 中调用标准 c 中自带的函数 printf(): 57 2 、实例二、调用 c 语言用户定义的函数 ... 58 3 、实例...
  • GLib学习

    2022-05-14 22:40:56
    struct _GMainContext { /* The following lock is used for both the list of sources * and the list of poll records */ GMutex mutex; GCond cond; GThread *owner; guint owner_count; GMainContextFlags ...
  • 可变参宏函数格式:#define VATEST(fmt, ...) vaExample(fmt, ##__VA_ARGS__) 三个点(三个连续的英文格式的句号)表示0到n个参数,参数间用逗号隔开。 宏函数中的 ##__VA_ARGS__ 用于替换三个点。 对于宏函数变参...
  • stdio.h

    2013-10-17 17:24:04
     #define __VALIST __gnuc_va_list  #else  #define __VALIST char*  #endif  #endif /* defined __VALIST */  /*  * The structure underlying the FILE type.  *  * Some believe that nobody in their ...
  • build魔改之后变成这样了: [LOCATION]: /workspace/xulun/github/lang/emacs/src #SHELL (cd '/workspace/xulun/github/lang/emacs/src' && '/usr/bin/clang-10' '-cc1' '-triple' 'x86_64-pc-linux-gnu' '-analyze...
  • stdio.h文件介绍

    2012-01-13 11:03:00
    #include文件的目的就是把多个编译单元(也就是c或者cpp文件)公用的内容,单独放在一个文件里减少整体代码尺寸;或者提供跨工程公共代码。 目录 简介 使用 1.调用 2.stdio.h中的标准输入输出函数 3....
  • 关于stdio.h的一些说明

    千次阅读 2011-03-20 13:52:00
     #define __VALIST __gnuc_va_list  #else  #define __VALIST char*  #endif  #endif /* defined __VALIST */  /*  * The structure underlying the FILE type.  *  * ...
  • GNU 通过 __attribute__ 扩展的 format 属性,用来指定变参函数的参数格式检查。 它的使用方法如下: __attribute__(( format (archetype, string-index, first-to-check))) void LOG(const char *fmt, ...) __...
  • C 可变参数

    2019-07-18 15:02:31
    C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根据具体的需求接受可变数量的参数。下面的实例演示了这种函数的定义。 int func(int, ... ) { . . . } int main() { func(2, 2, 3); func(3...
  • stdio.c and ctype.c source

    2009-09-06 19:36:14
    [size=large][color=red][/color][/size] ...C"]00001 /* 00002 * Copyright (c) 1990 The Regents of the University of California. 00003 * All rights reserved. 00004 * 00005 * Redistribution a...
  • #ifdef __GNUC__ /* * In GNU the stack is not necessarily arranged very neatly in order to * pack shorts and such into a smaller argument list . Fortunately a * neatly arranged ...
  • 递归语法格式如下:...}imageC 语言支持递归,即一个函数可以调用其自身。但在使用递归时,程序员需要注意定义一个从函数退出的条件,否则会进入死循环。递归函数在解决许多数学问题上起了至关重要的作用,比如计...

空空如也

空空如也

1 2 3 4 5
收藏数 81
精华内容 32
关键字:

__valist __gnuc_va_list

友情链接: 程序.rar