c语言可变参数函数

c语言支持可变参数函数。这里的可变指,函数的参数个数可变。

其原理是,一般情况下,函数参数传递时,其压栈顺序是从右向左,栈在虚拟内存中的增长方向是从上往下。所以,对于一个函数调用 func(int a, int b, int c); 如果知道了参数a的地址,那么,可以推导出b,c的地址

#include <stdio.h>


void test(int a, int b, int c)
{
    printf("%p, %p, %p\n", &a, &b, &c);
}


int sum(int n, ...)
{
    int * p = &n;
    int s = 0;

    for (int i = 0; i < n; i++)
    {
        s += *(++p);
    }

    return s;
}


int main()
{
    test(1,3,4);
    printf("sum = %d\n", sum(3,4,5,6));

    return 0;
}

对于上面的代码,

suse10 32位,运行结果:

    0xbfc7d700, 0xbfc7d704, 0xbfc7d708
    sum = 15

vs2013 64位,运行结果:

    0046FBBC, 0046FBC0, 0046FBC4
    sum = 15

分析这两个结果,可以发现,test函数参数地址递增,相邻的差值是4。类似,所以sum函数,可以正确执行。

ubuntu 18.04 64位系统,test函数地址也是递增,相邻的差值是4。但是,sum函数并不能正确执行。分析其汇编代码后,发现,参数n后边紧跟的4字节并不是下一个参数的地址。下面三个参数地址相对于n的偏移分别是 -0xa8, -0xa0, -0x98。这和该版本ubuntu内核有关系吧。所以,sum函数也就无效了。

操作可变参数的宏

针对可变参数,系统提供了va_arg宏。man 3 va_arg可以看到文档。

具体包括

       #include <stdarg.h>

       void va_start(va_list ap, last);
       type va_arg(va_list ap, type);
       void va_end(va_list ap);
       void va_copy(va_list dest, va_list src);
stdarg.h 文件在 /usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h

在linux下,宏的定义如下:

#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)
#define va_copy(d,s)    __builtin_va_copy(d,s)

这是使用gcc内建的定义了,尴尬。到此打住,不深究了。

我们来看看vs2013的定义:

stdarg.h:
    #define va_start _crt_va_start
    #define va_arg _crt_va_arg
    #define va_end _crt_va_end
    
vadef.h:
    #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )  // 内存按照4字节对齐

    #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)) )  // 获取地址中的值。并移动pa指针
    #define _crt_va_end(ap)      ( ap = (va_list)0 )                   // 指针赋空

有了系统提供的宏,sum函数可以改写了:

int sum(int n, ...)
{
    va_list ap;
    int s = 0;

    va_start(ap, n);

    for (int i = 0; i < n; i++)
    {
        s += va_arg(ap, int);
    }

    return s;
}

这个函数可以在ubuntu 18.04 64位正确运行了。

printf函数

第一次遇到可变参数函数,就是这个printf函数了。懂了原理之后,我们可以写一个简单的:

#include <unistd.h>
#include <stdarg.h>


int print(const char * fmt, ...)
{
    char szbuf[2048] = {0};
    char *p = szbuf;
    va_list ap;

    va_start(ap, fmt);
  // 简单做了一下判断,不严谨
while (*fmt && p < szbuf) { if ('%' == *fmt) { ++fmt; switch (*fmt++) { case '%': *p++ = '%'; break; case 'c': *p++ = va_arg(ap, int); break; case 'd': { int num = va_arg(ap, int); char sztmp[40] = {0}; char *tmp = sztmp; while (num) { *++tmp = num % 10 + '0'; num /= 10; } while (tmp != sztmp) { *p++ = *tmp--; } break; } case 's': { char * tmp = va_arg(ap, char*); while (*tmp) { *p++ = *tmp++; } break; } } } else { *p++ = *fmt++; } } int len = p - szbuf; write(0, szbuf, len); va_end(ap); return len; } int main() { print("hello\n"); int ret = print("%d, %d\n", 82,87635); print("ret = %d\n", ret); print("zhe shi yige jieguo ret = %d, per = %%%d\n", 23, 98); return 0; }

 

posted @ 2018-10-26 21:47  二狗啸地  阅读(945)  评论(0编辑  收藏  举报