我正在审查一些代码,我看到有人做了
if (0 == strcmp(foo,""))
我很好奇,因为我认为这样做会更快
if (foo[0] == '\0')
这是正确的还是 strcmp 优化到足以使它们相同。
(我意识到即使有一些差异也会很小,但我认为您使用我的方法至少可以节省一些说明。)
你是对的:因为调用strcmp()
增加了堆栈管理和内存跳转到实际的 strcmp 指令,你只需检查字符串的第一个字节就可以获得一些指令。
出于好奇,您可以在此处查看 strcmp() 代码:http: //sourceware.org/git/ ?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hb=HEAD
(我以为代码会被填充#ifdef
和模糊__GNUSOMETHING
,但它实际上相当简单!)
strcmp() 是一个函数调用,因此具有函数调用开销。foo[0] 是直接访问数组,所以它显然更快。
在这种情况下,我认为使用 strcmp 没有任何优势。编译器足够聪明,可以优化它,但它不会比直接检查 '\0' 字节快。这个的实现者可能选择了这个结构,因为他认为它更具可读性,但我认为在这种情况下这是一个品味问题。虽然我会写检查有点不同,因为这是似乎最常用于检查空字符串的习语:
if( !*str )
和
if( *str )
检查非空字符串。
+1 到 Gui13 以提供 gcc stdlib strcmp 源的链接(http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hb =头)!
你是对的,strcmp 永远不会比直接比较快[1],但问题是,编译器会优化它吗?我不敢尝试测量它,但我对它的简单程度感到惊喜。我的示例代码是(省略标题):
bool isEmpty(char * str) {
return 0==std::strcmp(str,"");
}
bool isEmpty2(char * str) {
return str[0]==0;
}
我尝试编译它,首先使用gcc -S -o- emptystrcmptest.cc
然后使用gcc -S -O2 -o- emptystrcmptest.cc
. 令我惊喜的是,虽然我不能很好地阅读程序集,但未优化的版本清楚地显示了差异,优化的版本清楚地显示了两个函数产生了相同的程序集。
所以,我想说,一般来说,担心这种优化水平是没有意义的。
如果您正在为嵌入式系统使用编译器并且知道它不能处理这种简单的优化(或者根本没有标准库),请使用手工编码的特殊情况版本。
如果您正常编码,请使用更具可读性的版本(恕我直言,可能是 strcmp 或 strlen 或 [0]==0 取决于上下文)。
如果您正在编写高效的代码,您希望每秒被调用数千或数百万次,(a)实际上更有效的测试和(b)如果可读版本实际上太慢,请尝试编写可以编译为更好的组装。
与gcc -S -o- emptystrcmptest.cc
:
.file "emptystrcmptest.cc"
.section .rdata,"dr"
LC0:
.ascii "\0"
.text
.align 2
.globl __Z7isEmptyPc
.def __Z7isEmptyPc; .scl 2; .type 32; .endef
__Z7isEmptyPc:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $LC0, 4(%esp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call _strcmp
movl %eax, -4(%ebp)
cmpl $0, -4(%ebp)
sete %al
movzbl %al, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
leave
ret
.align 2
.globl __Z8isEmpty2Pc
.def __Z8isEmpty2Pc; .scl 2; .type 32; .endef
__Z8isEmpty2Pc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
popl %ebp
ret
emptystrcmptest.cc:10:2: warning: no newline at end of file
.def _strcmp; .scl 2; .type 32; .endef
与gcc -S -O2 -o- emptystrcmptest.cc
:
.file "emptystrcmptest.cc"
emptystrcmptest.cc:10:2: warning: no newline at end of file
.text
.align 2
.p2align 4,,15
.globl __Z7isEmptyPc
.def __Z7isEmptyPc; .scl 2; .type 32; .endef
__Z7isEmptyPc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
ret
.align 2
.p2align 4,,15
.globl __Z8isEmpty2Pc
.def __Z8isEmpty2Pc; .scl 2; .type 32; .endef
__Z8isEmpty2Pc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
ret
[1] 尽管要小心——在比对零进行直接测试更复杂的情况下,库和编译器代码通常会比手工编写的代码更好。
一个好的优化编译器可能会优化掉函数调用,然后从内联函数中消除循环。你的方法不可能更慢,尽管它有可能是相同的速度。
它显然会更快,如果您打算继续使用它,可能值得将您自己的代码放入内联函数甚至宏中:
int isEmpty(const char *string)
{
return ! *string;
}
int isNotEmpty(const char *string)
{
return *string;
}
int isNullOrEmpty(const char *string)
{
return string == NULL || ! *string;
}
int isNotNullOrEmpty(const char *string)
{
return string != NULL && *string;
}
并让编译器为您优化。无论如何,strcmp
需要最终检查,'\0'
所以你总是至少等于它。(老实说,我可能会让编译器优化上述内容的内部共享,例如,isEmpty
可能只是翻转isNotEmpty
)
对数组的访问在执行时间中是 1 阶的,因此它比函数快。
这是尽可能地进行微优化,但我想如果您在索引 foo 之前添加了一个空检查(除非您知道它永远不会为空),它在技术上会节省函数调用的开销。