我有一个函数可以将vsnsprintf
放入堆栈中创建的对象的临时缓冲区中。
在对象的构造函数中,我将缓冲区的第一个字符初始化为null。
Valgrind抱怨vfprintf.c
下面的完整工作示例,然后是valgrind输出
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
struct tmp_buf
{
tmp_buf() { *b = 0; }
mutable char b[1024];
};
char const* va_stack_str(const char* format, va_list ap, const tmp_buf& b = tmp_buf())
{
vsnprintf(b.b, sizeof(b.b), format, ap);
return b.b;
}
char const* stack_str(const char* format, ...)
{
va_list ap;
va_start(ap, format);
const char* str = va_stack_str(format, ap);
va_end(ap);
return str;
}
int main()
{
printf("%s", stack_str("hello %s", "world"));
return 0;
}
该应用按预期工作,但通过valgrind运行会抱怨未初始化的值
我的valgrind命令行是valgrind --leak-check=full --track-origins=yes --quiet
Valgrind输出:
==30513== Conditional jump or move depends on uninitialised value(s)
==30513== at 0x4E828F3: vfprintf (vfprintf.c:1661)
==30513== by 0x4E8B388: printf (printf.c:33)
==30513== by 0x400A73: main (main.cpp:28)
==30513== Uninitialised value was created by a stack allocation
==30513== at 0x4E80BF6: vfprintf (vfprintf.c:235)
==30513==
==30513== Syscall param write(buf) points to uninitialised byte(s)
==30513== at 0x4F233B0: __write_nocancel (syscall-template.S:81)
==30513== by 0x4EB0A82: _IO_file_write@@GLIBC_2.2.5 (fileops.c:1261)
==30513== by 0x4EB1F5B: _IO_do_write@@GLIBC_2.2.5 (fileops.c:538)
==30513== by 0x4EB3ADD: _IO_flush_all_lockp (genops.c:848)
==30513== by 0x4EB3C39: _IO_cleanup (genops.c:1013)
==30513== by 0x4E730FA: __run_exit_handlers (exit.c:95)
==30513== by 0x4E73194: exit (exit.c:104)
==30513== by 0x4E58ECB: (below main) (libc-start.c:321)
==30513== Address 0x4025000 is not stack'd, malloc'd or (recently) free'd
==30513== Uninitialised value was created by a stack allocation
==30513== at 0x4E80BF6: vfprintf (vfprintf.c:235)
将tmp_buf
构造函数更改为memset
整个缓冲区不会改变valgrind的输出
tmp_buf() { memset(b, 0, sizeof(b)); }
答案 0 :(得分:4)
虽然我对Valgrind并不是非常熟悉,但我可以在你的代码中看到一个明显的问题,并且可以提供我最好的猜测,为什么Valgrind以它的方式抱怨。
首先,问题是:
函数va_stack_str
返回指向b
类型的参数b
的成员const tmp_buf&
的指针。因为此函数无法控制此参数引用的对象的生命周期,所以它返回一个指针,该指针的有效性只能保证,直到调用它的完整表达式结束。如果参数b
由临时语句初始化(这正是stack_str
使用它的方式),那么完整表达式的结尾正是返回指针有效的持续时间。 / p>
函数stack_str
继续将va_stack_str
返回的指针存储在局部变量str
中,然后返回它。到这时,调用va_stack_str
的完整表达式已经结束,因此指针悬空 - 它指向一个在堆栈上分配但后来被释放的缓冲区。
代码的工作原理可能是因为缓冲区确实存在的堆栈部分在读取时没有被覆盖,因此仍然包含以前作为缓冲区的内容。
为什么我认为valgrind问题&未初始化的价值&#39;警告:
vfprintf
肯定会为局部变量分配一些堆栈空间,其中一些可能分配在同一堆栈内存中,而这些内存曾经是我们要求它打印的缓冲区。当vfprintf
然后使用此缓冲区(我们传递它的那个)时,Valgrind将此内存视为我们的原始缓冲区(已被解除分配),但作为vfprintf
分配的局部变量的地址
我的猜测是,其中一个局部变量在vfprintf
扫描我们传递它的缓冲区时寻找终止NULL字符时未初始化。在这种情况下,它检查指向其自身未初始化的局部变量的内存,这通常不会发生,因为vfprintf
稍后会初始化它,但在它打算使用它之前。 vfprintf
期望您将指针传递给已分配的缓冲区,而不是最终指向它自己的局部变量的缓冲区!