【问题标题】:memcpy on new std::string implementation (gcc 5.1)新 std::string 实现上的 memcpy (gcc 5.1)
【发布时间】:2015-10-18 14:13:33
【问题描述】:

我编写了以下代码,它似乎可以与旧的 std::string 实现一起正常工作。但是使用 gcc 5.1,它会崩溃。

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

struct abc
{
   public:
   abc() {}
   abc(const std::string& x)
      : gullu(x)
   {
   }
   std::string gullu;
};

int main()
{
   abc *a = new abc("dhfghdf");
   abc *b = new abc();
   memcpy((void *)&b, (void *)&a, sizeof(abc));
   std::cout << a->gullu.data() << std::endl;
   std::cout << b->gullu.data();
   return 0;
}

调试了一下,发现做memcpy后对象'a'的内容变成了垃圾。

a 的构造后,

(gdb) p *a $1 = {gullu = {静态 npos = 4294967295, _M_dataplus = {> = {<:new_allocator> = {}, }, _M_p = 0x804ea18 "dhfghdf"}, _M_string_length = 7, {_M_local_buf = "dhfghdf\000\000\000\000\000\000\000\000", _M_allocated_capacity = 1734764644}}}

b 构造后

(gdb) p *b $2 = {gullu = {静态 npos = 4294967295, _M_dataplus = {> = {<:new_allocator> = {}, }, _M_p = 0x804ea38 ""}, _M_string_length = 0, {_M_local_buf = '\000' , _M_allocated_capacity = 0}}}

memcpy 之后

(gdb) p *a $4 = {gullu = {静态 npos = 4294967295, _M_dataplus = {> = {<:new_allocator> = {}, }, _M_p = 0x666468 },_M_string_length = 2572404,{_M_local_buf = "t@'\000\030Ùÿ¿I\215\004\b\001\000\000", _M_allocated_capacity = 2572404}}}

(gdb) p *b $5 = {gullu = {静态 npos = 4294967295, _M_dataplus = {> = {<:new_allocator> = {}, }, _M_p = 0x804ea18 "dhfghdf"}, _M_string_length = 7, {_M_local_buf = "dhfghdf\000\000\000\000\000\000\000\000", _M_allocated_capacity = 1734764644}}}

我正在使用一个第三方库,它似乎在做一个 memcpy,它与以前的编译器一起工作,但由于这个问题而不能与 gcc 5.1 一起工作

有人可以帮我解决这个问题吗?

【问题讨论】:

  • 你为什么要使用memcpy() 而不是拷贝构造函数呢?无论是哪个编译器版本,您一直称之为未定义的行为。
  • 我正在使用一个第三方库,它似乎在做一个 memcpy,由于这个问题,它与以前的编译器一起工作,而不是与 gcc 5.1 一起工作
  • 不要 memcpy std::string。您需要阅读浅拷贝的后果。无论如何,即使没问题,您也应该只复制指针的大小,因为这是您从 &amp;a 得到的。
  • GCC 5.1(C++11/C++14/C++17) 对我来说不会崩溃。给出输出dhfghdf dhfghdf
  • @Praveen:未定义的行为通常很难重现。

标签: c++ gcc g++ stdstring


【解决方案1】:

您正在复制 sizeof(abc) 字节的数据,但您给 memcpy 的指针指向 ab 的指针,而不是对象 - 这个 (void*)&amp;a 中的地址肯定是错误的。

但是你真的不应该这样做,因为像这样复制对象而不是使用它们的复制 ctor 或其他适当的方法会导致崩溃和未定义的行为。在这种特定情况下,如果字符串稍长一些可能会发生什么,您最终会得到两个指向同一个堆块的 string 对象,因此您将获得双重删除退出范围,这是一个不可恢复的崩溃。

【讨论】:

    【解决方案2】:

    您的代码触发了未定义的行为,因为您向指针b 的地址复制的字节数超过了指针的大小:您复制的是sizeof(abc) 而不是sizeof(a)

    【讨论】:

      【解决方案3】:

      好吧,你没有做你想做的事。虽然您显然是在尝试将 string 的内容复制到另一个,但您所做的只是将指针 (a) 的值复制到另一个 (b),但复制的大小错误!

      顺便说一句,你想要的是memcpy(b, a, sizeof(abc));,但这不应该也可以工作(虽然它可以在几种情况下工作)!正如我从皮肤上了解到的(感谢this answer),您不能memcpy 具有非平凡初始化的对象。通过重用此类对象的存储,您结束了它的生命周期,但只是 memcpy 对它您不会复活它,因此 b 指向的对象将不存在。

      引用 C++11 标准(§3.8 对象生命周期 [basic.life]):

      对象的生命周期是对象的运行时属性。据说一个对象具有非平凡的初始化 如果它是类或聚合类型,并且它或其成员之一由构造函数初始化,而不是琐碎 默认构造函数。 [注意:通过简单的复制/移动构造函数进行初始化是非平凡的初始化。 — end note ] T 类型对象的生命周期开始于: — 获得类型 T 的正确对齐和大小的存储,和 — 如果对象有非平凡的初始化,它的初始化就完成了。 T 类型对象的生命周期在以下情况下结束: — 如果 T 是具有非平凡析构函数 (12.4) 的类类型,则析构函数调用开始,或者 — 对象占用的存储空间被重用或释放。 §

      【讨论】:

      • 这是核心点——你永远不可能memcpystd::string,无论你是否得到正确的参数。
      • @Puppy 是的,对于一些复杂的对象来说这很明显,但在一些遗留代码上,我很难弄清楚问题出在哪里。事实证明(最终)问题是一个看起来很无辜的类上的一个看起来很无辜的 qsort ......但是没有调用复制ctor,所以出现了一些非常模糊的崩溃。
      猜你喜欢
      • 2012-06-02
      • 1970-01-01
      • 1970-01-01
      • 2013-11-01
      • 2010-11-30
      • 2011-06-30
      • 2012-12-17
      • 1970-01-01
      • 2018-10-23
      相关资源
      最近更新 更多