C ++程序员应该避免memset吗?

时间:2009-12-29 17:52:09

标签: c++ initialization memset

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

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

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

感谢。

11 个答案:

答案 0 :(得分:49)

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

答案 1 :(得分:44)

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

答案 2 :(得分:23)

零初始化应如下所示:

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,零初始化应该已经以这种或那种方式可用。

答案 3 :(得分:10)

C ++中memset的错误与C中memset的错误大致相同。memset用物理零位模式填充内存区域,而实际上是100您需要使用相应类型的逻辑零值填充数组的百分比。在C语言中,memset仅保证为整数类型正确初始化内存(并且所有整数类型的有效性,而不仅仅是char类型,是C中添加的相对较新的保证。语言规范)。不能保证将任何浮点值正确设置为零,不能保证产生正确的空指针。

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

答案 4 :(得分:8)

这是“糟糕的”,因为你没有实现你的意图。

您的目的是将数组中的每个值设置为零,并且您编写的内容是将原始内存区域设置为零。是的,这两件事具有相同的效果,但只需将代码编写为零,就可以更清楚了。

此外,它可能没有效率。

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,它可能不会那么容易。

答案 5 :(得分:1)

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

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多年,从来没有遇到过问题。

所以真正有趣的问题是它是如何运作的?我想编译器实际上开始设置零的BEYOND虚拟表...任何想法?

答案 6 :(得分:0)

你的代码很好。我认为在C ++中唯一一个memset很危险的时候是你做的事情: YourClass instance; memset(&instance, 0, sizeof(YourClass);

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

答案 7 :(得分:0)

除了应用于课程时的不良之外,memset也容易出错。很容易让参数无序,或忘记sizeof部分。代码通常会编译出这些错误,并悄悄地做错了。这个错误的症状可能要到很晚才会显现出来,因此难以追踪。

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

答案 8 :(得分:0)

没有真正的理由不使用它,除了少数情况下人们指出没有人会使用它,但除非你填充memguards或其他东西,否则使用它没有任何实际好处。

答案 9 :(得分:0)

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

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

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

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

这将允许“a”使用的类型以最小的麻烦改变,甚至是类的改变。

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

答案 10 :(得分:-4)

在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];

比数组中的每个元素都将调用其构造函数,并且我将设置为5.