C语言中可变参数的原理——printf()函数
函数原型: 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()函数的更多相关文章
- C语言中可变参数的函数(三个点,“...”)
C语言中可变参数的函数(三个点,“...”) 本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end ...
- C语言中可变参数函数实现原理
C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fu ...
- C语言中可变参数的用法
原文地址: http://blog.csdn.net/wooin/archive/2006/04/29/697106.aspx 我们在C语言编程中会遇到一些参数个数可变的函数,例如printf() ...
- C语言中可变参数的使用
在C语言程序编写中我们使用最多的函数一定包括printf以及很多类似的变形体.这个函数包含在C库函数中,定义为 int printf( const char* format, ...); 除了一个格式 ...
- 可变参数列表与printf()函数的实现
问题 当我们刚开始学习C语言的时候,就接触到printf()函数,可是当时"道行"不深或许不够细心留意,又或者我们理所当然地认为库函数规定这样就是这样,没有发现这个函数与普通的函数 ...
- c 中可变参数的实现
我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为: 例一: int printf( const char* format, ... ...
- C/C++函数调用时传参过程与可变参数实现原理
C/C++函数调用时传参过程与可变参数实现原理 C语言的经典swap问题 在学习C语言的时候,我们大都遇到过一些经典例题,这些经典例题背后所代表的是往往是C/C++背后的一些运行原理,比如下面这个示例 ...
- [11 Go语言基础-可变参数函数]
[11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...
- C语言的可变参数在Linux(Ubuntu)与Windows下注意点
基本上C语言的可变参数原理在不同平台和不同编译器下基本类似(通过函数入栈,从右向左,从高位到低位地址),不过部分实现会有所不同:在使用中需要注意的是: va_list 为char 类型指针,部分调用如 ...
随机推荐
- request和response对象如何解决中文乱码问题?
出现中文乱码的问题,一般的原因编码和和解码不一致造成的. /* 乱码:编码和解码不一致导致的 GET:你好 POST:?????? tomcat版本:8.5及以上版本 GET请求方式,request对 ...
- 4.使用Redis+Flask维护动态代理池
1.为什么使用代理池 许多⽹网站有专⻔门的反爬⾍虫措施,可能遇到封IP等问题. 互联⽹网上公开了了⼤大量量免费代理理,利利⽤用好资源. 通过定时的检测维护同样可以得到多个可⽤用代理理. 2.代理池的要 ...
- 由Nginx反向代理引出的JCaptcha验证码验证失败的问题
搜索关键字: 1)Windows本地开发正常,部署到Linux远程服务器上JCaptcha验证失败 2)Linux远程服务器上JCpatcha验证失败 3)Nginx反向代理后JCaptcha验证失败 ...
- 本周总结(19年暑假)—— Part2
日期:2019.7.21 博客期:108 星期日 这几天正在认真学习大数据,我是在B站上看尚老师的视频搞得.我已经配好了Hadoop的基本环境,现在学习的是HDFS的相关内容
- 使用c#调用API入门
使用C#调用windows API入门 一:入门,直接从 C# 调用 DLL 导出 其实我们的议题应该叫做C#如何直接调用非托管代码,通常有2种方法: 1. 直接调用从 DLL 导出的函数. 2 ...
- eclipse 编辑窗口不见了(打开左边的java、xml文件,中间不会显示代码)
参考:https://blog.csdn.net/u012062810/article/details/46729779
- pygame学习的第一天
pygame最小开发框架: import pygame, sys pygame.init() screen = pygame.display.set_mode((600, 480)) pygame.d ...
- markdown基本语法教程
标题 一级标题 二级标题 三级标题 以此类推,总共六级标题,建议在警号后面加一个空格,这是最标准的markdown语法 列表 在markdown下: 列表的显示只需要在文字前加上-.+或*即可变为无序 ...
- JS 自动返回每个月的天数
);//M代表月份,可以自己手动设置或者选择 date.setDate() var num = date.getDate(); console.log(num) //输出每个月的天数 var time ...
- 解题报告:CF622F
懒得码字了: 题目链接:CF622F 很简单的数论题,紫题显然是过了些,(不要说... 对于这个式子,是一个\(k+1\)次的多项式,插\(k+2\)次值就好了,烦人的是处理逆元,我的费马小定理显然是 ...