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. Django实战-用户注册和登陆系统

    1.环境搭建和创建项目 1.环境搭建 每当我们开始一个新项目的时候,通常都会搭建一个全新.独立.隔离的项目环境,这样做的好处自然不必多说.有很多种建立项目虚拟环境的工具,使用比较普遍的是Python中 ...

  2. SpringCloud的学习记录(7)

    这一章节讲zuul的使用. 在我们生成的Demo项目上右键点击New->Module->spring Initializr, 然后next, 填写Group和Artifact等信息, 这里 ...

  3. VS 打开时默认使用管理员权限

    1. 打开VS的安装目录,找到devenv.exe,右键,选择“兼容性疑难解答”. 2. 选择“疑难解答程序” 3. 选择“该程序需要附加权限” 4. 确认用户帐户控制后,点击测试程序,不然这个对话框 ...

  4. oracle自动异地备份数据库

    需求:实现oracle自动异地备份数据库 分析:1.oracle备份数据库     2.自动备份(定时)   3.非本地备份(因为如果备份到本地的话,如果硬件设备损坏可能导致数据丢失) 知识点:1.备 ...

  5. nginx配置vhost配置文件详解

    //千锋PHP-PHP培训的实力派server { listen 80; server_name www.sina.com; root /data/www/sina; index index.php; ...

  6. Selenium入门18 断言

    自动化测试需对比实际结果与预期结果,给出测试结论. 1 条件判断 if ...else... 2 assert ... #coding:utf-8 #断言 from selenium import w ...

  7. IOS GCD(线程的 串行、并发 基本使用)

    什么是GCD 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器” 纯C语言,提供了非常多强大的函数 GCD的优势 GCD是苹果公司为多核的并行运算提出的解决方案 GCD会自 ...

  8. MYSQL的基本语法

    .默认约束 区别:mysql里面DEFAULT关键字后面是不用加括号的 复制代码 代码如下: --sqlserver CREATE TABLE emp ( id INT DEFAULT() ) --m ...

  9. Android(java)学习笔记71:Tab标签的使用

    1. 案例1---TabProject (1)首先是main.xml文件: <?xml version="1.0" encoding="utf-8"?&g ...

  10. 前端高质量知识(一)-JS内存空间详细图解

    变量对象与堆内存   var a = 20;   var b = 'abc';   var c = true;   var d = { m: 20 } 因为JavaScript具有自动垃圾回收机制,所 ...