我在几个平台上注意到(我认为)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)时才会出现错误。
答案 0 :(得分:1)
由于全局对象(如示例中的数组)是0初始化的,因此最后一个snprintf应该永远不会超出字符串的结尾,而不管先前的sprintfs是否复制了终止0 char。唯一的解释是,之前的snprintfs复制的内容远远超过了提交的&#34; test1&#34;到目标test1
,用非0覆盖所有0(对于随机存储器,不可能不会有0)。
这是非常不可能的 - 这个明显的错误早先会被发现。关于驱动程序中的错误,我怀疑内存是被完全不相关的&#34;进程覆盖的。 (一般来说,也许是另一个司机)。对于桌面应用程序,我没有解释为什么它会失败。尝试使用gcc 4.8.3在Codingground上运行示例运行得很好并在最后添加printf()时打印了预期的字符串。
顺便说一句,原始代码在启用优化的情况下运行正常并不奇怪:由于没有可观察到的影响,编译器可能只发出一个NOP。