问题

  当我们刚开始学习C语言的时候,就接触到printf()函数,可是当时“道行”不深或许不够细心留意,又或者我们理所当然地认为库函数规定这样就是这样,没有发现这个函数与普通的函数存在区别,普通函数的参数在函数定义的时候就确定,而printf()函数的参数列表在调用时可变。还有一个原因导致我们没有去关注这个函数的实现,就是在编程的过程中很少用到参数列表可变的函数。的确是这样的,但是如果可以理解并内化,这将在编程过程中对某些功能实现带来很大的帮助。比如,在嵌入式设备开发中,可以利用设备的接口(UART、USB)编写出一套类似printf()函数功能的调试工具。

printf()函数的实现

static char sprint_buf[1024];

int printf(char *fmt, ...)

{

  va_list args;

  int n;

  va_start(args, fmt);

  n = vsprintf(sprint_buf, fmt, args);

  va_end(args);

  write(stdout, sprint_buf, n);

  return n;

}

  咋一看,printf()函数的试下也不是太复杂,有几个陌生又关键的词语va_list、va_start、va_end、vsprintf。其实还有一个va_arg(stdarg.h),是参数列表可变的关键,而真正的打印实现是vsprintf()函数,在这里不讨论vsprintf()函数,只讨论三个宏定义。

先看看stdarg.h文件下这三个宏的实现:

第一种:

typedef char  *va_list;

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

#define va_arg(ap,t)      (((t *)ap)++[0])

#define va_end(ap)

第二种:

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,type)     (*(type *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

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

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

在32位系统中,~(sizeof(int)–1) )展开为~(4-1)=~(00000011b)=11111100b,这样任何数& ~(sizeof(int)–1) )后最后两位肯定为0,也就是4的整数倍。

每个平台下的stdarg.h头文件的定义都不相同,但是意思都一样:

(1) va_list:    定义一个va_list型的变量ap,也就是char *;

(2) va_start:获取到可变参数表的首地址,并将该地址赋给指针ap;

(3) va_arg:   获取当前ap所指向的可变参数,并将指针ap指向下一个可变参数。注意,该宏的第二个参数为类型;

(4) va_end:   结束可变参数的获取。

应用

当理解了printf()函数的实现,便可以着手去实现嵌入式设备中的调试工具,其原理一样:

int bsp_debug_printf(const char *fmt, ...)

{

  int ret = -1;

  va_list ap;

  char buf[256];

   va_start(ap, fmt);

   (void)vsnprintf((char *)buf, sizeof(buf), fmt, ap);

  va_end(ap);

  bsp_puts(buf);

  ret = 0;

  return  ret;

}

以上只是简单的实现这个功能,并没有考虑硬件保护,返回打印字符数等。

再看一例:

找出最大值

int MaxTest(int c, ...)

{

  int max = c;

  va_list ap;

   va_start(ap,c);

  c = va_arg(ap,int);

  while(0 != c)

  {

    if(max < c) max = c;

  c = va_arg(ap,int);

  }

  va_end(ap);

  return max;

}

  这个例子主要想利用va_list、va_start、va_arg、va_end这四个关键指令来实现可变参数列表的函数,它需要一个参数列表末尾标识(0)来告诉系统这参数列表的最后一个参数。

还可以有另外一种方法来实现以上功能:

int MaxTest1(int n, ...)

{

int max = n;

int *p = &n + 1;

while(0 != *p)

{

if (max < *p) max = *p;

p++;

}

return max;

}

  这个例子利用了函数参数的存储规则来实现对参数的获取。可变参数函数的实现与函数调用的栈结构有关,正常情况下C/C++的函数参数入栈规则从右到左的,即函数中的最右边的参数最先入栈。

  这种方法看似效果相同,但是稍有不慎就会带来灾难。若printf()函数由这种方法实现,而没有边界的检查,当堆栈越界访问时极可能导致程序崩溃。

