【C语言】浅谈可变参数与printf函数
一.何谓可变参数
int printf( const char* format, ...);
这是使用过C语言的人所再熟悉不过的printf函数原型,它的参数中就有固定参数format和可变参数(用”…”表示).
而我们又可以用各种方式来调用printf,如:
printf( "%d ",value);
printf( "%s ",str);
printf( "the number is %d ,string is:%s ", value, str);
二.实现原理
C语言用宏来处理这些可变参数。
这些宏看起来很复杂,其实原理挺简单:就是根据参数入栈的特点从最靠近第一个可变参数的固定参数开始,依次获取每个可变参 数的地址.下面我们来分析这些宏.
在VC中的stdarg.h头文件中,针对不同平台有不同的宏定义,我们选取X86平台下的宏定义:
typedef char *va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v)( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
可以用下图来表示:
在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。
|——————————————————————————|
|最后一个可变参数 | -> 高内存地址处
|——————————————————————————|
...................
|——————————————————————————|
|第N个可变参数 | ->
va_arg(arg_ptr,int)后arg_ptr所指的地方,
| | 即第N个可变参数的地址。
|——————————————— |
………………………….
|——————————————————————————|
|第一个可变参数 | ->
va_start(arg_ptr,start)后arg_ptr所指的地方
| | 即第一个可变参数的地址
|——————————————— |
|———————————————————————— ——|
| |
|最后一个固定参数 | -> start的起始地址
|—————————————— —| .................
|—————————————————————————— |
| |
|——————————————— |-> 低内存地址处
三.printf研究
下面是一个简单的printf函数的实现,参考了中的156页的例子,读者可以结合书上的代码与本文参照。
#include <stdio.h>
#include <stdlib.h>
//一个简单的类似于printf的实现,//参数必须都是int 类型
void myprintf(char* fmt, ...){
char* pArg=NULL; //等价于原来的va_list
char c;
pArg = (char*)&fmt; //注意不要写成p = fmt !!因为这里要对参数取址,而不是取值
pArg += sizeof(fmt); //等价于原来的va_start
do{
c =*fmt;
if (c != '%'){
putchar(c); //照原样输出字符
}else{
//按格式字符输出数据
switch(*(++fmt)){
case 'd':
printf( "%d",*((int*)pArg));
break;
case 'x':
printf( "%#x",*((int*)pArg));
break;
default:
break;
}
pArg += sizeof(int); //等价于原来的va_arg
}
++fmt;
}while (*fmt != '');
pArg = NULL; //等价于va_end
return;
}
int main(){
int i = 1234;
int j = 5678;
myprintf( "the first test:i=%d
",i);
myprintf( "the secend test:i=%d; %x;j=%d; ",i,0xabcd,j);
system( "pause ");
return 0;
}
输出:
the first test:i=1234
the secend test:i=1234; 0xabcd;j=5678;
四.应用
求最大值:
#include <stdarg.h>//不定数目参数需要的宏
#include <stdio.h>
int max(int n,int num, ...){
int m = num;
int i = 0;
va_list x;//说明变量x
va_start(x,num);//x被初始化为指向num后的第一个参数
for(i=1; i<n; i++){
//将变量x所指向的int类型的值赋给y,同时使x指向下一个参数
int y = va_arg(x,int);
if(y>m){
m=y;
}
}
va_end(x);//清除变量x
return m;
}
int main(){
int ret1 = max(3,5,56,55);
int ret2 = max(6,0,4,32,45,533,6565);
printf( "%d,%d ",ret1,ret2);
return 0;
}
输出:
56,6565
(本文来源于互联网,若有侵权,请联系博主)
【C语言】浅谈可变参数与printf函数的更多相关文章
- 利用可变参数模拟Printf()函数实现一个my_print()函数和调用可变参数注意的陷阱!
可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈. 例如,对于函数: void test(char a ...
- 可变参数模拟printf()函数实现一个my_print()函数以及调用可变参数需注意的陷阱
入栈规则 可变参数函数的实现与函数调用的栈帧结构是密切相关的.所以在我们实现可变参数之前,先得搞清楚 栈是怎样传参的. 正常情况下,C的函数参数入栈遵照__stdcall规则, 它是从右到左的,即函数 ...
- Keil c中自定义带可变参数的printf函数
在嵌入式c中,往往采用串口打印函数来实现程序的调试,而在正式程序中一般是不需要这些打印代码的,通常做法是在这些调试用打印代码的前后设置一个宏定义块来实现是否启用这段代码,比如: // other us ...
- C语言中的可变参数-printf的实现原理
C语言中的可变参数-printf的实现原理 在C/C++中,对函数参数的扫描是从后向前的.C/C++的函数参数是通过压入堆栈的方式来给函数传参数的(堆栈是一种先进后出的数据结构),最先压入的参数最后出 ...
- C语言中的可变参数函数
C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为: int printf( const char* format, ...); 它除了有一个参数format固定以外 ...
- C语言笔记 12_可变参数&内存管理&命令行参数
可变参数 有时,您可能会碰到这样的情况,您希望函数带有可变数量的参数,而不是预定义数量的参数.C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根据具体的需求接受可变数量的参数.下面的实例 ...
- C语言怎么实现可变参数
可变参数 可变参数是指函数的参数的数据类型和数量都是不固定的. printf函数的参数就是可变的.这个函数的原型是:int printf(const char *format, ...). 用一段代码 ...
- C语言中的可变参数函数的浅析(以Arm 程序中的printf()函数实现为例) .
我们在C语言编程中会遇到一些参数个数可变的函数,一般人对它的实现不理解.例如Printf(): Printf()函数是C语言中非常常用的一个典型的变参数函数,它 的原型为: int printf( c ...
- python学习(28) 浅谈可变对象的单例模式设计
python开发,有时候需要设计单例模式保证操作的唯一性和安全性.理论上python语言底层实现和C/C++不同,python采取的是引用模式,当一个对象是可变对象,对其修改不会更改引用的指向,当一个 ...
随机推荐
- 小机器人自动回复(python,可扩展开发微信公众号的小机器人)
api来之图灵机器人.我们都知道微信公众号可以有自动回复,我们先用python脚本编写一个简单的自动回复的脚本,利用图灵机器人的api. http://www.tuling123.com/help/h ...
- sublime与Emment
sublime与Emment 作为一个开发者,想必用过sublime和Emment 的无不大快朵颐,这两者结合在一起简直是天合之作.它不仅仅提高编码的速度而且令开发者感到编码的乐趣和舒适感,今天准备写 ...
- 理解 ES6 语法中 yield 关键字的返回值
在 ES6 中新增了生成器函数的语法,本文解释了生成器函数内 yield 关键字的返回值. 描述 根据语法规范,yield 关键字用来暂停和继续执行一个生成器函数.当外部调用生成器的 next() 方 ...
- SDOI Day1
好了做了SDOI day1的3道题,来讲下做法及感想吧 T1:排序(暴力,搜索) 题目:http://www.lydsy.com/JudgeOnline/problem.php?id=3990 我们可 ...
- react-router 中的history(react中关于后退键的处理用的到)
react-router 中的history react-router 是建立在history之上的:我们来谈谈这个history吧. github: mjackson/history history ...
- 用DotRas来连接VPN网络
最近要用程序来不断的连接VPN(为什么要这样就不讨论了),开始用的是如下代码: public static bool ADSL() { bool flag = true; do { Console.W ...
- 阅读Facebook POP框架 笔记(一)
在这一系列文章里,我主要会将自己阅读第三方代码的经历记录下来,尝试独立分析解剖一个框架.之前也阅读过一些第三方代码,但是实际上来说对自己的成长并没有太大的帮助,因为阅读的不细致,没有领会到代码的精髓. ...
- MD5算法学习整理
一路学来 记得笔记不少 但是给自己看 当时就记得很随意 以为后面都懂 但还是太单纯了,现在回顾 自己都完全看不下去,所以以后的都放的博客上,让自己看懂,让感兴趣的看懂,详细,暴力 不废话了 MD5算法 ...
- [Selenium With C#学习笔记] Lesson-03 超级链接
超级链接或链接是Web页面的基本元素之一,而通过超级链接使得万维网可以互联互通.一个典型的链接如下所示: HTML源码如下所示: <a href="index.html" i ...
- 关于margin
----W3School -----margin CSS 边距属性定义元素周围的空间.通过使用单独的属性,可以对上.右.下.左的外边距进行设置.也可以使用简写的外边距属性同时改变所有的外边距. 这个简 ...