博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
可变长参数列表误区与陷阱——va_arg不可接受的类型
阅读量:6967 次
发布时间:2019-06-27

本文共 3191 字,大约阅读时间需要 10 分钟。

  hot3.png

实现一个有可变长参数列表函数的时候,会使用到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 
        
/*
 如果不是控制字符 
*/
10 
        
if
 (
*
fmt
!=
'
%
'
)
11 
        {
12 
            putchar(
*
fmt); 
/*
 直接输出 
*/
13 
            
continue
;
14 
        }
15 
        
/*
 如果是控制字符,查看下一字符 
*/
16 
        
++
fmt;
17 
        
if
 (
'
\0
'
==*
fmt) 
/*
 如果是结束符 
*/
18 
        {
19 
            assert(
0
);  
/*
 这是一个错误 
*/
20 
            
break
;
21 
        }
22 
        
switch
 (
*
fmt)
23 
        {
24 
        
case
 
'
%
'
/*
 连续2个'%'输出1个'%' 
*/
25 
            putchar(
'
%
'
);
26 
            
break
;
27 
        
case
 
'
d
'
/*
 按照int输出 
*/
28 
            {
29 
                
/*
 下一个参数是int,取出 
*/
30 
                
int
 i 
=
 va_arg(ap,
int
);
31 
                printf(
"
%d
"
,i);
32 
            }
33 
            
break
;
34 
        
case
 
'
c
'
/*
 按照字符输出 
*/
35 
            {
36 
                
/*
但是,下一个参数是char吗
*/
37 
                
/*
  可以这样取出吗? 
*/
38 
                
char
 c 
=
 va_arg(ap,
char
);
39 
                printf(
"
%c
"
,c);
40 
            }
41 
            
break
;
43 
        }
44 
    }
45 
    va_end(ap);  
/*
 释放ap—— 必须! 见相关链接
*/
46 
}
这与《C++程序设计语言》中的一道练习题很类似。
——需要支持"%c"控制符
在《C++程序设计语言-题解》中,给出了一个答案(中文p65页)。
但是,
如同上面的代码一样,它们都是
错误的!
简单的说,我们用
va_arg(
ap,
type)取出一个参数的时候,
type对不能为以下类型:
——
char
signed
char
unsigned
char
——
short
unsigned
short
——
signed
short
short
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个参数不能被指定为
char
short或者
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”。
见:
相关链接:
——《可变长参数列表误区与陷阱——va_end是必须的吗?》
本 作品采用 进行许可。

转载请注明 :

文章作者 - OwnWaterloo
发表时间 - 2009年04月21日

原文链接 -

 

Changelog:

1. C标准对默认实际参数提升规则有明确定。

也就是说, 带有可变长参数列表的函数, 绝对不会接受到char类型的实际参数。
2. C标准对va_arg是否自动对齐没有任何说明
你说的va_arg(va_list,type)是自动对齐, 只是在你的编译器上。
并不是所有编译器都能自动帮你完成这个工作。
在所有C实现上, 能保证第1点, 但不能保证第2点。
依赖于第2点, 代码就依赖于特定编译器。
你说va_arg(ap,type)是自动对齐, 证明你有研究过。
喜欢作这些研究的, 都是聪明的家伙。
但聪明的家伙总喜欢不按规矩办事
在gcc (GCC) 3.4.2 (mingw-special)中, type使用char, 会得到严重的警告:
`char' is promoted to `int' when passed through `...'
(so you should pass `int' not `char' to `va_arg')
note: if this code is reached, the program will abort
它会直接挂掉你的程序,来约束你必须按规矩办事。

转载于:https://my.oschina.net/qihh/blog/56673

你可能感兴趣的文章
第一章 Java初步
查看>>
洛谷P2462 [SDOI2007]游戏(哈希+最长路)
查看>>
HDU 1428 漫步校园
查看>>
dispatch_source_create创建定时器和UIWindow创建类似处
查看>>
Java语言基础(九)
查看>>
python基础一 day5 集合
查看>>
Tomcat指定特定JDK版本
查看>>
mybatis 自动生成代码
查看>>
unittest===unittest 的几种执行方式
查看>>
Xcode使用小结1
查看>>
bzoj2763
查看>>
《转》struts2动态方法配置 Action,使一个Action可处理多请求
查看>>
[Shoi2007]Vote 善意的投票
查看>>
eval()函数用法详解
查看>>
Angular 基础入门
查看>>
Xcode的一个简单的UITests
查看>>
前端--CSS之使用display:inline-block来布局(转)
查看>>
需求工程——软件建模与分析阅读笔记05
查看>>
备战找工作
查看>>
方维O2O调用UCENTER头像的方法
查看>>