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 = ; 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语言可变参数函数的更多相关文章
- C语言可变参数函数实现原理
一.可变参数函数实现原理 C函数调用的栈结构: 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈. 本 ...
- C语言可变参数函数的编写
1. 引言 C语言我们接触的第一个库函数是 printf(“hello,world!”);其参数个数为1个. 然后,我们会接触到诸如: printf(“a=%d,b=%s,c=%c”,a,b,c);此 ...
- C语言可变参数函数详解示例
先看代码 printf(“hello,world!”);其参数个数为1个. printf(“a=%d,b=%s,c=%c”,a,b,c);其参数个数为4个. 如何编写可变参数函数呢?我们首先来看看pr ...
- C语言中可变参数函数实现原理
C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fu ...
- C语言中的可变参数函数
C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为: int printf( const char* format, ...); 它除了有一个参数format固定以外 ...
- [11 Go语言基础-可变参数函数]
[11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...
- C语言学习020:可变参数函数
顾名思义,可变参数函数就是参数数量可变的函数,即函数的参数数量是不确定的,比如方法getnumbertotal()我们即可以传递一个参数,也可以传递5个.6个参数 #include <stdio ...
- C可变参数函数 实现
转自:http://blog.csdn.net/weiwangchao_/article/details/4857567 C函数要在程序中用到以下这些宏: void va_start( va_list ...
- 转:C语言 可变参数
C语言 可变参数 堆栈一般是怎么压栈处理的 /* * stack space: * * 参数3 | up * 参数2 | * 参数1 v ...
随机推荐
- 单元测试模拟request后台
编写测试单元 @RunWith(SpringJUnit4ClassRunner.class) 让测试运行于Spring测试环境 @WebAppConfiguration是一个类级别的注释,用于声明Ap ...
- python的变量命名及其使用
以下内容皆从书本自学 ,归纳. 1. 变量名只能包含字母.数字和下划线.变量名可以字母或下划线打头,但不能以数字打头,例如,可将变量命名为message_1,但不能将其命名为1_message. 2. ...
- JSON 常量详情参考 (内含对中文不转义的参数)
JSON_ERROR_NONE (integer) 没有错误发生.自 PHP 5.3.0 起生效. JSON_ERROR_DEPTH (integer) 到达了最大堆栈深度.自 PHP 5.3.0 起 ...
- redis在Windows下以后台服务一键搭建哨兵(主从复制)模式(多机)
redis在Windows下以后台服务一键搭建哨兵(主从复制)模式(多机) 一.概述 此教程介绍如何在windows系统中多个服务器之间,布置redis哨兵模式(主从复制),同时要以后台服务的模式运行 ...
- AOP术语分析
初看这么多术语,一下子都不好接受,慢慢来,很快就会搞懂. 通知.增强处理(Advice) 就是你想要的功能,也就是上说的安全.事物.日子等.你给先定义好,然后再想用的地方用一下.包含Aspect的一段 ...
- centos命令行常用操作
1.查看某个端口占用 lsof -i tcp:80 2.查看是否安装了防火墙 service iptables status 查看是否启用防火墙/etc/init.d/iptables status ...
- 磁盘空间满了之后MySQL会怎样
大多数用户在对于磁盘进行分区的时候都是习惯性的不给系统盘预留很大空间,其实这并不是一个好习惯.因为系统分区并不像我们想象的那样会仅仅安装一个操作系统,系统分区多数还是会承载操作系统主要应用软件安装任务 ...
- 正交矩阵、正规矩阵和酉矩阵(转自Ramble Over The Cloud~)
网址:http://blog.csdn.net/alec1987/article/details/7414450 在数学中,正规矩阵 是与自己的共轭转置交换的复系数方块矩阵,也就是说, 满足 其中 是 ...
- 【转载】#344 - Hidden Base Class Member Is Invoked Based on Declared Type of Object
When you use the new modifier to hide a base class method, it will still be called by objects whose ...
- Uva 12299 带循环移动的RMQ(线段树)
题目链接:https://vjudge.net/contest/147973#problem/C 题意:传统的RMQ是一个不变的数组a求区间最值.现在要循环移动(往前移动). 分析:求区间问题,很容易 ...