Valgrind 显示无效 read/write 错误,但我不使用 new 或 calloc,仅使用向量和固定数组

Valgrind shows invalid read/write errors but I don't use new or calloc, only vectors and fixed arrays

我的项目中有一个class,主要存储std::vector元素,应该能够存储和加载对象to/from盘的二进制表示。当一个对象加载一个文件时,它会将元素的数量读取到一个变量中,然后将存储的元素读取到一个数组中。从这个数组中,它将元素推入向量成员。我让 C++ 处理内存,因为我没有显式调用 callocnew。虽然它工作正常,但 Valgrind 给了我一些我不理解的关于无效读写的错误消息。它们似乎源自 class 析构函数,但由于 class 只有 std::vector 元素是 "dynamical" 我不应该在那里做任何事情,或者我呢?

这是一个最小的工作示例:

#include <vector>
#include <string>
#include <fstream>
#include <iostream>

class Test {
    public:
        Test() {}
        ~Test() {}

        void add(std::string s) { v.push_back(s); }
        std::vector<std::string>& get() { return v; }

        void store(char const* path) {
            std::ofstream file(path, std::ios_base::out | std::ios_base::binary);
            unsigned int n = v.size();
            file.write((char*) &n, sizeof(unsigned int));
            file.write((char*) v.data(), n*sizeof(std::string));
            file.close();
        }

        void read(char const* path) {
            std::ifstream file(path, std::ios_base::in | std::ios_base::binary);
            unsigned int n;
            file.read((char*) &n, sizeof(unsigned int));
            std::string in_v[n];
            file.read((char*) in_v, n*sizeof(std::string));
            v.clear();
            for (unsigned int i = 0; i < n; i++) {
                v.push_back(in_v[i]);
            }

            if (!file) { throw std::runtime_error("reading failed"); }
        }

    private:
        std::vector<std::string> v;
};

std::ostream& operator<<(std::ostream& os, Test& t) {
    for (unsigned int i = 0; i < t.get().size(); i++) {
        os << t.get()[i] << std::endl;
    }
    return os;
}

int main(int argc, char *argv[]) {
    Test a;
    a.add("foo");
    a.add("bar");

    std::cout << a << std::endl;

    a.store("file");

    //Test b;
    //b.read("file");
    //std::cout << "restored:" << std::endl << b << std::endl;

    return 0;
}

如果我在 valgrind 中编译并运行这个,一切都按预期工作并且没有检测到泄漏:

==24891== Memcheck, a memory error detector
==24891== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==24891== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==24891== Command: ./dummy
==24891== 
foo
bar

==24891== 
==24891== HEAP SUMMARY:
==24891==     in use at exit: 72,704 bytes in 1 blocks
==24891==   total heap usage: 7 allocs, 6 frees, 81,528 bytes allocated
==24891== 
==24891== LEAK SUMMARY:
==24891==    definitely lost: 0 bytes in 0 blocks
==24891==    indirectly lost: 0 bytes in 0 blocks
==24891==      possibly lost: 0 bytes in 0 blocks
==24891==    still reachable: 72,704 bytes in 1 blocks
==24891==         suppressed: 0 bytes in 0 blocks
==24891== Rerun with --leak-check=full to see details of leaked memory
==24891== 
==24891== For counts of detected and suppressed errors, rerun with: -v
==24891== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

但是一旦我取消注释 main() 函数中的最后几行,一切仍然 有效 ,但现在 valgrind 打印了一些消息,我不明白为什么。

foo
bar

restored:
foo
bar

