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. jsp---tomcat===》》内置对象

    1.内置对象:    request: 方法:getParameter("txtName"):获取和页面上的name属性对象的value值       返回String       ...

  2. Android 接入支付宝支付实现

    接上篇android接入微信支付文章,这篇我们带你来接入支付宝支付服务 简介 首先要说明的是个人感觉接入支付宝比微信简单多了,很轻松的,所以同学们不要紧张~ 当然还是老规矩啦,上来肯定的贴上官网地址, ...

  3. Linux centos6.x 配置免密码登录

    免密码登录主要就是被访机器提供公匙给访问者,然后访问者使用ssh协议时可以使用所配置好的公匙验证.这样就免去了输入密码的麻烦. 某些集群例如hadoop,一般都需要将主机和其他机器间配置无密码公匙认证 ...

  4. Spring实践系列-入门篇(一)

    本文主要介绍了在本地搭建并运行一个Spring应用,演示了Spring依赖注入的特性 1 环境搭建 1.1 Maven依赖 目前只用到依赖注入的功能,故以下三个包已满足使用. <properti ...

  5. [原创]Debian9 从零编译配置Redis4.0

    序言 Redis 一.准备工作 1.1 更新系统安装包列表 没啥,就他喵想用个最新的. # apt update 1.2 创建需要使用的目录 创建目录source和web,分别用来放源码和编译后的文件 ...

  6. lucene的使用与优化

    1 lucene简介1.1 什么是luceneLucene是一个全文搜索框架,而不是应用产品.因此它并不像www.baidu.com 或者google Desktop那么拿来就能用,它只是提供了一种工 ...

  7. 笨办法学Python(七)

    习题 7: 更多打印 现在我们将做一批练习,在练习的过程中你需要键入代码,并且让它们运行起来.我不会解释太多,因为这节的内容都是以前熟悉过的.这节练习的目的是巩固你学到的东西.我们几个练习后再见.不要 ...

  8. mysql在控制台里出现中文问号问题

    由于重装了wampserver,之前遇到的问题统统会重新出现,那么今天遇到的是在mysql控制台中,在表里输入中文数据,却出现问号的问题: 那么这个就跟编码有关系了,那么,我们就去wampserver ...

  9. 2017.10.19 java中的关键词 及软件开发的一些重要单词

    1.java中的关键词 ·final 最后的 (1)可以修饰表示该类不能被继承 (2)可以修饰方法不能被重写 ·sattic 静态的 类的 (1)修饰属性 可以通过类直接调用 可以通过对象调用 (2) ...

  10. maven parent version not found

    需要把parent工程,也就是package是pom的那个工程先install一下 要是不行的话可以试下mvn -X clean install,-X表示强制从远程库更新dependency:再不行可 ...