在c / c ++中初始化和清零数组的区别?

在c(或c ++)中,有什么区别

 char myarr [16] = {0x00};

 char myarr [16];
 memset(myarr,'\ 0',sizeof(myarr));

??

编辑:我问这个是因为在vc ++ 2005中结果是一样的..
编辑更多:和

  char myarr [16] = {0x00,}; 


也许可以得到更全面的答案,而不是含糊不清,因为下面的一些答案是指这种代码,即。 在关闭花括号之前放入逗号。 vc ++ 2005中的结果也是一样的。

重要的区别是第一个默认值以特定于元素的方式初始化数组:指针将接收空指针值 ,该不需要是0x00(如在所有位中为零),布尔值将为false 。 如果元素类型是一个不是所谓的POD (普通旧数据类型)的类类型,那么你只能做第一个,因为第二个只适用于最简单的情况(你没有虚拟的情况)函数 ,用户定义的构造函数等)。 相反,使用memset的第二种方法是将数组的所有元素设置为全位 – 零。 这并不总是你想要的。 例如,如果您的数组有指针,则它们不会被设置为空指针。

第一个将默认初始化数组的元素,第一个元素除外,它显式设置为0。 如果数组是本地的并且在堆栈上(即不是静态的),编译器内部通常会执行memset来清除数组。 如果arrays是非本地或静态的,则第一个版本可以更加高效 。 编译器可以在编译时将初始化程序放入生成的汇编程序代码中,使其根本不需要运行时代码。 或者,当程序以快速方式(即逐页)启动时,arrays可以布置在自动归零的部分(对于指针,如果它们具有全比特零表示)。

第二个在整个数组中明确地执行memset。 优化编译器通常会使用内联机器代码替换较小区域的memset,这些代码只使用标签和分支进行循环。

这是为第一种情况生成的汇编程序代码。 我的gcc内容没有太多优化,所以我们得到了一个真正的memset调用(堆栈顶部的16个字节总是分配,即使我们没有本地。$ n是寄存器号):

void f(void) { int a[16] = { 42 }; } sub $29, $29, 88 ; create stack-frame, 88 bytes stw $31, $29, 84 ; save return address add $4, $29, 16 ; 1st argument is destination, the array. add $5, $0, 0 ; 2nd argument is value to fill add $6, $0, 64 ; 3rd argument is size to fill: 4byte * 16 jal memset ; call memset add $2, $0, 42 ; set first element, a[0], to 42 stw $2, $29, 16 ; ldw $31, $29, 84 ; restore return address add $29, $29, 88 ; destroy stack-frame jr $31 ; return to caller 

来自C ++标准的血腥细节。 上面的第一个案例将默认初始化剩余的元素。

8.5

为类型为T的对象零初始化存储意味着:

  • 如果T是标量类型,则将存储设置为0(零) 转换为T的值 ;
  • 如果T是非联合类类型,则每个非静态数据成员和每个基类子对象的存储都是零初始化的;
  • 如果T是联合类型,则其第一个数据成员的存储是零初始化的;
  • 如果T是数组类型,则每个元素的存储都是零初始化的;
  • 如果T是引用类型,则不执行初始化。

默认初始化T类型的对象意味着:

  • 如果T是非POD类类型,则调用T的默认构造函数
  • 如果T是数组类型,则每个元素都是默认初始化的;
  • 否则,对象的存储被零初始化。

8.5.1

如果列表中的初始值设定项少于聚合中的成员,则未明确初始化的每个成员都应默认初始化 (8.5)。

ISO / IEC 9899:TC3 6.7.8,第21段:

如果括号括起的列表中的初始值设定项少于聚合的元素或成员,或者用于初始化已知大小的数组的字符串文字中的字符数少于数组中的元素,则聚合的其余部分应为隐式初始化与具有静态存储持续时间的对象相同。

具有静态存储持续时间的数组初始化为0 ,因此C99规范保证未显式初始化的数组元素也设置为0


在我对这篇文章的第一次编辑中,我在初始化后使用复合文字分配给数组时发出了一些废话。 这不起作用。 如果您真的想使用复合文字来设置数组的值,则必须执行以下操作:

 #define count(ARRAY) (sizeof(ARRAY)/sizeof(*ARRAY)) int foo[16]; memcpy(foo, ((int [count(foo)]){ 1, 2, 3 }), sizeof(foo)); 

