函数原型: int printf(const char *format[,argument]...)

返 回 值: 成功则返回实际输出的字符数,失败返回-1.

函数说明:

使用过C语言的人所再熟悉不过的printf函数原型,它的参数中就有固定参数format和可变参数(用"…"表示),format后面的参数个数不确定,且类型也不确定,这些参数都存放在栈内。而程序员又可以用各种方式来调用printf,如: 

            printf("%d ",value);   

            printf("%s ",str);   

            printf("the number is %d,string is:%s ",value,str); 

调用printf()函数时,根据format里的格式("%d %f...")依次将栈里参数取出。而取出动作要用到va_arg、va_end、va_start这三个宏定义,再加上va_list。

     (1)va_list事实上是一char *类型,即:

            typedef char* va_list;

     (2)三个宏定义:

        #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) 

为了字节对齐,将n的长度化为int长度的整数倍。

补充:对((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) 的解释

1.举个栗子解释一下内存对齐是什么?

      比方说有一个箱子可以装4个瓶子,我有8个瓶子 ,那么我需要2个箱子如果我有10个瓶

子呢,我不能说我需要10除4,需要2.5个箱子吧。实际上我需要3个箱子,那怎么求我实际需

要的箱子数呢?

用一个容易理解的公式来求上述问题:

设我的瓶子数为B,我需要的箱子数为C,一个箱子最多可以装A个瓶子。

公式:C=(B+A-1)/A

带入几个例子:

B=10,  A=4   得C=13/4 ,C=3        每个箱子最多能装4个瓶子,10个瓶子需要3个箱子

B=14,A=4  得C=17/4  ,C=4        每个箱子最多能装4个瓶子,14个瓶子需要4个箱子

2.细致的分析一下((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1)) 

((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1)) :  ((sizeof(n)+sizeof(int)-1)其实就是 (B+A-1)

a、(sizeof(n)是我们需要的实际内存大小,相对于我的瓶子数。

b、sizeof(int)是内存大小分配的最小刻度,相对于箱子最多可以装瓶子的个数。

c、在32位系统中,~(sizeof(int)–1) )展开为~(4-1)=~(00000011b)=11111100b,这样

任何数 & ~(sizeof(int)–1) )后最后两位肯定为0,也就是4的整数倍。

// e、((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1)) & ~(sizeof(int)-1))其实就是  除以A或者说除

// 以4;((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1)) 其实就是实现栗子中的(B+A-1)/A。

// (此处描述错误,并不是/4)

总结:

_INTSIZEOF(n)整个做的事情就是将n的长度化为int长度的整数倍。

比如n为5,二进制就是101b,int长度为4,二进制为100b,那么n化为int长度的整数倍就应

该为8。~(sizeof(int) – 1) )就应该为~(4 - 1) = ~(00000011b) = 11111100b,这样任何

数 & ~(sizeof(int) – 1) )后最后两位肯定为0,就肯定是4的整数倍了。

(sizeof(n) + sizeof(int) – 1)就是将大于4m但小于等于4(m + 1)的数提高到大于等于

4(m + 1)但小于4(m + 2),这样再& ~(sizeof(int) – 1))后就正好将原长度补齐到4的倍数

了。

        #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) 

通过该宏定义可以获取到可变参数表的首地址,并将该地址赋给指针ap。

         
 

#define va_arg(ap,type) ( *(type *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 

通过该宏定义可以获取当前ap所指向的可变参数,并将指针ap指向下一个可变参

数。注意,该宏的第二个参数为类型。

        #define va_end(ap)          ( ap = (va_list)0) 

通过该宏定义可以结束可变参数的获取。

可以看出,该函数的参数格式不固定,参数类型不固定。在C语言中使用宏来处理这些可变参数。这些宏看起来很复杂,其实原理挺简单,即根据参数入栈的特点从最靠近第一个可变参数的固定参数开始,依次获取每个可变参数的地址。

 
 

程序员通过这三个宏定义就可以实现对可变参数的处理。例如:

