2017-02-09 12 views
0

У меня есть класс в моем проекте, который в основном хранит std::vector элементов и должен иметь возможность хранить и загружать двоичное представление объект на/из диска. Когда объект загружает файл, он считывает количество элементов переменной, а затем хранимые элементы в массив. Из этого массива он перемещает элементы в векторный элемент. Я разрешаю C++ обрабатывать память, как в я не явным образом вызываю calloc или new. Хотя он работает нормально, Valgrind дает мне некоторые сообщения об ошибках о недопустимых чтениях и записи, которые я не понимаю. Они, похоже, происходят из деструктора класса, но поскольку класс содержит только std::vector элементов, которые являются «динамическими», мне не нужно ничего делать, или я?Valgrind показывает неверные ошибки чтения/записи, но я не использую новые или calloc, только векторы и фиксированные массивы

Это рабочий минимальный пример:

#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»), где раньше ничего не было?

+1

'std :: string in_v [n];' является VLA и может быть тем, что вызвало его. Если размер массива - это то, что неизвестно, вы должны использовать 'std :: vector' – NathanOliver

+2

' file.read ((char *) in_v, n * sizeof (std :: string)); 'look _really_ dodgy –

+0

@NathanOliver спасибо за совет, я просто попробовал 'v.resize (n);' и 'file.read ((char *) v.data(), n * sizeof (std :: string));'. Это работает, но я все еще получаю ошибки valgrind. То же самое, если я явно выделяю 'std :: stiring * in_v = new std :: string [n]' и 'delete []' it в конце метода. – mable

ответ

1

Эта линия -

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

вызывает неопределенное поведение позже на дороге. Вы пытаетесь прочитать некоторые символы в объект std :: string, который не был предназначен для него. Делая это, вы, вероятно, работает над внутренними данными (вероятно, указатели), и доступ к ним будет вызывать UB

Вместо этого, вы должны использовать std::vector<char>, reserve достаточно байт для хранения строки вы ели чтение из файла и читать в vec.data()