==4004== Invalid read of size 4
==4004==    at 0x4F04610: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (in /usr/lib64/libstdc++.so.6.0.21)
==4004==    by 0x4026CE: void std::_Destroy<std::string>(std::string*) (stl_construct.h:93)
==4004==    by 0x402534: void std::_Destroy_aux<false>::__destroy<std::string*>(std::string*, std::string*) (stl_construct.h:103)
==4004==    by 0x4021FD: void std::_Destroy<std::string*>(std::string*, std::string*) (stl_construct.h:126)
==4004==    by 0x401D76: void std::_Destroy<std::string*, std::string>(std::string*, std::string*, std::allocator<std::string>&) (stl_construct.h:151)
==4004==    by 0x401B5B: std::vector<std::string, std::allocator<std::string> >::~vector() (stl_vector.h:424)
==4004==    by 0x4016E5: Test::~Test() (dummy.cpp:9)
==4004==    by 0x4015B0: main (dummy.cpp:48)
==4004==  Address 0x5aa8c90 is 16 bytes inside a block of size 28 free'd
==4004==    at 0x4C2A184: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==4004==    by 0x4F04603: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (in /usr/lib64/libstdc++.so.6.0.21)
==4004==    by 0x4026CE: void std::_Destroy<std::string>(std::string*) (stl_construct.h:93)
==4004==    by 0x402534: void std::_Destroy_aux<false>::__destroy<std::string*>(std::string*, std::string*) (stl_construct.h:103)
==4004==    by 0x4021FD: void std::_Destroy<std::string*>(std::string*, std::string*) (stl_construct.h:126)
==4004==    by 0x401D76: void std::_Destroy<std::string*, std::string>(std::string*, std::string*, std::allocator<std::string>&) (stl_construct.h:151)
==4004==    by 0x401B5B: std::vector<std::string, std::allocator<std::string> >::~vector() (stl_vector.h:424)
==4004==    by 0x4016E5: Test::~Test() (dummy.cpp:9)
==4004==    by 0x4015A4: main (dummy.cpp:56)
==4004== 
==4004== Invalid write of size 4
==4004==    at 0x4F04616: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (in /usr/lib64/libstdc++.so.6.0.21)
==4004==    by 0x4026CE: void std::_Destroy<std::string>(std::string*) (stl_construct.h:93)
==4004==    by 0x402534: void std::_Destroy_aux<false>::__destroy<std::string*>(std::string*, std::string*) (stl_construct.h:103)
==4004==    by 0x4021FD: void std::_Destroy<std::string*>(std::string*, std::string*) (stl_construct.h:126)
==4004==    by 0x401D76: void std::_Destroy<std::string*, std::string>(std::string*, std::string*, std::allocator<std::string>&) (stl_construct.h:151)
==4004==    by 0x401B5B: std::vector<std::string, std::allocator<std::string> >::~vector() (stl_vector.h:424)
==4004==    by 0x4016E5: Test::~Test() (dummy.cpp:9)
==4004==    by 0x4015B0: main (dummy.cpp:48)
==4004==  Address 0x5aa8c90 is 16 bytes inside a block of size 28 free'd
==4004==    at 0x4C2A184: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==4004==    by 0x4F04603: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (in /usr/lib64/libstdc++.so.6.0.21)
==4004==    by 0x4026CE: void std::_Destroy<std::string>(std::string*) (stl_construct.h:93)
==4004==    by 0x402534: void std::_Destroy_aux<false>::__destroy<std::string*>(std::string*, std::string*) (stl_construct.h:103)
==4004==    by 0x4021FD: void std::_Destroy<std::string*>(std::string*, std::string*) (stl_construct.h:126)
==4004==    by 0x401D76: void std::_Destroy<std::string*, std::string>(std::string*, std::string*, std::allocator<std::string>&) (stl_construct.h:151)
==4004==    by 0x401B5B: std::vector<std::string, std::allocator<std::string> >::~vector() (stl_vector.h:424)
==4004==    by 0x4016E5: Test::~Test() (dummy.cpp:9)
==4004==    by 0x4015A4: main (dummy.cpp:56)
==4004== 
==4004== Invalid free() / delete / delete[] / realloc()
==4004==    at 0x4C2A184: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==4004==    by 0x4F04603: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (in /usr/lib64/libstdc++.so.6.0.21)
==4004==    by 0x4026CE: void std::_Destroy<std::string>(std::string*) (stl_construct.h:93)
==4004==    by 0x402534: void std::_Destroy_aux<false>::__destroy<std::string*>(std::string*, std::string*) (stl_construct.h:103)
==4004==    by 0x4021FD: void std::_Destroy<std::string*>(std::string*, std::string*) (stl_construct.h:126)
==4004==    by 0x401D76: void std::_Destroy<std::string*, std::string>(std::string*, std::string*, std::allocator<std::string>&) (stl_construct.h:151)
==4004==    by 0x401B5B: std::vector<std::string, std::allocator<std::string> >::~vector() (stl_vector.h:424)
==4004==    by 0x4016E5: Test::~Test() (dummy.cpp:9)
==4004==    by 0x4015B0: main (dummy.cpp:48)
==4004==  Address 0x5aa8c80 is 0 bytes inside a block of size 28 free'd
==4004==    at 0x4C2A184: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==4004==    by 0x4F04603: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (in /usr/lib64/libstdc++.so.6.0.21)
==4004==    by 0x4026CE: void std::_Destroy<std::string>(std::string*) (stl_construct.h:93)
==4004==    by 0x402534: void std::_Destroy_aux<false>::__destroy<std::string*>(std::string*, std::string*) (stl_construct.h:103)
==4004==    by 0x4021FD: void std::_Destroy<std::string*>(std::string*, std::string*) (stl_construct.h:126)
==4004==    by 0x401D76: void std::_Destroy<std::string*, std::string>(std::string*, std::string*, std::allocator<std::string>&) (stl_construct.h:151)
==4004==    by 0x401B5B: std::vector<std::string, std::allocator<std::string> >::~vector() (stl_vector.h:424)
==4004==    by 0x4016E5: Test::~Test() (dummy.cpp:9)
==4004==    by 0x4015A4: main (dummy.cpp:56)
==4004== 
==4004== 
==4004== HEAP SUMMARY:
==4004==     in use at exit: 72,704 bytes in 1 blocks
==4004==   total heap usage: 11 allocs, 12 frees, 90,296 bytes allocated
==4004== 
==4004== LEAK SUMMARY:
==4004==    definitely lost: 0 bytes in 0 blocks
==4004==    indirectly lost: 0 bytes in 0 blocks
==4004==      possibly lost: 0 bytes in 0 blocks
==4004==    still reachable: 72,704 bytes in 1 blocks
==4004==         suppressed: 0 bytes in 0 blocks
==4004== Rerun with --leak-check=full to see details of leaked memory
==4004== 
==4004== For counts of detected and suppressed errors, rerun with: -v
==4004== ERROR SUMMARY: 6 errors from 3 contexts (suppressed: 0 from 0)

这些错误从何而来?我希望它与 read 方法有关,但是为什么 valgrind 显示创建 a ("dummy.cpp:48") 之前没有错误的错误?

这一行 -

file.read((char*) in_v, n*sizeof(std::string));

稍后在路上造成未定义的行为。您正在尝试将一些字符读入不是为它设计的 std::string 对象。这样做,您可能 运行 访问内部数据(可能是指针),访问它们将导致 UB

相反,您应该使用 std::vector<char>reserve 足够的字节来保存您从文件中读取的字符串,并读入 vec.data()