Неправильно отображаются данные

Мой код:

#include <iostream>
#include <stdlib.h>
#include <exception>
#include <string>

class MemoryException : public std::exception{

public:

    MemoryException(std::string message){
        this->message = message;
    }

    const char* what() const noexcept override{
        return message.c_str();
    }

private:

    std::string message;

};

class Memory{

public:
    Memory(int size){
        data = malloc(size);
        if(data == nullptr){
            throw MemoryException("Failed to allocate memory");
        }
        sizeMemory = size;
        freeMemory = size;
    }

    ~Memory(){
        free(data);
        delete[] headline;
    }

    template<typename T>
    void set(T values){
        const int size = sizeof(values);
        if(size > freeMemory){
            throw MemoryException("Object too bid " + std::to_string(size) + "/" + std::to_string(freeMemory));
        }
        set(values, sizeMemory - freeMemory);
        addH(size);
        freeMemory -= size;
    }

    template<typename T>
    T get(int index){
        if(index > quantity){
            throw MemoryException("Went beyond the limits of memory");
        }
        return *getType<T>(getPosition(index));
    }

    int getFreeMemory(){
        return freeMemory;
    }

    void remove(int index){
        if(index > quantity){
            throw MemoryException("Went beyond the limits of memory");
        }
        const int start = getPosition(index);
        const int size = headline[index];
        char* val = (char*)data;
        val += start;
        for(int i = 0; i < sizeMemory - start; i++){
            *(val + i) = *(val + size + i);
        }
        int* h = new int[quantity - 1];
        for(int i = 0; i < index; i++){
            h[i] = headline[i];
        }
        for(int i = index; i < quantity - 1; i++){
            h[i] = headline[i + 1];
        }
        delete[] headline;
        headline = h;
        quantity--;
        freeMemory += size;
    }

private:

    int sizeMemory;
    int freeMemory;
    int quantity = 0;
    int* headline = nullptr;
    void* data;

    template<typename T>
    T* getType(int size){
        char* f = static_cast<char*>(data) + size;
        return static_cast<T*>(static_cast<void*>(f));
    }

    int getPosition(int index){
        int s = 0;
        for(int i = 0; i < index; i++){
            s += headline[i];
        }
        return s;
    }

    template<typename T>
    void set(T values, int position){
        const int size = sizeof(values);
        char* val = (char*)data;
        val += position;
        T* d = &values;
        char* dI = (char*)d;
        for(int i = 0; i < size; i++){
            *(val + i) = *(dI + i);
        }
    }

    void addH(int size){
        int* h = new int[quantity + 1];
        for(int i = 0; i < quantity; i++){
            h[i] = headline[i];
        }
        h[quantity] = size;
        delete[] headline;
        headline = h;
        quantity++;
    }

};

struct Human{

    std::string name;
    int age;

    std::string info(){
        return "name: " + name + " age: " + std::to_string(age);
    }

};

int main(){
    Human h;
    Memory a(80);
    h.age = 28;
    h.name = "Bob";
    try{
        a.set(h);
        a.set(5);
        a.set("is C++ code");
        a.set(false);
        std::cout << a.get<Human>(0).info() << std::endl;
        std::cout << a.get<int>(1) << std::endl;
        std::cout << a.get<const char*>(2) << std::endl;
        std::cout << a.get<bool>(3) << std::endl;
        std::cout << "---REMOVE---" << std::endl;
        a.remove(1);
        std::cout << a.get<Human>(0).info() << std::endl;
        std::cout << a.get<const char*>(1) << std::endl;
        std::cout << a.get<bool>(2) << std::endl;
    }catch(const MemoryException& ex){
        std::cout << ex.what() << std::endl;
    }
}

Вывод:

name: Bob age: 28
5
is C++ code
0
---REMOVE---
name: 8j_ age: 28
is C++ code
0

после удаления 1-го элемента вместо Bob выводится непонятно что, а в остальных сучьях вывод корректный


Ответы (1 шт):

Автор решения: Stanislav Volodarskiy

Я немного упростил ваш пример:

#include <cstring>
#include <string>

class Memory{
public:
    void set(std::string s) { memcpy(data, &s, sizeof(s)); }
    std::string get() {
        return *static_cast<std::string*>(static_cast<void*>(data));
    }

private:
    char data[sizeof(std::string)];
};

int main(){
    Memory a;
    std::string name = "BobBobBobBobBobBobBobBobBobBobBob";
    a.set(name);
    auto s = a.get();
}

Скомпилировал его с отладочной информацией и запустил valgrind:

$ g++ -g temp.cpp 

$ valgrind ./a.out
==2407848== Memcheck, a memory error detector
==2407848== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==2407848== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==2407848== Command: ./a.out
==2407848== 
==2407848== Invalid read of size 8
==2407848==    at 0x484B345: memmove (vg_replace_strmem.c:1382)
==2407848==    by 0x49AC806: void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::_M_construct<char*>(char*, char*, std::forward_iterator_tag) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==2407848==    by 0x1093B6: Memory::get[abi:cxx11]() (temp.cpp:10)
==2407848==    by 0x1092CE: main (temp.cpp:21)
==2407848==  Address 0x4d7bcf0 is 0 bytes inside a block of size 34 free'd
==2407848==    at 0x484471B: operator delete(void*) (vg_replace_malloc.c:923)
==2407848==    by 0x1092B8: main (temp.cpp:20)
...

valgrind жалуется что программа читает память, которую уже освободили. Чтобы понять почему посмотрим что делает вызов a.set(name);:

  • создаёт копию строки name. Строка передана в метод по значению, поэтому она копируется.
  • копирует байты копии строки в "память".
  • уничтожает копию строки name.

Копия строки содержит в себе указатель на массив символов. Когда копия уничтожается, массив освобождается. Но вы запомнили указатель на него в "памяти" и позже будете обращаться к освобождённому блоку памяти.

Важное дополнение: строка сделана длинной чтобы избежать Small string optimization (SSO). Это механизм при котором буфер под короткие строки не выделяется в свободной памяти а помещается прямо в тело объекта. SSO делает ошибку менее заметной, скажем так. Короткую строку можно скопировать и копия будет выглядеть для программы нормально.

Но в любом случае вы пытаетесь создать объект C++ без вызова конструктора. Есть условия при которых это возможно, но не в случае std::string. Все экземпляры std::string должны быть сконструированы должным образом. Копировать байты и рассчитывать что полученная копия будет работать нельзя.

Есть способ создавать объекты в "сырой" памяти. Почитайте про placement new.

→ Ссылка