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 = ; for (int i = ; i < n; i++)
{
s += *(++p);
} return s;
} int main()
{
test(,,);
printf("sum = %d\n", sum(,,,)); return ;
}

对于上面的代码,

suse10 32位,运行结果:

    0xbfc7d700, 0xbfc7d704, 0xbfc7d708
sum =

vs2013 64位,运行结果:

    0046FBBC, 0046FBC0, 0046FBC4
sum =

分析这两个结果,可以发现,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 = ; va_start(ap, n); for (int i = ; 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[] = {};
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[] = {};
char *tmp = sztmp;
while (num)
{
*++tmp = num % + '';
num /= ;
}
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(, szbuf, len); va_end(ap); return len;
} int main()
{
print("hello\n"); int ret = print("%d, %d\n", ,); print("ret = %d\n", ret); print("zhe shi yige jieguo ret = %d, per = %%%d\n", , ); return ;
}

c语言可变参数函数的更多相关文章

  1. C语言可变参数函数实现原理

    一.可变参数函数实现原理 C函数调用的栈结构: 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈. 本 ...

  2. C语言可变参数函数的编写

    1. 引言 C语言我们接触的第一个库函数是 printf(“hello,world!”);其参数个数为1个. 然后,我们会接触到诸如: printf(“a=%d,b=%s,c=%c”,a,b,c);此 ...

  3. C语言可变参数函数详解示例

    先看代码 printf(“hello,world!”);其参数个数为1个. printf(“a=%d,b=%s,c=%c”,a,b,c);其参数个数为4个. 如何编写可变参数函数呢?我们首先来看看pr ...

  4. C语言中可变参数函数实现原理

    C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fu ...

  5. C语言中的可变参数函数

    C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为: int printf( const char* format, ...); 它除了有一个参数format固定以外 ...

  6. [11 Go语言基础-可变参数函数]

    [11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...

  7. C语言学习020:可变参数函数

    顾名思义,可变参数函数就是参数数量可变的函数,即函数的参数数量是不确定的,比如方法getnumbertotal()我们即可以传递一个参数,也可以传递5个.6个参数 #include <stdio ...

  8. C可变参数函数 实现

    转自:http://blog.csdn.net/weiwangchao_/article/details/4857567 C函数要在程序中用到以下这些宏: void va_start( va_list ...

  9. 转:C语言 可变参数

    C语言 可变参数 堆栈一般是怎么压栈处理的 /* * stack space: * *        参数3   |    up *        参数2   | *        参数1   v   ...

随机推荐

  1. Html+CSS--->第一周初探

    html css 学了一周的前端,谈谈我的感想 我一直在使用sublime text 3来编辑我的代码,其中有很多很好用的快捷键和插件大大提高了我的开发效率 在这里我极力推荐使用编辑器来进行学习前端. ...

  2. Mincost

    The cost of taking a taxi in Hangzhou is not a constant for each kilometer you travel: the first 4 k ...

  3. 利用C语言编辑画图程序的实现方法

    不知道大家在进行开发县级电网调度自动化系统的时候,是否都会遇到一个问题就是:要绘制一个电力系统一次接线图.大家都应该知道其实电力系统的一次接线图是较为复杂的,如果想要使用一般的编程方法来进行绘制的话, ...

  4. php session小节

    1.为什么要用session? 在人们访问网站的时候,有很多个网页,由于http自身的特点,用户每执行一个脚本都需要和web服务器重新建立连接.由于他们之间是无状态的,这次的连接无法得到上次连接的状态 ...

  5. 静态库是.o文件的集合与弱符号

    静态库是.o文件的集合. 静态库与弱符号的概念相关联. 在生成库文件时,不做强符号检查.

  6. react里面Fragments的使用

    关于react Fragments,React 中一个常见模式是为一个组件返回多个元素.Fragments 可以让你聚合一个子元素列表,并且不在DOM中增加额外节点. render() { retur ...

  7. 你不得不掌握的thinkphp5

    thinkphp官网在去年的时候发布了tp的颠覆版本thinkphp5,tp5确实比之前的版本好用了很多,增加了很多的一些特性,它采用全新的架构思想,引入了更多的PHP新特性,优化了核心,减少了依赖, ...

  8. P3901 【数列找不同】

    这个题我们可以使用树状数组做 啥? 树状数组? 那个不是维护前缀和的东西吗? 各位看官,让我慢慢道来. 首先我们可以想到,对于一个询问$ [l,r] \(,只有\)[1,r]$中的数可能对这个询问有影 ...

  9. javascript原生API总结

    一.查找: getElementById() 方法返回带有指定 ID 的元素(唯一): getElementsByTagName() 返回包含带有指定标签名称的所有元素的节点列表(集合/节点数组). ...

  10. quartz的持久化任务调度使用应用的dataSource

    Quartz提供两种基本作业存储类型--->第一种类型叫做RAMJobStore:     最佳的性能,因为内存中数据访问最快     不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所 ...