【问题标题】:C memset seems to not write to every memberC memset 似乎不会写给每个成员
【发布时间】:2009-04-12 20:56:34
【问题描述】:

我写了一个小坐标类来处理 int 和 float 坐标。

template <class T>
class vector2
{
public:
    vector2() { memset(this, 0, sizeof(this)); }
    T x;
    T y;
};

然后在 main() 我做:

vector2<int> v;

但根据我的 MSVC 调试器,只有 x 值设置为 0,y 值未触及。我以前从未在模板类中使用过 sizeof() ,这可能是造成问题的原因吗?

【问题讨论】:

  • 好的,好的,我确信,memset 很糟糕(至少在这种情况下)。 ;) 但是关于它覆盖 vtable 的能力的部分有点吓人。谢天谢地,我现在学会了简单的方法。
  • memset() 在 C++ 中通常是个坏主意。忘记它的存在。
  • 调试一个在运行时被意外修改的vtable一点也不好玩……至少这个错误可能会在第一次调用虚函数时失败,因为vtable指针本身是NULL。
  • 不要使用 memset()。但如果你至少使用正确的尺寸。 sizeof(*pointer) 而不是 sizeof(pointer)。 sizeof(this) 在 32 位机器上为 4 个字节,在 64 位机器上为 8 个字节,与 sizeof(*this) 无关。

标签: c++ templates sizeof memset


【解决方案1】:

不,不要使用memset——它将指针的大小(我的 x86 Intel 机器上的 4 个字节)字节从this 指向的位置开始清零。这是一个坏习惯:在将memset 与复杂类一起使用时,您还会将虚拟指针和指向虚拟基的指针归零。而是这样做:

template <class T>
class vector2
{
public:
    // use initializer lists
    vector2() : x(0), y(0) {}
    T x;
    T y;
};

【讨论】:

  • 我听说虽然结构不是类,但您可以使用“构造函数”将所有成员归零。即使您稍后添加更多,memset 也会捕获所有人。没有办法让 memset 工作吗?
  • 在 C++ 中,结构与任何类具有完全相同的保存成员函数的能力。只是一些不同之处,例如:成员具有默认的公共可见性等。对于 POD,与 vector2 一样,这是有效的。但坚持一种风格总是更好。
  • +1,关于虚拟表指针和虚拟基的好点
  • mizipzor,即使 C++ 中有“将所有数字成员设置为零”的语法,它也没有用。除了最简单的情况外:(A)您不想将所有数字初始化为 0,(B)您想有意义地初始化类类型的成员,以及(C)添加更多成员并不意味着您希望它们归零。
【解决方案2】:

正如其他人所说,memset() 不是这样做的正确方法。 然而,为什么不呢?

首先,您尝试使用memset() 只是清除sizeof(void *) 字节。对于您的示例情况,这显然是 x 成员占用的字节数。

简单的解决方法是编写memset(this, 0, sizeof(*this)),在这种情况下会同时设置xy

但是,如果您的 vector2 类有任何虚方法,并且编译器使用通常的机制来表示它们,那么 memset 将破坏 vtable 并通过设置 vtable 指针来破坏实例为空。哪个不好。

另一个问题是,如果类型 T 需要一些比将其位设置为 0 更复杂的构造函数操作,那么成员的构造函数会被 not 调用,但它们的效果会被破坏用memset()覆盖成员的内容。

唯一正确的做法是将默认构造函数编写为

vector2(): x(0), y(0), {}

并且完全忘记尝试使用memset()

编辑: D.Shawley 在评论中指出,xy 的默认构造函数实际上是在原始代码中的 memset() 之前调用的。虽然在技术上是正确的,但调用 memset() 会覆盖成员,这充其量是非常非常糟糕的形式,最坏的情况是调用未定义行为的恶魔。

正如所写,vector2 类是 POD,只要 T 类型也是普通的旧数据,就像 Tintfloat 的情况一样。

然而,只要T 成为某种bignum 值类,就会导致可能很难诊断的问题。如果幸运的话,它们会通过取消引用 memset() 创建的 NULL 指针而导致访问冲突提前显现。但幸运女神是一个善变的情妇,更有可能的结果是一些内存泄漏,应用程序变得“不稳定”。或者更有可能是“摇摇欲坠”。

OP 在对另一个答案的评论中问道“......难道没有办法让 memset 工作吗?”

答案很简单,“不”。