可变参数列表与printf()函数的实现的更多相关文章

  1. C语言中可变参数的原理——printf()函数

    函数原型: int printf(const char *format[,argument]...) 返 回 值: 成功则返回实际输出的字符数,失败返回-1. 函数说明: 使用过C语言的人所再熟悉不过 ...

  2. C语言函数可变参数列表

    C语言允许使用可变参数列表,我们常用的printf函数即为可变参数函数,C标准库提供了stdarg.h为我们提供了这方面支持:该头文件提供了一些类型和宏来支持可变参数列表,包括类型va_list,宏v ...

  3. C利用可变参数列表统计一组数的平均值,利用函数形式参数栈原理实现指针运算

    //描述:利用可变参数列表统计一组数的平均值 #include <stdarg.h> #include <stdio.h> float average(int num, ... ...

  4. PHP函数可变参数列表的具体实现方法介绍

    PHP函数可变参数列表可以通过_get_args().func_num_args().func_get_arg()这三个函数来实现.我们下面就对此做了详细的介绍. AD:2014WOT全球软件技术峰会 ...

  5. php实现函数可变参数列表

    使用func_get_args().func_num_args().func_get_arg() 可以构造一个可变参数列表的函数. 首先大致介绍以上三个函数. (1)array func_get_ar ...

  6. 可变参数列表---以dbg()为例

    在UART驱动的drivers/serial/samsung.h中遇到如下定义: #ifdef CONFIG_SERIAL_SAMSUNG_DEBUG extern void printascii(c ...

  7. tips:可变参数列表

    tips:可变参数列表! 先来看看以往我们要传递许多参数时是怎么做的: java: public static void main(String []args){} c: int main(int a ...

  8. C++可变参数列表处理宏va_list、va_start、va_end的使用

      VA_LIST是在C语言中解决变参问题的一组宏他有这么几个成员: 1)va_list型变量: #ifdef     _M_ALPHA typedef    struct{ char* a0; /* ...

  9. 【转】C++可变参数列表处理宏va_list、va_start、va_end的使用

    VA_LIST是在C语言中解决变参问题的一组宏他有这么几个成员: 1)va_list型变量: #ifdef     _M_ALPHA typedef    struct{ char* a0; /*po ...

随机推荐

  1. JavaScript对象创建,继承

    创建对象 在JS中创建对象有很多方式,第一种: var obj = new Object(); 第二种方式: var obj1 = {};//对象直面量 第三种方式:工厂模式 function Per ...

  2. Gridview中运用CommandField 删除控件时注意点

    我在gridview1 <编辑列>里面添加了一个<CommandField 删除>的控件,之后在gridview1的事件<RowDeleting>事件下 写了一段删 ...

  3. Qt拖拽界面 (*.ui) 缩放问题及解决办法

    问题 使用Qt Designer 设计的界面,在缩放的时候不能随着主窗口一起缩放. 解决办法 之前遇到这个问题的时候,都是直接重写resizeEvent接口来实现的,在自动生成的Ui_Widget或U ...

  4. 在WPF中使用WinForm控件方法

    1.      首先添加对如下两个dll文件的引用:WindowsFormsIntegration.dll,System.Windows.Forms.dll. 2.      在要使用WinForm控 ...

  5. Git 修改源地址

    git remote set-url origin http://git.xxx.com/xxx/repo.git

  6. MQTT(三)-----连接与心跳

    MQTT协议笔记之连接和心跳 - 推酷 http://www.tuicool.com/articles/AFvmee 互联网推送服务原理:长连接+心跳机制(MQTT协议) - clh604的专栏 - ...

  7. win8 app GridView点击子项布局变更

    要触发点击必须设置IsItemClickEnabled="True" 要变更布局代码如下: private void gridView_ItemClick_1(object sen ...

  8. git 删除分支

    1,删除本地分支 git branch -D 本地分支名 2,删除远程分支 git push origin --delete 远程分支名 注意:如果只删除了本地分支而没有删除远程分支,再新建一个相同名 ...

  9. USB_HID读写上位机VC++

    在工程属性-->链接器-->添加以下库 open 打开,close 关闭,打开后将获得reader 与writer 的handle,分别进行读写即可 #pragma once #ifdef ...

  10. python 旋转数组

    #!/usr/bin/env python3 #-*-encoding:utf-8-*- l = [] u = [] q = 5 xx=[[col for col in range(q)] for r ...