Valgrind 报告标准库内未初始化的值 (vfprintf.c)

Ste*_*mer 4 c++ valgrind

我有一个函数,它可以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;
}
Run Code Online (Sandbox Code Playgroud)

该应用程序按预期工作,但通过 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)
Run Code Online (Sandbox Code Playgroud)

将构造函数更改tmp_bufmemset整个缓冲区不会更改 valgrind 的输出

tmp_buf() { memset(b, 0, sizeof(b)); }
Run Code Online (Sandbox Code Playgroud)

Int*_*ity 5

虽然我对 Valgrind 不是很熟悉,但我可以在您的代码中看到一个明显的问题,并且可以提供我对 Valgrind 为何以这种方式抱怨的最佳猜测。

一、问题:

该函数返回一个指向argument类型的va_stack_str成员的指针。由于此函数无法控制此参数引用的对象的生命周期,因此它返回一个指针,该指针的有效性只能在调用它的完整表达式结束之前得到保证。如果参数由临时变量初始化(这正是 所使用的方式),则完整表达式的结尾正是返回指针有效的持续时间。bbconst tmp_buf&bstack_str

该函数stack_str继续将返回的指针存储va_stack_str在局部变量中str,然后返回它。此时,被va_stack_str调用的完整表达式已经结束,因此指针悬空 - 它指向一个在堆栈上分配但已被释放的缓冲区。

该代码之所以有效,可能是因为缓冲区确实存在的堆栈部分在读取时没有被覆盖,因此仍然包含曾经是缓冲区的内容。

为什么我认为 valgrind 会发出“未初始化值”警告:

vfprintf当然会为局部变量分配一些堆栈空间,其中一些可能是在与我们要求其打印的缓冲区相同的堆栈内存中分配的。然后,当vfprintf使用该缓冲区(我们传递给它的缓冲区)时,Valgrind 不会将该内存视为我们的原始缓冲区(已被释放),而是将其视为vfprintf分配的局部变量的地址。

vfprintf我的猜测是,这些局部变量之一在扫描我们传递给它的缓冲区以查找终止 NULL 字符时未初始化。在这种情况下,它会检查指向其自己的未初始化局部变量的内存,这通常不会发生vfprintf,因为稍后会在打算使用它之前对其进行初始化。vfprintf期望您将传递一个指向您已分配的缓冲区的指针,而不是最终指向它自己的局部变量的指针!