【问题标题】:strcmp for empty stringstrcmp 用于空字符串
【发布时间】:2011-09-06 09:10:28
【问题描述】:

我正在审查一些代码,我看到有人这样做了

if (0 == strcmp(foo,""))

我很好奇,因为我认为这样做会更快

if (foo[0] == '\0')

这是正确的还是 strcmp 优化到足以使它们相同。

(我意识到即使有一些差异也会很小,但我认为您使用我的方法至少可以节省一些指令。)

【问题讨论】:

  • 你质疑这种模式是对的,我更喜欢你的方式。
  • @Jamie Wong:如果字符串是 1Meg 长,那么您必须检查 100 万字节才能找到终止的 null;当您只对字符串包含零个或多个零个字符的情况感兴趣时,这是一个相当大的损失。
  • @TokenMacGuy 啊。确实如此。 +1
  • 您的方式总是会更快。即使您忽略 strcmp 具有调用开销的事实,它最终也必须执行相同的内存访问。因此,它只能是相等或较慢的速度。
  • @Mike:我认为问题的精神更多地与 strcmp 可能比直接数组访问更明智的情况有关;缓冲区溢出等等。我认为答案是,在这种情况下,没有。

标签: c strcmp


【解决方案1】:

你是对的:因为调用 strcmp() 会增加堆栈管理和内存跳转到实际的 strcmp 指令,您只需检查字符串的第一个字节即可获得一些指令。

出于好奇,您可以在此处查看 strcmp() 代码:http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hb=HEAD

(我原以为代码会被#ifdef 填充而晦涩的__GNUSOMETHING,但实际上相当简单!)

【讨论】:

    【解决方案2】:

    strcmp() 是一个函数调用,因此具有函数调用开销。 foo[0] 是直接访问数组,所以明显更快。

    【讨论】:

      【解决方案3】:

      我认为在这种情况下使用 strcmp 没有任何好处。编译器很聪明,可以优化掉它,但它不会比直接检查 '\0' 字节快。这个的实现者可能选择了这个结构,因为他认为它更具可读性,但我认为在这种情况下这是一个品味问题。虽然我会写检查有点不同,因为这是似乎最常用于检查空字符串的习语:

      if( !*str )
      

      if( *str )
      

      检查非空字符串。

      【讨论】:

      • 是的我更喜欢这样对于 '\0' 或 0
      【解决方案4】:

      +1 到 Gui13 以提供 gcc stdlib strcmp 源的链接 (http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h= bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hb=HEAD)!

      你说得对,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] 尽管要小心——在比对零进行直接测试更复杂的情况下,库和编译器代码通常会比手工编写的代码更好。

      【讨论】:

      • +1 @Jack 这为@Brandon Moretz 的评论提供了极大的信任。好帖子。就纯粹的可读性而言,我仍然不确定我更喜欢哪个,但这绝对是过早优化的一个好点。
      【解决方案5】:

      一个好的优化编译器可能会优化掉函数调用,然后从内联函数中消除循环。您的方法不可能更慢,尽管它可能会保持相同的速度。

      【讨论】:

        【解决方案6】:

        对数组的访问在执行时间上是 1 阶的,因此它比函数快。

        【讨论】:

          【解决方案7】:

          这是微优化,但我想如果你在索引 foo 之前添加一个空检查(除非你知道它永远不会为空),从技术上讲,它会节省函数调用的开销。

          【讨论】:

          • 我认为你没有抓住重点。 OP 不是检查空指针,而是检查零长度字符串。执行直接检查和strcmp 调用从来没有任何目的,因为效果是相同的。
          • @R 我相信你应该重新阅读我的回复。我只是说他应该在盲目索引指针之前对指针执行空检查。我没说调用 strcmp。
          【解决方案8】:

          它显然会更快,如果您打算继续使用它,可能值得将您自己的代码放入内联函数甚至宏中:

          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

          【讨论】:

          • 我很确定确定f() == !a() 远远超出任何优化器的能力。
          • @mike 正确,但它仍然可以将调用优化为内联,而不是将相同的指针压入堆栈。这也可以防止潜在的“非标准”代码乱扔代码库。
          猜你喜欢
          • 2015-05-10
          • 1970-01-01
          • 1970-01-01
          • 2022-11-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多