格式化输出的坑

在工作中,c/c++开发中,日志输出是必不可少的,而日志输出,基本都是使用格式化输出。正常的情况下,我们使用下面的语句来输出日志

printf("%s/%d: some thing error=[%s]", __FILE__, __LINE__, geterror());

但是,如果手抖,写错了呢

printf("%s/%d: some thing error=[%s]", __FILE__, __LINE__,error);

这就悲剧了,程序会core dump。如果是如下的用法呢?

printf("%s/%d", __FILE__, __LINE__, geterror());
printf("%s/%d: some thing error=[%d]", __FILE__, __LINE__);
printf("%s/%d: some thing error=[%d]", __FILE__, __LINE__, geterror());

事实证明,上述三个表达式都不会造成程序core dump,Why?,为了寻找原因,咱们还是得从C的可变参数函数的实现原理中找到病根。先来看看printf的源码

int printf(const char *fmt, ...)
{
    char printf_buf[1024];
    va_list args;
    int printed;

    va_start(args, fmt);
    printed = vsprintf(printf_buf, fmt, args);
    va_end(args);

    puts(printf_buf);

    return printed;
}

C为了实现可变参数函数,提供了va_list,va_start, va_arg,va_end等宏或函数来支持可变参数函数。这里不一一粘贴它们的定义,在msdn上都有。

va_list是指向可变参数列表数据结构,其定义如下:

#ifdef _M_ALPHA
typedef struct {
char *a0; /* pointer to first homed integer argument */
int offset; /* byte offset of next parameter */
} va_list;
#else
typedef char * va_list;
#endif

va_start是用于初始化va_list变量,使其指向可变参数栈的第一个参数。

va_arg用于从可变参数栈中根据参数类型获取参数的地址,同时计算偏移地址。

va_end用于将va_list变量作废。


这里的关键点就是va_arg函数,函数声明为

type va_arg( va_list arg_ptr, type ); 
type指定了要读取的参数类型,va_arg的实现是

arg_ptr += _SIZEOF(type);
return *( type *)(arg_ptr - _SIZEOF(type));

分析了printf实现原理,再来看看问题,printf其实就是内存操作,core dump的原因肯定就是段错误,段错误无非就是内存非法,在c/c++基本类型操作中,只有指针才会有内存非法的情况发生。

先来看案例1,

printf("%s/%d: some thing error=[%s]", __FILE__, __LINE__, error);

vprintf解析格式串的时候,发现需要从va_list找出第三个类型为char *的参数值

char *s = va_arg(args, char *);

如果error值为0,则s=0; 这样输出s就会段错误,这就是core dump的原因。


再来看案例2:

printf("%s/%d", __FILE__, __LINE__, geterror());

这个情况是参数值多一个,va_arg少取是没有问题的,所以,这里没有问题。


再来看案例3:

printf("%s/%d: some thing error=[%d]", __FILE__, __LINE__);

这个案例是参数值少了一个,溢出参数栈了,按理说,应该可能会段错误呀,但是我实验了很多次都没有,估计这里做了保护,或者地址还属于进程空间。


再来看案例4:

printf("%s/%d: some thing error=[%d]", __FILE__, __LINE__, geterror());

这个案例是把char *s的地址值赋给一个整形,输出的时候,不会有问题,只是将这个地址值打印一下而已。


这个情况很是应该引起开发人员的注意,这在大多数c++编译器下是不会检查输出格式和实际的参数类型的对应关系的,这将对程序带来不可预估的危险,特别是在测试没有走到的分支里面。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值