实现一个有可变长参数列表函数的时候,会使用到stdarg.h(这里不讨论varargs.h)中提供的宏。



例如,我们要实现一个简易的my_printf:

1. 它只返回void, 不记录输出的字符数目

2. 它只接受"%d"按整数输出、"%c"按字符输出、"%%"输出'%'本身

如下:

 1 #include <stdarg.h>

 2 

 3 void my_printf(const char* fmt, ... )

 4 {

 5     va_list ap;

 6     va_start(ap,fmt); /* 用最后一个具有参数的类型的参数去初始化ap */

 7     for (;*fmt;++fmt)

 8     {

 9         /* 如果不是控制字符 */

         if (*fmt!='%')

         {

             putchar(*fmt); /* 直接输出 */

             continue;

         }

         /* 如果是控制字符,查看下一字符 */

         ++fmt;

         if ('\0'==*fmt) /* 如果是结束符 */

         {

 );  /* 这是一个错误 */

             break;

         }

         switch (*fmt)

         {

         case '%': /* 连续2个'%'输出1个'%' */

             putchar('%');

             break;

         case 'd': /* 按照int输出 */

             {

                 /* 下一个参数是int,取出 */

                 int i = va_arg(ap,int);

                 printf("%d",i);

             }

             break;

         case 'c': /* 按照字符输出 */

             {

                 /** 但是,下一个参数是char吗*/

                 /*  可以这样取出吗? */

                 char c = va_arg(ap,char);

                 printf("%c",c);

             }

             break;

         }

     }

     va_end(ap);  /* 释放ap——
必须! 见相关链接*/

 }

这与《C++程序设计语言》中的一道练习题很类似。

——需要支持"%c"控制符



在《C++程序设计语言-题解》中,给出了一个答案(中文p65页)。

但是, 如同上面的代码一样,它们都是错误的!






简单的说,我们用va_arg(ap,type)取出一个参数的时候,

type绝对不能为以下类型:

——char、signed
char、unsigned
char

——short、unsigned
short

——signed shortshort
int、signed
short int、unsigned
short
int

——float





一个简单的理由是:

——调用者绝对不会向my_printf传递以上类型的实际参数





在C语言中,调用一个不带原型声明的函数时:

调用者会对每个参数执行“默认实际参数提升(default argument promotions)”。



同时,对可变长参数列表超出最后一个类型声明的形式参数之后的每一个实际参数,也将执行上述提升工作。

提升工作如下:

——float类型的实际参数将提升到double

——char、short和相应的signed、unsigned类型的实际参数提升到int

——如果int不能存储原值,则提升到unsigned int



然后,调用者将提升后的参数传递给被调用者。

所以,my_printf是绝对无法接收到上述类型的实际参数的。




上面的代码的38与39行,应该改为:

int c = va_arg(ap,int);

printf("%c",c);

同理, 如果需要使用short和float, 也应该这样:

short s = (short)va_arg(ap,int);

float f = (float)va_arg(ap,double);

这也是printf族函数没有用于short和float的控制符的原因。




附录:



在《C语言程序设计》对可变长参数列表的相关章节中,并没有提到这个陷阱。

但是有提到默认实际参数提升的规则:



在没有函数原型的情况下,char与short类型都将被转换为int类型,float类型将被转换为double类型。

                ——《C语言程序设计》第2版  2.7 类型转换 p36







在其他一些书籍中,也有提到这个规则:





事情很清楚,如果一个参数没有声明,编译器就没有信息去对它执行标准的类型检查和转换。

在这种情况下,一个char或short将作为int传递,float将作为double传递。

这些做未必是程序员所期望的。

脚注:这些都是由C语言继承来的标准提升。

对于由省略号表示的参数,其实际参数在传递之前总执行这些提升(如果它们属于需要提升的类型),将提升后的值传递给有关的函数。——译者注

                ——《C++程序设计语言》第3版-特别版 7.6 p138



…… float类型的参数会自动转换为double类型,short或char类型的参数会自动转换为int类型 ……

                ——《C陷阱与缺陷》 4.4 形参、实参与返回值 p73





这里有一个陷阱需要避免:

va_arg宏的第2个参数不能被指定为charshort或者float类型。

因为char和short类型的参数会被转换为int类型,而float类型的参数会被转换为double类型 ……

例如,这样写肯定是不对的:

c = va_arg(ap,char);

因为我们无法传递一个char类型参数,如果传递了,它将会被自动转化为int类型。上面的式子应该写成:

c = va_arg(ap,int);

                ——《C陷阱与缺陷》p164




2009/05/07 修改:

printf函数族有用于short的控制符“h”。

见:http://www.cplusplus.com/reference/clibrary/cstdio/printf/




相关链接:



——《可变长参数列表误区与陷阱——va_end是必须的吗?》

http://www.cppblog.com/ownwaterloo/archive/2009/04/21/is_va_end_necessary.html










本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。

转载请注明 :

文章作者 - OwnWaterloo

发表时间 - 2009年04月21日

原文链接 - http://www.cppblog.com/ownwaterloo/archive/2009/04/21/unacceptable_type_in_va_arg.html