使用一些宏魔术和非标准的__typeof__运算符,可以大大缩短:

 #define set_array(ARRAY, ...) \ memcpy(ARRAY, ((__typeof__(ARRAY)){ __VA_ARGS__ }), sizeof(ARRAY)) int foo[16]; set_array(foo, 1, 2, 3); 

也许char myarr[16]={0x00}; 这不是一个很好的例子,因为显式和隐式成员初始化都使用零,这使得更难以解释在那种情况下发生了什么。 我认为一个真实的例子,非零值可能更具说明性:

 /** * Map of characters allowed in a URL * * !, \, (, ), *, -, ., 0-9, AZ, _, az, ~ * * Allowed characters are set to non-zero (themselves, for easier tracking) */ static const char ALLOWED_IN_URL[256] = { /* 0 1 2 3 4 5 6 7 8 9*/ /* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 30 */ 0, 0, 0, '!', 0, 0, 0, 0, 0, '\'', /* 40 */ '(', ')', '*', 0, 0, '-', '.', 0, '0', '1', /* 50 */ '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, /* 60 */ 0, 0, 0, 0, 0, 'A', 'B', 'C', 'D', 'E', /* 70 */ 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', /* 80 */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', /* 90 */ 'Z', 0, 0, 0, 0, '_', 0, 'a', 'b', 'c', /* 100 */ 'd', 'e', 'f', 'g' , 'h', 'i', 'j', 'k', 'l', 'm', /* 110 */ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', /* 120 */ 'x', 'y', 'z', 0, 0, 0, '~', }; 

这是一个查找表,可以在对字符串进行URL编码时使用。 只有URL中允许的字符才会设置为非零值。 零表示不允许该字符,需要进行URL编码( %xx )。 请注意,该表突然以波形符后面的逗号结尾。 波浪符号后面的所有字符都不允许,因此应设置为零。 但是,我们不是编写更多的零来填充表中的256个条目,而是让编译器隐式地将其余条目初始化为零。

鉴于难以争议的事实= { 0 }memset(..., ..., ... sizeof ...)更具可读性,那么以下内容将不鼓励明确使用memset

在Visual Studio 2005中,为Windows Mobile编译完全优化的发布版本:

 ; DWORD a[10] = { 0 }; mov r3, #0 mov r2, #0x24 mov r1, #0 add r0, sp, #4 str r3, [sp] bl memset add r4, sp, #0 mov r5, #0xA ; DWORD b[10]; ; memset(b, 0, sizeof(b)); mov r2, #0x28 mov r1, #0 add r0, sp, #0x28 bl memset add r4, sp, #0x28 mov r5, #0xA 

几乎相同的。

在变量声明中定义初始值发生在与使用memset不同的地方。

对于前一种情况,零在二进制文件中以某种forms定义为零初始化内存(或者取决于您初始化的内容为非零),并且您希望加载器尊重它,绝对与C语言标准无关。 后者,使用memset取决于您也可以使用的C库。 我对图书馆更有信心。

我做了很多嵌入式代码,你学会避免在变量声明中初始化变量的坏习惯,而是在代码中进行。

对于标准操作系统,Linux,Windows等,变量声明期间的init很好,您将获得不可察觉的性能提升,但是如果您运行的是操作系统,那么您所处的平台就足够快,以至于看不到这种差异。

根据二进制类型,声明期间init的前一种情况可以使二进制文件更大。 这非常容易测试。 如上所述编译二进制文件,然后将数组大小从[16]更改为[16000],然后再次编译。 然后在没有= {0x00}的情况下编译并比较三个二进制大小。

对于大多数程序员将看到的大多数系统,没有function差异。 我推荐memset作为一种习惯。 尽管有什么标准说许多(如果不是大多数程序员在他们的职业生涯中从未见过的)编译器不喜欢那个init,因为元素的数量与大小不匹配。 大多数编译器即使声称也不符合标准。 相反,要养成避免捷径的好习惯或几乎任何适用于标准X的习惯,但不同于以前的标准M.(避免任何gee whiz编译器或基于标准的技巧)。

实际上他们是一样的。 第一种forms保证将整个类型初始化为0x00(例如,结构元素之间甚至是填充空间),这是从C90开始定义的。 不幸的是,gcc使用-Wmissing-field-initializers选项为第一个表单发出警告。 更多细节在这里:

http://www.pixelbeat.org/programming/gcc/auto_init.html