Valgrind ClientCheck 未初始化的字符串

Valgrind ClientCheck uninitialized string

我是 Valgrind 的新手,我在查找其中一些警告的来源时遇到了一些麻烦。我一直在使用 memcheck.h 中的 VALGRIND_CHECK_VALUE_IS_DEFINED 宏来尝试找到错误的确切来源,这让我怀疑我是否正确使用了该工具。

这是我 运行 使用 Valgrind 的示例程序:

#include <valgrind/memcheck.h>
    
int main() {
    std::string str("test");
    VALGRIND_CHECK_VALUE_IS_DEFINED(str);
    return 0;
}

这会导致以下警告:

==9612== Uninitialised byte(s) found during client check request
==9612==    at 0x11EB45: main (main.cpp:5)
==9612==  Address 0x1ffefffd35 is on thread 1's stack
==9612==  in frame #0, created by main (main.cpp:3)
==9612==  Uninitialised value was created by a stack allocation
==9612==    at 0x11EA8E: main (main.cpp:3)

一个非常相似的程序:

#include <valgrind/memcheck.h>
    
int main() {
    int x = 0;
    VALGRIND_CHECK_VALUE_IS_DEFINED(x);
    return 0;
}

没有这样的问题。我使用标志 --track-origins=yes 进行线跟踪,并在 Ubuntu 20.04 LTS 上使用 g++ 9.4.0 编译为 c++17(尽管我收到了与 clang++ 14.0.0 相同的警告)。

这是我的错误,还是 Valgrind 的问题?

这样做的原因相当简单。 std::string 大致由指针、长度和 allocated_capacity 成员组成。在 64 位 Unix-like 平台上,它们都是 8 个字节。 “SSO”【小字符串优化】将通过联合回收(并扩展)分配的容量成员来存储足够短的字符串。

请注意,clang libc++ 做的事情有点不同(我认为那里的联合会回收指针和容量以允许更大的“小”字符串)。

这意味着sizeof(std::string)是32。那么有两种可能。

  1. 如果字符串不“短”,则指针、长度和allocated_capacity都被使用,最后8个字节将被取消初始化。
  2. 如果字符串“短”,则 local_buf(libstdc++ 为 16 字节)中的任何备用容量都将未初始化。

考虑以下代码。我使用了 VALGRIND_GET_VBITS ,它会得到一个字节,对于已定义的内存,位设置为零,对于未定义的内存,位设置为 1。

#include "valgrind/memcheck.h"
#include <string>
#include <iostream>

int main() {
    const auto size = sizeof(std::string);
    unsigned char bits[size];
    // SSO, local buf not filled
    std::string str("test");
    VALGRIND_CHECK_VALUE_IS_DEFINED(str);
    VALGRIND_GET_VBITS(&str, bits, size);
    for (int i = 0; i < size; ++i)
       std::cout << "byte " << std::dec << i << " bits " << std::hex << static_cast<int>(bits[i]) << '\n';

    // SSO, local buf illed
    std::string str2("123456789012345");
    VALGRIND_CHECK_VALUE_IS_DEFINED(str2);
    VALGRIND_GET_VBITS(&str2, bits, size);
    for (int i = 0; i < size; ++i)
       std::cout << "byte " << std::dec << i << " bits " << std::hex << static_cast<int>(bits[i]) << '\n';

    // not SSO
    std::string str3("12345678901234567890");
    VALGRIND_CHECK_VALUE_IS_DEFINED(str3);
    VALGRIND_GET_VBITS(&str3, bits, size);
    for (int i = 0; i < size; ++i)
       std::cout << "byte " << std::dec << i << " bits " << std::hex << static_cast<int>(bits[i]) << '\n';
    return 0;
}

对于 str(使用了 4 个字节加上一个用于 nul,所以 SSO 缓冲区的 5 个字节)。 “未初始化字节”消息后的输出是

字节 0 位 0
[相同的 1 到 13]
字节 14 位 0
字节 15 位 ff
[相同的 16 到 30]
字节 31 位 ff

所以你可以看到字节 15 到 31 是未定义的。那是 11 个字节,这是我所期望的。 SSO local_buf 的 16 个字节,其中前 5 个字节由“test[=52=]”初始化。

对于 str2 没有“未初始化字节”消息,并且 VBITS 都已定义。

最后是 str3,对于 SSO 来说太长了,再次出现“未初始化字节”消息,这次输出是

字节 0 位 0
[相同的 1 到 22]
字节 23 位 0
字节 24 位 ff
[相同的 25 到 30]
字节 31 位 ff

同样符合预期。现在字符串的最后 8 个字节就像未使用的填充一样,未初始化。