42

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

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

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

谢谢。

4

11 回答 11

52

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

于 2009-12-29T18:14:56.670 回答
50

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

于 2009-12-29T18:02:14.730 回答
24

零初始化应该是这样的:

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

于 2009-12-29T17:55:01.890 回答
13

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

当然,以上内容可能会被视为过于迂腐,因为在给定平台上活跃的附加标准和约定可能(并且肯定会)扩展memset.除非您真的必须这样做,否则请依赖任何其他标准和约定。C++ 语言(以及 C)提供了多种语言级别的功能,可让您使用正确类型的正确零值安全地初始化聚合对象。其他答案已经提到了这些功能。

于 2009-12-29T18:34:08.690 回答
7

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

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

此外,它可能不再有效。

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

于 2009-12-29T18:06:24.520 回答
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,从来没有遇到过问题。

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

于 2012-12-21T02:37:10.900 回答
0

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

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

于 2009-12-29T18:01:34.683 回答
0

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

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

于 2009-12-29T19:12:00.410 回答
0

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

于 2009-12-29T21:01:45.520 回答
0

简短的回答是使用初始大小为 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(虚拟函数)的类,它也会覆盖该数据。

于 2012-05-23T16:35:25.847 回答
-5

在 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。

于 2009-12-29T17:58:27.873 回答