linux c可变参数va_start、va_end、va_arg、va_list

2018-06-29 17:12:21

va_start、va_end、va_arg、va_list 其实是宏定义,在不定参数函数中会用到。

在解释上面几个之前,我们先来做几件事。


确定栈增长方向

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

void test2();

void test1(){
    int a = 1;
    printf("a %p\n", &a);
    
    test2();
}

void test2(){
    int b = 1;
    printf("b %p\n", &b);
}

int main(int argc, char *argv[]){
    test1();
    
    return 0;
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
a 0x7ffc59d91b5c
b 0x7ffc59d91b3c

上面 test1 调用 test2,所以test1函数中的变量肯定比test2函数变量要先入栈,所以 变量a 先入栈。从输出的结果可以看出,a地址 > b地址。

得出结论先入栈的地址在高位,栈是向虚拟地址减少的方向增长(不同的环境可能不一样)。


确定函数参数入栈顺序

#include <stdio.h>

void test(int a, int b, char *aa, char *bb, char aaa,char bbb){
    printf("%p %p %p %p %p %p\n", &a, &b, &aa, &bb, &aaa, &bbb);
}

int main(int argc, char *argv[]){
    test(1,2,"沧浪水", "http://www.freecls.com",'a','b');
    
    return 0;
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
0x7ffc985f6abc 0x7ffc985f6ab8 0x7ffc985f6ab0 0x7ffc985f6aa8 0x7ffc985f6aa4 0x7ffc985f6aa0

从输出可以看出 参数a 地址最大,所以参数a先入栈,所以函数参数是从左往右依次入栈。


了解va_start、va_end...原理

从上文我们可以看出,函数参数是依次压入栈的,大小会根据实际类型,最小为int型(从上面可以看出,char型也占用4个字节)。所以要实现可变参数函数,我们必须知道以下3个条件:

1.最后一个已知参数的地址
2.未知参数的个数
3.每个未知参数的类型

就可以通过指针移动,分别取得各个参数。下面内核3.16 的相关宏定义,有兴趣的朋友可以自己去研究。

#ifndef va_arg

#ifndef _VALIST
#define _VALIST
typedef char *va_list;
#endif				/* _VALIST */

/* Storage alignment properties */

#define  _AUPBND                (sizeof (acpi_native_int) - 1)
#define  _ADNBND                (sizeof (acpi_native_int) - 1)

/* Variable argument list macro definitions */

#define _bnd(X, bnd)            (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T)           (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap)              (ap = (va_list) NULL)
#define va_start(ap, A)         (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))

#endif				/* va_arg */


va_start、va_end...用法

在了解了原理之后,我们通过例子来讲解实际用法。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdarg.h>

#define BUF_SIZE 500

//未知类型,未知参数个数格式化
void errExit(int err_code, char *format, ...){
    char userMsg[BUF_SIZE];
    va_list argList;
    
    //这里的第二个参数为最后一个已知参数
    va_start(argList, format);
    
    vsnprintf(userMsg, BUF_SIZE, format, argList);
    
    va_end(argList);

    printf("%s", userMsg);
    exit(err_code);
}

//已知参数个数,已知类型
//第一个参数用来指定后面参数个数
//后面的参数类型全部为int
void dosum(int sum, ...){
    va_list argList;
    
    va_start(argList, sum);
    
    int n = 0;
    int i = 0;
    for(; i < sum; i++){
        n = n + va_arg(argList, int);
    }
    
    va_end(argList);
    
    printf("sum:%d\n", n);
}

int main(int argc, char *argv[]){
    dosum(4, 1,2,3,4);
    errExit(1, "%s %s\n", "hello", "freecls");
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
sum:10
hello freecls

读者只要记住一点,想要实现可变参数函数,必须满足上面提到的3个条件,当然实现方式各种各样。比如 vsnprintf() 它是通过传入格式化字符串来确定参数个数以及参数类型的(下面代表3个参数,类型分别为 int、char *、char)。

"%d %s %c\n"


 备注

1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/67 

 

©著作权归作者所有
收藏
推荐阅读
  • linux c字符串转数字

    #字符串转int型 int atoi(const char *str); //等同于strtol(str, NULL, 10),只是atoi不检测错误 #字符串转long型 long int ato...

  • c标准库 string.h

    库变量size_t 无符号整形,通常为sizeof,strlen的结果。库宏命令NULL 空指针常量。库函数size_t strlen(const char *s); 计算字符串长度。char *st...

  • linux c预处理器

    #define定义明显常量(符号常量), &nbsp;定义宏命令,定义后,编译器在预处理阶段就会直接替换值,所以一些关系到运算符优先级的尽量多的使用括号。#include&lt;stdio.h&gt;...

  • linux c按位运算符

    按位取反:~0变成1,1变成0。按位与:&amp;两边都为1的为1,其他为0。按位或:|只要有一方为1的就为1,其他为0。按位异或:^一方为0,一方为1的为1,其他为0例子#include&lt;st...

  • linux c其他复杂的声明以及typedef

    先了解下优先级,[]和()的优先级相同,他们比*的优先级高,结合是从左往右。int * days[5]days是数组,包含5个元素(int型指针)int (* days)[5]days是指针,指向一个...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。