C ++程序员应该避免memset?
我听到一个说c ++程序员应该避免memset,
class ArrInit { //! int a[1024] = { 0 }; int a[1024]; public: ArrInit() { memset(a, 0, 1024 * sizeof(int)); } };
所以考虑上面的代码,如果你不使用memset,你怎么能做一个[1..1024]填零?memset在C ++中有什么错误?
谢谢。
这个问题不是在内buildtypes上使用memset(),而是在类(又名非POD)types上使用它们。 这样做几乎总会做错误的事情,经常做致命的事情 – 例如,它可能会践踏虚拟函数表指针。
在C ++ std::fill
或std::fill_n
可能是一个更好的select,因为它是通用的,因此可以对对象和POD进行操作。 但是, memset
在原始字节序列上运行,因此不应用于初始化非POD。 无论如何,如果types是POD, std::fill
优化实现可能会在内部使用专门化来调用memset
。
零初始化应该是这样的:
class ArrInit { int a[1024]; public: ArrInit(): a() { } };
至于使用memset,有几种方法使用法更加健壮(如同所有这些函数一样):避免硬编码数组的大小和types:
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)); }
但是对于非字符types,我想可以用它来填充的唯一值是0,并且零初始化应该已经可以用这种或那种方式。
这是“坏”,因为你没有实现你的意图。
您的目的是将数组中的每个值设置为零,并且您已经编程的是将原始内存区域设置为零。 是的,这两个东西具有相同的效果,但是只需编写代码来清零每个元素就会更清楚。
而且,它可能不会更有效率。
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的话,它可能做不到那么容易。
memset
在C ++中出了什么问题,与memset
在C. memset
中的错误大部分是相同的, memset
填充了物理零位模式的内存区域,而实际上,在几乎100%的情况下,您需要用逻辑零值对应的types。 在C语言中, memset
只保证为整数types正确初始化内存(对于所有整数types来说,它的有效性与chartypes相反,是对C语言规范的一个相对较新的保证)。 不能保证将任何浮点值设置为零,但不能保证产生正确的空指针。
当然,上述可能被认为是过于迂腐,因为在特定平台上活动的附加标准和惯例可能(并且肯定会)扩展memset
的适用性,但我仍然build议遵循Occam的剃刀原则:除非你真的必须依靠任何其他的标准和惯例。 C ++语言(以及C语言)提供了几种语言级别的function,可以让您安全地初始化具有适当types的适当零值的聚合对象。 其他答案已经提到这些function。
这是一个旧的线程,但这是一个有趣的转折:
class myclass { virtual void somefunc(); }; myclass onemyclass; memset(&onemyclass,0,sizeof(myclass));
完美的作品!
然而,
myclass *myptr; myptr=&onemyclass; memset(myptr,0,sizeof(myclass));
的确将虚拟(即上面的somefunc())设置为NULL。
鉴于memset的速度比设置为大类中的每个成员都要快得多,所以我一直在做上面的第一个memset,从来没有遇到过问题。
所以真正有趣的问题是它是如何起作用的? 我想,编译器实际上开始设置零的BEYOND虚拟表…任何想法?
你的代码很好。 我认为memset是危险的C ++唯一的一次是当你做一些事情的时候:
YourClass instance; memset(&instance, 0, sizeof(YourClass);
YourClass instance; memset(&instance, 0, sizeof(YourClass);
我相信它可能会将编译器创build的实例中的内部数据清零。
当应用于类时,除了糟糕之外, memset
也容易出错。 乱序或者忘记sizeof
部分是非常容易的。 代码通常会编译这些错误,并悄悄地做错误的事情。 该错误的症状可能不会performance出来,直到很久以后,这使得难以追踪。
memset
也是很多普通types的问题,比如指针和浮点。 一些程序员将所有字节设置为0,假设指针将为NULL,浮点数为0.0。 这不是一个便携式的假设。
没有什么真正的理由不使用它,除非less数人指出没有人会使用它,但是除非你正在填写保护程序或什么东西,否则没有任何真正的好处。
简短的答案是使用初始大小为1024的std :: vector。
std::vector< int > a( 1024 ); // Uses the types default constructor, "T()".
因为std :: vector(size)构造函数(以及vector :: resize)复制了所有元素的默认构造函数的值,所以“a”的所有元素的初始值都是0。 对于内置types(又名内在types或POD),保证初始值为0:
int x = int(); // x == 0
这样可以让“a”的types用最小的改动来改变,甚至是一个类的改变。
大多数将void指针(void *)作为参数的函数(如memset)都不是types安全的。 忽略对象的types,这样就删除了所有依赖的C ++样式语义对象,如构造,销毁和复制。 memset对一个类进行假设,这违反了抽象(不知道或关心类内部的东西)。 虽然这种违规并不总是立即显而易见的,尤其是对于内部types而言,它可能导致难以定位错误,特别是随着代码库的增长和易手。 如果memset的types是一个具有虚表(虚函数)的类,它也会覆盖这些数据。
在C ++中,你应该使用新的。 在你的例子中使用简单数组的情况下,使用它没有实际的问题。 但是,如果你有一个类的数组,并用memset来初始化它,那么你就不能正确地构造类。
考虑一下:
class A { int i; A() : i(5) {} } int main() { A a[10]; memset (a, 0, 10 * sizeof (A)); }
每个这些元素的构造函数都不会被调用,所以成员variablesi将不会被设置为5.如果您使用new来代替:
A a = new A[10];
比数组中的每个元素都有其构造函数调用,我将被设置为5。