可变长参数列表误区与陷阱——va_arg不可接受的类型的更多相关文章

  1. 在Python中使用可变长参数列表

    函数定义 在函数定义中使用*args和**kwargs传递可变长参数. *args用作传递非命名键值可变长参数列表(位置参数); **kwargs用作传递键值可变长参数列表 函数调用 在调用函数时,使 ...

  2. Java 可变长参数列表

    Java中定义了变长参数,允许在调用方法时传入不定长度的参数. 定义及调用 在定义方法时,在最后一个形参后加上三点 …,就表示该形参可以接受多个参数值,多个参数值被当成数组传入.上述定义有几个要点需要 ...

  3. go实例—函数或方法的可变长参数

    支持可变长参数列表的函数可以支持任意个传入参数,比如fmt.Println函数就是一个支持可变长参数列表的函数. 需要注意的是,可变长参数应该是函数定义的最右边的参数,即最后一个参数 package ...

  4. python基础语法5 函数定义,可变长参数

    函数 1.什么是函数 函数就是一种工具. 可以重复调用 2.为什么要用函数 1.防止代码冗(rong)余 2.代码的可读性差 3.怎么用函数 1.定义函数-->制造工具 2.调用函数--> ...

  5. Java中可变长参数的使用及注意事项

    在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用,例如print("hello");print( ...

  6. Java中可变长参数的方法

    原文转自:http://www.cnblogs.com/lanxuezaipiao/p/3190673.html 在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定 ...

  7. [转]深度探索C语言函数可变长参数

    转自:http://www.cnblogs.com/chinazhangjie/archive/2012/08/18/2645475.html 一.基础部分 1.1 什么是可变长参数 可变长参数:顾名 ...

  8. 关于C中可变长参数

    前言 可变长参数指函数的参数个数在调用时才能确定的函数参数.基本上各种语言都支持可变长参数,在特定情形下,可变长参数使用起来非常方便.c语言中函数可变长参数使用“...”来表示,同时可变长参数只能位于 ...

  9. C++ 系列:函数可变长参数

    一.基础部分 1.1 什么是可变长参数 可变长参数:顾名思义,就是函数的参数长度(数量)是可变的.比如 C 语言的 printf 系列的(格式化输入输出等)函数,都是参数可变的.下面是 printf ...

随机推荐

  1. HTML5中的localStorage用法

    存储数据的方法就是直接给window.localStorage添加一个属性,例如:window.localStorage.name 或者 window.localStorage["name& ...

  2. 最全的PHP常用函数大全

    PHP的一些常用函数 quotemeta() 函数在字符串中某些预定义的字符前添加反斜杠. quoted_printable_decode() 函数对经过 quoted-printable 编码后的字 ...

  3. html_表单

    一.表单框架简介 表单是提供让读者在网页上输入,勾选和选取数据,以便提交给服务器数据库的工具.(用于将信息提交给服务器) <form>...</form>标记 作用:用于创建一 ...

  4. git subtree有效管理公共第三方lib

    如果你的项目中有很多第三方的lib,你希望使用它,并且也希望可能对该lib做修改并且贡献到原始的项目中去,或者你的项目希望模块化,分为几个repo单独维护,那么git subtree就是一个选择.gi ...

  5. 【第四篇】说说layer的iframe弹窗给里面的标签赋值的问题

    说到这一篇,真的是颠覆了我的思维. 官方文档,没有介绍这一部分的操作,大致上提了一下. 我的思路是把页面的数据传过去,在iframe弹窗的页面拿到接收到的数据,然后赋值,但是这样就会有个问题, 怎么传 ...

  6. Codeforces 443 B Kolya and Tandem Repeat【暴力】

    题意:给出一个字符串,给出k,可以向该字符串尾部添加k个字符串,求最长的连续重复两次的子串 没有想出来= =不知道最后添加的那k个字符应该怎么处理 后来看了题解,可以先把这k个字符填成'*',再暴力枚 ...

  7. windows下github pages + hexo next 搭建个人博客

    一.github pages 搭建个人博客一般需要购买域名和空间,github pages为我们提供了这两样东西,而且是免费的,相关介绍和使用方法参考这里 github pages. 二.Hexo 一 ...

  8. 这篇博客的内容基本没见过,mark 一下以后可以学习

    初识机器学习算法有哪些? 机器学习无疑是现在数据分析领域的一个重要内容,凡事从事IT工作领域的人都在平时的工作中或多或少的会用到机器学习的算法. 机器学习有很多算法,不过大的方面可分为两类:一个是学习 ...

  9. 基于CentOS与VmwareStation10搭建Oracle11G RAC 64集群环境:2.搭建环境-2.6. 安装Oracle所依赖的必要包

    2.6. 安装Oracle所依赖的必要包 2.6.1. 检查Oracle所依赖的必要rpm包 [root@localhost /]#rpm -q binutils compat-libstdc elf ...

  10. Excel 绘制图表,如何显示横轴的数据范围

    右键点击X坐标轴,然后选中“设置图表区域格式”,然后在“坐标轴选项”--“区域”处设置X轴范围. 备注,这种方式仅使用与第一列时日期时间类型的数据. 应用场景 当,选择有两列数据,第一列为横轴数据,第 ...