我维护一些遗留的 C 代码,在很多地方都有像 int a[32];
这样的小数组,后面跟着一个 memset(a, 0, sizeof a);
来进行零初始化。
我想将其重构成 int a[32] = {0};
的形式,并删除 memset。
问题是:使用零初始化器通常比调用 memset 更快吗?
我维护一些遗留的 C 代码,在很多地方都有像 int a[32];
这样的小数组,后面跟着一个 memset(a, 0, sizeof a);
来进行零初始化。
我想将其重构成 int a[32] = {0};
的形式,并删除 memset。
问题是:使用零初始化器通常比调用 memset 更快吗?
memset()
更差。这取决于您的编译器。它不应该比调用memset()
慢(因为调用memset()
是编译器提供的一种选项)。
与命令式地覆盖数组相比,初始化器更易读;如果元素类型更改为某些情况下不需要全零位的内容,则初始化器也能很好地适应。
作为一个实验,让我们看看GCC对此做了什么:
#include <string.h>
int f1()
{
int a[32] = {0};
return a[31];
}
int f2()
{
int a[32];
memset(a, 0, sizeof a);
return a[31];
}
gcc -S -std=c11
进行编译,结果如下:f1:
.LFB0:
.file 1 "40786375.c"
.loc 1 4 0
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $8, %rsp
.loc 1 5 0
leaq -128(%rbp), %rdx
movl $0, %eax
movl $16, %ecx
movq %rdx, %rdi
rep stosq
.loc 1 6 0
movl -4(%rbp), %eax
.loc 1 7 0
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
f2:
.LFB1:
.loc 1 10 0
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
addq $-128, %rsp
.loc 1 12 0
leaq -128(%rbp), %rax
movl $128, %edx
movl $0, %esi
movq %rax, %rdi
call memset@PLT
.loc 1 13 0
movl -4(%rbp), %eax
.loc 1 14 0
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
f1()
使用rep stosq
进行初始化,而f2()
则像C语言代码一样有函数调用。对于大型数组,memset()
可能具有更高效的矢量化实现,但对于像这样的小型数组,任何好处都可能被函数调用开销所抵消。如果我们将a
声明为volatile
,启用优化后(gcc -S -std=c11 -O3
),就可以看到发生了什么:
f1:
.LFB4:
.cfi_startproc
subq $16, %rsp
.cfi_def_cfa_offset 24
xorl %eax, %eax
movl $16, %ecx
leaq -120(%rsp), %rdi
rep stosq
movl 4(%rsp), %eax
addq $16, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
f2:
.LFB5:
.cfi_startproc
subq $16, %rsp
.cfi_def_cfa_offset 24
xorl %eax, %eax
movl $16, %ecx
leaq -120(%rsp), %rdx
movq %rdx, %rdi
rep stosq
movl 4(%rsp), %eax
addq $16, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
memset
和初始化都执行清零操作,也可能使用memset
在运行时执行初始化。您只需要进行(优化后的)构建并查看生成的代码即可。 - Some programmer dudememset()
,您的解决方案也更好,因为它更高级。此外,对于使用没有括号的sizeof a
的遗留代码印象非常深刻! - unwind