意外的snprintf行为

时间:2015-01-20 13:34:14

标签: c++

我在几个平台上注意到(我认为)snprintf与c ++的非常奇怪的行为。请考虑以下代码(导致观察到的行为的最小工作示例):

#include <stdio.h>

char test1[512];
char test2[512];
char test3[1024];
char test4[1024];

int main()
{
    snprintf(test1, sizeof(test1), "test1");
    snprintf(test2, sizeof(test2), "test2");
    snprintf(test3, sizeof(test3), "%s %s", test1, test2);
    return 0;
}

使用--tool = exp-sgcheck运行valgrind时,会报告以下错误(对于第3个snprintf语句):

==30302== Invalid read of size 1
==30302==    at 0x568E4EB: vfprintf (in /lib64/libc-2.19.so)
==30302==    by 0x56B7608: vsnprintf (in /lib64/libc-2.19.so)
==30302==    by 0x5695209: snprintf (in /lib64/libc-2.19.so)
==30302==    by 0x4006AD: main (1.cc:12)
==30302==  Address 0x601460 expected vs actual:
==30302==  Expected: global array "test1" of size 1,024 in object with soname "NONE"
==30302==  Actual:   global array "test2" of size 512 in object with soname "NONE"
==30302==  Actual:   is 0 after Expected

因此,将test1作为参数传递给第一个%s导致在test1数组结束后进行读取。

此行为导致Windows驱动程序出现多个页面错误(是的,我知道它的静态数据......)。幸运的是代码是可移植的,当移植到linux valgrind报告错误时。

但据我所知,snprintf应该在第6个字节用\ 0终止test1(它确实如此)。那么为什么在test1数组结束后读取第3个snprintf语句呢?将第3个snprintf语句更改为

snprintf(test3, sizeof(test3), "%.512s %s", test1, test2);

解决了两个平台上的问题。将代码编译为C代码(而不是C ++)不会导致错误。

更新:在linux(也可能是Windows)上,只有在编译时包含调试信息并禁用优化(-g -O0表示gcc)时才会出现错误。

1 个答案:

答案 0 :(得分:1)

由于全局对象(如示例中的数组)是0初始化的,因此最后一个snprintf应该永远不会超出字符串的结尾,而不管先前的sprintfs是否复制了终止0 char。唯一的解释是,之前的snprintfs复制的内容远远超过了提交的&#34; test1&#34;到目标test1,用非0覆盖所有0(对于随机存储器,不可能不会有0)。

这是非常不可能的 - 这个明显的错误早先会被发现。关于驱动程序中的错误,我怀疑内存是被完全不相关的&#34;进程覆盖的。 (一般来说,也许是另一个司机)。对于桌面应用程序,我没有解释为什么它会失败。尝试使用gcc 4.8.3在Codingground上运行示例运行得很好并在最后添加printf()时打印了预期的字符串。

顺便说一句,原始代码在启用优化的情况下运行正常并不奇怪:由于没有可观察到的影响,编译器可能只发出一个NOP。