选择了 C++ 语言并选择充分利用模板后,您必须通过正确使用该语言来为这些优势付出代价。绕过构造函数(在一般情况下)根本不正确。虽然在某些情况下在 C++ 程序中调用 memset() 是合法、安全和明智的,但这并不是其中之一。

【讨论】:

  • 实际上 x 和 y 的默认构造函数都在 OP 中调用。并不是说这会使使用 memset 变得不那么残酷。在您的情况下,将调用来自 int -> T 的转换构造函数。
  • 是的。然后,在复杂类型 T 完成其默认构造之后,memset 残酷地将位重回全零。
【解决方案3】:

问题是这是一个指针类型,它是 4 个字节(在 32 位系统上),而整数是 4 个字节(在 32 位系统上)。试试:

sizeof(*this)

编辑:虽然我同意其他人的观点,构造函数中的初始化列表可能是这里的正确解决方案。

【讨论】:

  • 成功了! :) 但我不记得以前做过那个取消引用,只是“这个”。我的记忆是否让我失望,或者这通常是它的完成方式?我现在正在挖掘我的代码以找到我做过类似事情的其他地方。
  • 这是 sizeof() 一直以来的工作方式......虽然你真的应该考虑初始化列表,假设 0 总是与你计划使用的模板类型兼容。
  • Downvoted -- memset 永远不应该用于 C++ 中的初始化,POD 结构的 可能 例外。查看有关 memset 和 virtuals 的其他答案。
  • 您也可以删除答案。或者,至少,删除使用 memset 的“正确”方式,因为在这种情况下没有正确的方式。
【解决方案4】:

不要使用 memset。它会在非 POD 类型上严重中断(并且不一定很容易调试),在这种情况下,它可能比简单地将两个成员初始化为零(两个赋值与函数调用)要慢得多。

此外,您通常不希望将类的所有成员归零。您希望将零是有意义的默认值的那些清零。并且您应该养成在任何情况下将您的成员初始化为有意义的值的习惯。将所有内容归零并假装问题不存在只会保证以后会让人头疼。如果您将成员添加到类中,请决定是否应该初始化该成员以及如何初始化。

如果您确实需要类似 memset 的功能,请至少使用与非 POD 类型兼容的 std::fill。

如果您使用 C++ 进行编程,请使用 C++ 提供的工具。否则,称它为 C。

【讨论】:

  • memset() 通常优化得很好,如果编译器不将分配转换为对 memset() 的调用,我希望它会更快。
  • memset 针对其设计用途进行了很好的优化,即将内存范围设置为一个特定值。使用它来设置两个整数可能会更慢。
  • 但就像我说的,如果需要类似 memset 的功能,请使用 std::fill。由于它仅在标题中定义,因此可以轻松内联。 Memset 需要更多的编译器技巧来内联。
  • @Bastien Léonard:如果 memset 是为相邻字段实现初始化程序的最快方法,那么即使是简单优化的编译器也会使用 memset(或其等效项)。正如lothar 所说,不要尝试比编译器更聪明。
【解决方案5】:

勉强是正确的。然而,与使用 0 构造 x 和 y 不同,显式调用默认构造函数会将内部类型设置为 0,并允许模板用于具有默认构造函数的结构和类。

template <class T>
class vector2
{
public:
    // use initializer lists
    vector2() : x(), y() {}
    T x;
    T y;
};

【讨论】:

    【解决方案6】:

    不要试图比编译器更聪明。按照语言的意图使用初始值设定项列表。编译器知道如何有效地初始化基本类型。

    如果您尝试对具有虚函数的类进行 memset hack,您很可能会覆盖 vtable,最终导致灾难。不要那样使用 hack,它们是维护的噩梦。

    【讨论】:

    • 其实不,都是我。所以我写的书不要看我的书,呵呵。 ;)
    【解决方案7】:

    这可能会起作用:

    
    char buffer[sizeof(vector2)];
    memset(buffer, 0, sizeof(buffer));
    vector2 *v2 = new (buffer) vector2();
    

    ..或替换/覆盖 vector2::new 来做类似的事情。 不过对我来说仍然很奇怪。

    肯定会选择

    
    vector2(): x(0), y(0), {}
    

    【讨论】:

      猜你喜欢
      • 2013-10-15
      • 1970-01-01
      • 1970-01-01
      • 2016-04-10
      • 2012-12-11
      • 1970-01-01
      • 1970-01-01
      • 2011-04-13
      • 1970-01-01
      相关资源
      最近更新 更多