1 #include <stdio.h>  
2   
3 typedef char* va_list;   
4
5 #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))   
6 #define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )   
7 #define va_arg(ap,type) ( *(type *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )   
8 #define va_end(ap)      ( ap = (va_list)0 )   
9   
10 int cal_val(int c, ...)   
11 {   
12         int sum = c;   
13         va_list ap;              //声明指向char型的指针  
14     va_start(ap, c);         //获取可变参数列表的首地址,并赋给指针ap  
15   
16     c = va_arg(ap, int);     //从可变参数列表中获取到第一个参数(返回值即为参数)  
17     while(0 != c)   
18     {   
19         sum += c;   
20         c = va_arg(ap,int);  //循环的从可变参数列表中获取到参数(返回值即为参数)  
21     }  
22     va_end(ap);              //结束从可变参数列表中获取参数  
23     return sum;   
24 }    
25    
26 int main(int argc, char* argv[])   
27 {   
28         int value1, value2; 
29         value1 = cal_val(1,2,3,4,5,6,7,8,9,0);   
30         printf("value1=%d/n",value1);  
31         value2 = cal_val(6,7,8,9,0);   
32

C语言中可变参数的原理——printf()函数的更多相关文章

  1. C语言中可变参数的函数(三个点,“...”)

    C语言中可变参数的函数(三个点,“...”) 本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end ...

  2. C语言中可变参数函数实现原理

    C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fu ...

  3. C语言中可变参数的用法

    原文地址: http://blog.csdn.net/wooin/archive/2006/04/29/697106.aspx   我们在C语言编程中会遇到一些参数个数可变的函数,例如printf() ...

  4. C语言中可变参数的使用

    在C语言程序编写中我们使用最多的函数一定包括printf以及很多类似的变形体.这个函数包含在C库函数中,定义为 int printf( const char* format, ...); 除了一个格式 ...

  5. 可变参数列表与printf()函数的实现

    问题 当我们刚开始学习C语言的时候,就接触到printf()函数,可是当时"道行"不深或许不够细心留意,又或者我们理所当然地认为库函数规定这样就是这样,没有发现这个函数与普通的函数 ...

  6. c 中可变参数的实现

    我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:     例一: int   printf(   const   char*   format,   ... ...

  7. C/C++函数调用时传参过程与可变参数实现原理

    C/C++函数调用时传参过程与可变参数实现原理 C语言的经典swap问题 在学习C语言的时候,我们大都遇到过一些经典例题,这些经典例题背后所代表的是往往是C/C++背后的一些运行原理,比如下面这个示例 ...

  8. [11 Go语言基础-可变参数函数]

    [11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...

  9. C语言的可变参数在Linux(Ubuntu)与Windows下注意点

    基本上C语言的可变参数原理在不同平台和不同编译器下基本类似(通过函数入栈,从右向左,从高位到低位地址),不过部分实现会有所不同:在使用中需要注意的是: va_list 为char 类型指针,部分调用如 ...

随机推荐

  1. mybatis 多参数传递

    参考: 1. MyBatis传入多个参数的问题 http://www.cnblogs.com/mingyue1818/p/3714162.html2. MyBatis报错 Parameter '0' ...

  2. swoole 消息队列

    <?php /** * 场景: * 监控订单表状态 队列通信 * 一个进程向队列发布消息 另外两个进程争抢 */ //设置主进程名 echo '主进程id:' . posix_getpid() ...

  3. C语言入门---第七章 C语言函数

    函数就是一段封装好的,可以重复使用的代码,它使得我们的程序更加模块化,不需要编写大量重复的代码.函数可以提前保存起来,并给它起一个独一无二的名字,只要知道它的名字就能使用这段代码.函数还可以接收数据, ...

  4. 在webView中的返回键

    在写webView中我们按一下返回键,退到上一个我们浏览的网页,到第一个页面时,按两下退出程序,且按一下时提示你在按一下退出程序 只要加上这个方法即可 public void onBackPresse ...

  5. electron 查看版本信息

    console.log(process) console.log(process.versions.electron) process 里包含很多信息:

  6. 法兰克因恶意软件感染而关闭了整个IT网络

    导读 现在,越来越多的黑客组织将目标对准大型企业和政府机构.对于黑客而言,这比感染家庭用户具有更高的潜在利益.由于勒索软件的攻击,许多市政网络已被暂时关闭,而这次法兰克福遇到了恶意软件. 法兰克福是世 ...

  7. js 判断时间大小

    //判断结束时间一定要大于开始时间 function comparativeTime(){ var isok=true; //早餐配送时间 var breakfastScanTimeMin = $(& ...

  8. spring-cloud-服务调用流程

  9. 设计模式课程 设计模式精讲 2-3 UML类图讲解自上而下

    1 内容简析(易错加粗) 2 uml类图展示 1 内容简析(易错加粗) 1.1 设计模式中的对象关系 依赖关系 虚线箭头 组合关系 实心菱形 聚合关系 空心菱形(想象成盘子,盛东西,盘子的多,另一方少 ...

  10. 首款中文渗透测试专用Linux系统—MagicBox

    1.     MagicBox的介绍 首款中文渗透测试专用Linux系统——MagicBox即将问世,中文名称:“魔方系统”,开发代号:Genesis.第一版本发布时间计划为2012年12月5日 这是 ...