【问题标题】:Should C++ programmer avoid memset?C++ 程序员应该避免 memset 吗?
【发布时间】:2010-12-30 20:39:39
【问题描述】:

听说c++程序员应该避免memset,

class ArrInit {
    //! int a[1024] = { 0 };
    int a[1024];
public:
    ArrInit() {  memset(a, 0, 1024 * sizeof(int)); }
};

所以考虑到上面的代码,如果你不使用 memset,你怎么能让一个 [1..1024] 填充零?C++ 中的 memset 有什么问题?

谢谢。

【问题讨论】:

  • 你能给出你认为不应该在 C++ 中做 memset 的原因吗?我不知道为什么做 memset 会导致 C++ 出现任何问题。如果我错了,请纠正我。谢谢!
  • 他可能是在“不要使用 memset 将类对象归零”的上下文中听到的。
  • @Jay:他们上面是好的。但是使用 memset 将类对象本身(不仅仅是单个成员)归零并不是一个好主意。如果对象包含具有构造函数(进行一些初始化)的成员,这尤其有问题。
  • 顺便说一句,它是 [0..1023],而不是 [1..1024]。
  • 我建议不要使用 C 样式数组,而是使用向量。在这种情况下,您可以将构造函数替换为 ArrInit() : a(1024, 0 ) {},这将删除 memset 并使您的类在风格上可以说是更“C++”。

标签: c++ initialization memset


【解决方案1】:

零初始化应该是这样的:

class ArrInit {
    int a[1024];
public:
    ArrInit(): a() { }
};

关于使用 memset,有几种方法可以使使用更加健壮(与所有此类函数一样):避免对数组的大小和类型进行硬编码:

memset(a, 0, sizeof(a));

对于额外的编译时检查,还可以确保a 确实是一个数组(所以sizeof(a) 有意义):

template <class T, size_t N>
size_t array_bytes(const T (&)[N])  //accepts only real arrays
{
    return sizeof(T) * N;
}

ArrInit() { memset(a, 0, array_bytes(a)); }

但对于非字符类型,我想你会用它来填充的唯一值是 0,并且零初始化应该已经以一种或另一种方式可用。

【讨论】:

  • 如果想用非零初始化数组怎么办?
  • 您可以在大括号内放入任何您想要的值(例如 ArrInit(): a() {5}),它将使用该值初始化数组。
  • 您确实意识到我所要做的就是将您示例中的int 更改为具有虚函数的某个类,您的代码很可能会清除 vptr,不是吗?您正在解释如何以更安全的方式引发灾难。
  • @Pace:不,你会得到一个语法错误。这些大括号是分隔构造函数主体的大括号。即使使用实际的数组初始化语法:“int a[1024] = { 5 };”只有你列出的元素会被初始化,所以在这个例子中,只有 a[0] 会是 5,而不是整个数组。
【解决方案2】:

在 C++ 中,您应该使用 new。在您的示例中使用简单数组的情况下,使用它并没有真正的问题。但是,如果您有一个类数组并使用 memset 对其进行初始化,那么您将无法正确构造这些类。

考虑一下:

class A {
    int i;

    A() : i(5) {}
}

int main() {
    A a[10];
    memset (a, 0, 10 * sizeof (A));
}

每个元素的构造函数不会被调用,所以成员变量 i 不会被设置为 5。如果你使用 new 代替:

 A a = new A[10];

数组中的每个元素都会调用其构造函数,并且 i 将被设置为 5。

【讨论】:

  • 我错过了关于将其初始化为零的问题,并专注于 memset 和 new 之间的区别。
  • @Casey:A a[1] 在我的 g++ 编译器中确实调用了构造函数,并且成员变量 i 将设置为 5。
  • A a[10] = new A[10]; 不是有效的 C++。您似乎将 C++ 与另一种语言混淆了。
  • 当然在A a[10] 中,A 的构造函数会为创建的十个实例中的每一个调用。这个答案应该被删除。
