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 ...
随机推荐
- vue 修饰符 整理
事件修饰符 .stop <!-- 阻止单击事件继续传播 --> <a v-on:click.stop="doThis"></a> .preven ...
- CF1096C Polygon for the Angle
思路: 要想到正n边形中所有可能的ang为180 * k / n (1 <= k <= n - 2). 根据n = 180 * k / ang, n是大于等于3的整数,并且n >= ...
- Java笔记 —— this 关键字
Java笔记 -- this 关键字 h2{ color: #4ABCDE; } a{ color: blue; text-decoration: none; } a:hover{ color: re ...
- 《spring技术内幕》读书笔记(1)——什么是POJO模式
今天在看<spring技术内幕>,第一章中多次提到了使用POJO来完成开发,就百度了一下,在此保留 1. 什么是POJO POJO的名称有多种,pure old java obje ...
- springboot项目搭建:结构和入门程序
Spring Boot 推荐目录结构 代码层的结构 根目录:com.springboot 1.工程启动类(ApplicationServer.java)置于com.springboot.build包下 ...
- sql注入【手工及一些工具】
Sql注入原理分析: 网站程序存在可控传递参数,参数未进行过滤直接带入数据库查询,导致攻击者可通过传递恶意sql语句代码进行执行攻击. Sql注入产生条件 1.必须有参数传递 2.参数值带入数据库查询 ...
- Element-ui(el-table、el-pagination)实现表格分页
HTML代码:(重点关注el-table中:data数据的绑定) el-pagination中: layout代表组件布局,子组件名用逗号分隔 属性: total代表总条目数 事件: current- ...
- Android程序员不容错过的10款在线实用工具
Android十款在线工具,在做Android开发过程中,会遇到一些小的问题,虽然自己动手也能解决,但是有了一些小工具,解决这些问题就得心应手了.Android在线工具,包括在线测试工具,及其他较为重 ...
- nodejs+MQTT协议实现远程主机控制
摘抄自百度:MQTT(MessageQueuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分. 所谓物联网,就是“万物互 ...
- 将Apache2.4手动安装成Windows的服务
将Apache2.4手动安装成Windows的服务 可以选择在安装Apache时自动将其安装为一个服务.如果选择"for all users",那么Apache将会被安装为服务. ...