двойная ошибка освобождения или повреждения при копировании объекта с помощью memcpy

У меня есть следующий код:

#include <iostream>
#include <string>
#include <cstring>

struct test {
    std::string name;
    size_t id;
};


int main() {
    test t;
    t.name = "147.8.179.239";
    t.id = 10;

    char a[sizeof(t)] = "";
    std::memcpy(a, &t, sizeof(t));

    test b;
    std::memcpy(&b, a, sizeof(t)); 

    std::cout << b.name << " " << b.id << std::endl;
}

когда я его компилирую и запускаю, он выдает следующую ошибку:

147.8.179.239 10
*** Error in `./test': double free or corruption (fasttop): 0x0000000000bf9c20 ***
Aborted (core dumped)

Оказывается, код может распечатать результат. Но как я могу исправить эту ошибку?


person Johnnylin    schedule 18.08.2016    source источник
comment
@stackptr Что такое VLA?   -  person Johnnylin    schedule 18.08.2016
comment
Массив переменной длины. Его размер неизвестен во время компиляции и недопустим в C++.   -  person Edward Karak    schedule 18.08.2016
comment
@Johnnylin Массив переменной длины. Это запрещено в стандарте C++ (хотя разрешено некоторыми расширениями компилятора).   -  person Algirdas Preidžius    schedule 18.08.2016
comment
@stackptr как это VLA? sizeof(t) известно во время компиляции. Если бы OP делал char a[t.name.length()], это был бы VLA, но я не понимаю, как это VLA.   -  person Borgleader    schedule 18.08.2016
comment
@stackptr в C++ нет VLA   -  person M.M    schedule 18.08.2016
comment
Я думаю, что sizeof(t) здесь фиксированная длина   -  person Johnnylin    schedule 18.08.2016
comment
Печально, что компиляторы не предупреждают об использовании memcpy для типов, отличных от std::is_trivial<T>::value.   -  person KABoissonneault    schedule 18.08.2016


Ответы (3)


Используя memcpy таким образом, вы получаете два абсолютно идентичных объекта std::string. Сюда входят любые указатели, которые они могут использовать внутри. Поэтому, когда запускается деструктор для каждого объекта, они оба пытаются освободить один и тот же указатель.

Вот почему вам нужно использовать либо конструктор копирования, либо присвоить один другому (т.е. использовать переопределенный operator=). Он знает об этих различиях в реализации и корректно их обрабатывает, т. е. выделяет отдельный буфер памяти для целевого объекта.

Если вы хотите извлечь строку, содержащуюся в std::string, вам необходимо сериализовать объект в известное представление. Затем вы можете десериализовать его, чтобы преобразовать обратно.

std::string s1 = "hello";
printf("len=%zu, str=%s\n",s1.size(),s1.c_str());

// serialize
char *c = new char[s1.size()+1];
strcpy(c, s1.c_str());
printf("c=%s\n",c);

// deserialize
std::string s2 = c;
printf("len=%zu, str=%s\n",s2.size(),s2.c_str());

Вы должны выполнить аналогичные шаги для других объектов класса.

person dbush    schedule 18.08.2016
comment
чего я хочу добиться, так это преобразовать структуру, содержащую std::string и другой тип POD, в массив символов. Затем я могу использовать сокет для его передачи. Спасибо за ваше объяснение. - person Johnnylin; 18.08.2016
comment
@Johnnylin some_string.c_str() предоставит вам базовый массив символов строки, затем вы можете отправить его, нет необходимости в копиях. (также .size() даст вам, сколько символов в массиве) - person Borgleader; 18.08.2016
comment
@Johnnylin Тогда вам нужно выбрать формат для этих данных на уровне байтов, потому что сокеты предоставляют каналы на уровне байтов. Затем вам нужно написать код для преобразования данных, которые вы хотите отправить, в выбранный вами формат, чтобы вы могли их отправить. - person David Schwartz; 18.08.2016

Вы не можете memcpy() использовать структуру без POD, например test. Вы полностью разрушаете std::string члена.

Вы должны использовать конструктор копирования для копирования объектов C++.

person Jesper Juhl    schedule 18.08.2016
comment
То есть две ошибки? один представляет собой массив переменной длины, а другой - структуру memcpy без POD? - person Johnnylin; 18.08.2016
comment
VLA незаконны точнее, они являются нестандартным расширением некоторых компиляторов (gcc и, возможно, clang), при этом я не вижу никаких VLA. - person Borgleader; 18.08.2016
comment
Если вы просто хотите сделать b равным t, то просто сделайте b = t; - или я неправильно понимаю, что вы пытаетесь сделать? - person Jesper Juhl; 18.08.2016
comment
чего я хочу добиться, так это преобразовать структуру, содержащую std::string вместе с другими типами POD, в массив символов. Затем я могу использовать сокет для его передачи. Спасибо за ваше объяснение. - person Johnnylin; 18.08.2016

Фактическая причина, по которой вы получаете двойную свободную ошибку, связана с тем фактом, что вместо создания нового строкового объекта для ваших переменных a и b вы просто копируете ссылку (объект string реализован с использованием переменной длины char *).

Поскольку деструктор string освобождает этот адрес памяти, когда ваша программа завершается, и, как объяснялось выше, у вас есть два строковых объекта, указывающих на один и тот же адрес, вы получаете двойную ошибку освобождения

Это будет работать, как сказал @JesperJuhl, вы должны использовать конструктор копирования

#include <iostream>
#include <string>
#include <cstring>

struct test
{
    std::string name;
    size_t id;
};


int main()
{
    test t;
    test a;
    test b;

    t.name = "147.8.179.239";
    t.id = 10;

    a=t;
    b=t;

    std::cout << b.name << " " << b.id << std::endl;
}
person Ishay Peled    schedule 18.08.2016
comment
Итак, в принципе, я не могу запомнить структуру, содержащую не-POD тип (string), если я не изменю массив std::string на char? - person Johnnylin; 18.08.2016
comment
Правильно, обычно рекомендуется использовать конструкторы копирования для объектов. На самом деле, не должно быть веских причин, если вы используете С++ - конструктор копирования абстрагирует все дополнительные выделения памяти, о которых вы, возможно, даже не подозреваете, что происходит под капотом. - person Ishay Peled; 18.08.2016