使用字符串指针或字符串字面量作为参数时,strcmp() 返回值不一致的问题。

33

当我在使用 strcmp 时,我注意到了这个问题,以下是代码:

#include <string.h>
#include <stdio.h>

int main(){

    //passing strings directly
    printf("%d\n", strcmp("ahmad", "fatema"));

    //passing strings as pointers 
    char *a= "ahmad";
    char *b= "fatema";
    printf("%d\n",strcmp(a,b));

    return 0;

}

输出结果为:

-1
-5

strcmp应该是一样的吧?我传递字符串"ahmad"或者char* a = "ahmad"时为什么会得到不同的值?当你把值传递给函数时,它们分配在其堆栈中,对吗?


1
这对我很有效! - πάντα ῥεῖ
2
@Barmar 我在我的代码中包含了 <string.h>,而且 OP 是正确的。 - Iharob Al Asimi
11
行为正确,两种情况下返回值均为负数。问题出在哪里? - davmac
17
根据该函数的定义,这两个不同的返回值具有完全相同的含义。 - davmac
3
з”ұдәҺж ҮеҮҶ规е®ҡstrcmpзҡ„иҝ”еӣһеҖјеҸӘдҝқиҜҒдёәиҙҹж•°гҖҒйӣ¶жҲ–жӯЈж•°пјҢеӣ жӯӨдҪ е”ҜдёҖеҸҜд»ҘзңҹжӯЈдҫқиө–strcmp(a,b) == strcmp(c,d)жҲҗз«Ӣзҡ„жғ…еҶөжҳҜдёӨж¬ЎжҜ”иҫғйғҪиҝ”еӣһйӣ¶гҖӮ - David Hammen
显示剩余13条评论
2个回答

48

TL:DR: 使用gcc -fno-builtin-strcmp,这样strcmp()不会被视为等同于__builtin_strcmp()禁用优化, GCC只能在单个语句内进行常量传播,而不能跨语句进行。 实际的库版本会减去不同的字符;编译时评估可能会将结果规范化为1/0/-1,这并不是ISO C所要求或保证的。



你可能看到的是编译器优化的结果。如果我们在 godbolt 上使用 -O0 优化等级来 测试代码, 我们可以看到在第一个情况下它不会调用strcmp函数。
movl    $-1, %esi   #,
movl    $.LC0, %edi #,
movl    $0, %eax    #,
call    printf  #

由于您在strcmp中使用常量作为参数,编译器能够执行常量折叠并在编译时调用编译器内置函数,然后生成-1,而不是必须在运行时调用在标准库中实现的strcmp,该实现与可能更简单的编译时strcmp不同。
在第二种情况下,它确实生成对strcmp的调用:
call    strcmp  #
movl    %eax, %esi  # D.2047,
movl    $.LC0, %edi #,
movl    $0, %eax    #,
call    printf  #

这与gcc有一个strcmp内置函数的事实一致,这也是gcc在常量折叠期间使用的内容。
如果我们使用 使用-O1优化级别或更高级别进行测试 gcc将折叠两种情况,结果都为-1
movl    $-1, %esi   #,
movl    $.LC0, %edi #,
xorl    %eax, %eax  #
call    printf  #
movl    $-1, %esi   #,
movl    $.LC0, %edi #,
xorl    %eax, %eax  #
call    printf  #

当优化选项打开时,优化器能够确定 ab 也指向编译时已知的常量,并且还可以在编译时计算此情况下 strcmp 的结果。

我们可以通过使用 -fno-builtin flag 进行构建并观察所有情况下都会生成对 strcmp 的调用来确认 gcc 是否正在使用内置函数。

clang 稍有不同,它在 -O0 下不进行折叠,但在 -O1 及以上进行折叠。

请注意,任何负面结果都是完全符合规范的,我们可以查看 C99 草案标准第 7.21.4.2 节 strcmp 函数,其中写道(我强调):

int strcmp(const char *s1, const char *s2);

The strcmp function returns an integer greater than, equal to, or less than zero, accordingly as the string pointed to by s1 is greater than, equal to, or less than the string pointed to by s2.

technosurus指出strcmp被规定为将字符串视为由unsigned char组成,这在C99下的7.21.1中有所涵盖,其内容如下:

对于本子句中的所有函数,每个字符都应被解释为具有类型unsigned char(因此每个可能的对象表示都是有效的且具有不同的值)。


1
虽然这很有趣,但对答案并不是非常重要。即使在两种情况下都调用strcmp,它也可以为相同的输入字符串返回不同的值(只要返回值都是相同的符号等)。 - davmac
11
@davmac,看起来原帖作者想知道为什么这两个值不同(尽管它们都是负数)。 - asimes
3
我的观点是,这只是为什么它们不同的解释的一部分。或者,如果您愿意,这是为什么它们可能不同的一个解释(请记住,不同的编译器、平台等可能会给出不同的结果)。但问题意味着提问者不理解为什么它们可以不同,并且假设相同的输入字符串必须始终产生相同的数值结果是错误的。 - davmac
1
@davmac,它们可以不同,但这样做会让人感到惊讶和不合逻辑。除非你有像“地狱中的混蛋编译器”™这样的东西... - glglgl
@technosaurus,你说得很好,我实际上最初使用了“-fno-builtin”进行验证,但我没有添加这个细节,当我有更多时间时,我可能会添加。最近,我写了一个自问自答的问题,涉及内置函数和常量表达式,所以我最近一直在思考类似的东西,你可以在这里看到它:是否符合规范的编译器扩展将非constexpr标准库函数视为constexpr? - Shafik Yaghmour
显示剩余5条评论

14

我认为你认为strcmp返回的值应该在某种方式上取决于传递给它的输入字符串,而这种方式在函数规范中没有定义。这是不正确的。例如,请参见POSIX的定义:

http://pubs.opengroup.org/onlinepubs/009695399/functions/strcmp.html

完成后,如果指向s1的字符串大于指向s2的字符串,则strcmp()将返回大于0的整数;如果相等,则返回0;如果小于,则返回小于0的整数。

这正是你所看到的内容。实现不需要对确切的返回值做出任何保证-只需要适当地小于零、等于零或大于零即可。


3
那么它返回一个随机的负数吗?我认为它应该是确定性的,需要解释观察到的行为。 - Iharob Al Asimi
8
在第二种情况下,结果不是“随机”的,它是由'f' - 'a'得出的,但这本身就是特定strcmp()的结果(尽管我怀疑实现在这方面是否有所不同)。第一个结果由Shafic的答案解释,并且取决于编译器(或编译器选项)。无论哪种方式,您都不能依赖除函数标准规范保证的结果之外的任何结果。 - Clifford
@iharob:返回一个随机的负数确实可以满足规范,但这不是这里发生的事情。 "观察行为的解释"是strcmp实现在不同场合返回不同的值-换句话说,观察到的行为取决于函数的实现。我们可以探讨各种原因,为什么实现可能会给出不同的数字结果(参见Shafik的答案),但我个人认为关键的教训是“不要对规范中未明确说明的行为做出假设”。 - davmac

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接