【解决方案3】:

您的代码很好。我认为在 C++ 中 memset 是危险的唯一一次是当您执行以下操作时:
YourClass instance; memset(&amp;instance, 0, sizeof(YourClass);

我相信它可能会将编译器创建的实例中的内部数据清零。

【讨论】:

    【解决方案4】:

    问题不在于在内置类型上使用 memset(),而是在类(也称为非 POD)类型上使用它们。这样做几乎总是会做错事,而且经常会做致命的事——例如,它可能会践踏虚函数表指针。

    【讨论】:

    • 在任何具有虚函数的类上使用 memset 都可能不好。
    • @Otto:因为 sizeof(class) 会将虚函数表指针视为一个数据成员。
    • 或者在任何包含非pod类型的类上,比如字符串
    • memset 在用于某些 POD 类型(如指针和浮点类型)时也会出现问题。将所有字节设置为 0 不会将指向 NULL 或浮点类型的指针可移植地设置为 0.0。
    • @toto:POD 代表“普通旧数据”。本质上,它指的是内置类型或内置类型的结构或联合。如果你可以用 C 声明它,它可能是 C++ 中的 POD。
    【解决方案5】:

    这是“不好的”,因为您没有实现您的意图。

    您的意图是将数组中的每个值设置为零,而您所编程的是将原始内存区域设置为零。是的,这两件事具有相同的效果,但更清楚的是只需编写代码将每个元素归零。

    而且,它可能不会更有效。

    class ArrInit
    {
    public:
        ArrInit();
    private:
        int a[1024];
    };
    
    ArrInit::ArrInit()
    {
        for(int i = 0; i < 1024; ++i) {
            a[i] = 0;
        }
    }
    
    
    int main()
    {
        ArrInit a;
    }
    

    使用 Visual c++ 2008 32 位打开优化编译此循环编译为 -

    ; Line 12
        xor eax, eax
        mov ecx, 1024               ; 00000400H
        mov edi, edx
        rep stosd
    

    这几乎正是 memset 可能编译成的样子。但是,如果您使用 memset,则编译器没有执行进一步优化的余地,而通过编写您的意图,编译器可能会执行进一步的优化,例如注意到每个元素在使用之前被设置为其他内容,因此初始化可以优化出来,如果你使用了 memset,它可能不会那么容易。

    【讨论】:

    • 我当然理解默认初始化程序也会将数组归零,所以这只是一个示例,但重点是,实现您的要求,在这种情况下是将每个数组元素设置为零,而不是其他方法来实现结果,除非它是您可以实现其他要求(例如性能)的唯一方法
    • Which is pretty much exactly what the memset would likely compile to anyway. 不,memset 可以比简单的 rep stosd 复杂和高效得多
    【解决方案6】:

    在 C++ 中,std::fillstd::fill_n 可能是更好的选择,因为它是通用的,因此可以对对象和 POD 进行操作。但是,memset 对原始字节序列进行操作,因此不应用于初始化非 POD。无论如何,如果类型是 POD,std::fill 的优化实现可能会在内部使用特化来调用 memset

    【讨论】:

    • 我忘记了 std::fill 所以我对此 +1。是的,有一个专门设计用于填充容器的 c++ 函数,所以请使用它!
    【解决方案7】:

    C++ 中 memset 的问题与 C 中 memset 的问题基本相同。memset 用物理零位模式填充内存区域,而实际上在几乎 100% 的情况下您需要用相应类型的逻辑零值填充数组。在 C 语言中,memset 仅保证为整数类型正确初始化内存(并且它对 all 整数类型的有效性,而不是 char 类型,是添加到 C 语言规范中的相对较新的保证)。不保证将任何浮点值正确设置为零,也不保证产生正确的空指针。

    当然,以上内容可能会被视为过于迂腐,因为在给定平台上有效的附加标准和约定可能(而且肯定会)扩展memset 的适用性,但我仍然建议遵循奥卡姆剃刀这里的原则:不要依赖任何其他标准和约定,除非你真的必须这样做。 C++ 语言(以及 C)提供了多种语言级别的功能,可让您使用正确类型的正确零值安全地初始化聚合对象。其他答案已经提到了这些功能。

    【讨论】:

    • 物理零和逻辑零有什么区别?
    • @Adil 物理零是内存中明确的实际“全零”位模式。逻辑零是 [可能非零] 位模式,被语言(在我们的例子中是 C 或 C++)解释为某种类型的零值。
    【解决方案8】:

    memset 应用于类时除了不好之外,还容易出错。很容易让参数乱序,或者忘记sizeof 部分。代码通常会在编译时出现这些错误,并悄悄地做错事。该错误的症状可能要到很久以后才会出现,因此很难追踪。

    memset 对于很多普通类型也有问题,比如指针和浮点。一些程序员将所有字节设置为 0,假设指针为 NULL,浮点数为 0.0。这不是一个可移植的假设。

    【讨论】:

    • 将指针和浮点数设置为二进制零通常是可行的,但我不想养成这种习惯。尽管如此,IEEE 浮点标准变得越来越根深蒂固,并将所有位零解释为 0.0。
    • @David:是的,它通常可以工作,但有一天你会在一个平台上,它不能。
    【解决方案9】:

    没有真正的理由不使用它,除了人们指出无论如何都不会使用它的少数情况,但是除非你正在填充内存保护或其他东西,否则使用它也没有真正的好处。

    【讨论】:

      【解决方案10】:

      简短的回答是使用初始大小为 1024 的 std::vector。

      std::vector< int > a( 1024 ); // Uses the types default constructor, "T()".
      

      “a”的所有元素的初始值为 0,因为 std::vector(size) 构造函数(以及 vector::resize)复制所有元素的默认构造函数的值。对于内置类型(也称为内在类型或 POD),您可以保证初始值为 0:

      int x = int(); // x == 0
      

      这将允许“a”使用的类型轻松更改,甚至更改为类的类型。

      大多数将 void 指针 (void*) 作为参数的函数,例如 memset,都不是类型安全的。通过这种方式,忽略对象的类型会删除对象倾向于依赖的所有 C++ 样式语义,例如构造、销毁和复制。 memset 对类做出假设,这违反了抽象(不知道或关心类中的内容)。虽然这种违规并不总是很明显,尤其是对于固有类型,但它可能会导致难以定位错误,尤其是在代码库增长和易手时。如果 memset 类型是具有 vtable(虚拟函数)的类,它也会覆盖该数据。

      【讨论】:

        【解决方案11】:

        这是一个旧线程,但这里有一个有趣的转折:

        class myclass
        {
          virtual void somefunc();
        };
        
        myclass onemyclass;
        
        memset(&onemyclass,0,sizeof(myclass));
        

        效果很好!

        然而,

        myclass *myptr;
        
        myptr=&onemyclass;
        
        memset(myptr,0,sizeof(myclass));
        

        确实将虚拟变量(即上面的 somefunc())设置为 NULL。

        鉴于 memset 比将大型班级中的每个成员都设置为 0 快得多,我已经做了上面的第一个 memset 很久了,从来没有遇到过问题。

        所以真正有趣的问题是它是如何工作的?我想编译器实际上开始将零设置在虚拟表之外......有什么想法吗?

        【讨论】:

        • “它不会崩溃或做任何我可以看到的明显错误的事情”和“它有效”是非常不一样的东西。 AFAICT 上面的两个代码 sn-ps 是相同的,但是一旦你开始调用未定义的行为,所有的赌注都没有了。执行上述任一操作的程序很可能仅(似乎)在非常特定的情况下工作,并且在其他情况下会严重中断(例如,在不同的编译器、操作系统或 CPU 架构上)
        猜你喜欢
        • 2014-10-24
        • 2012-10-14
        • 1970-01-01
        • 2013-10-25
        • 2012-02-25
        • 2014-10-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多