当我注意到这一点时,我正在玩
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"
传递时,我会得到不同的值。当您将值传递给函数时,它们会分配在堆栈中,对吗?
TL:DR:使用
gcc -fno-builtin-strcmp
,这样 strcmp()
就不会被视为等同于 __builtin_strcmp()
。 禁用优化后,GCC 将只能在单个语句内进行常量传播,而不能跨语句进行常量传播。 实际的库版本减去不同的字符;编译时 eval 可能会将结果标准化为 1 / 0 / -1,这不是 ISO C 所要求或保证的。
您很可能会看到编译器优化的结果。如果我们在 godbolt 上使用 gcc 测试代码,优化级别为
-O0
,我们可以看到第一种情况它不会调用 strcmp
:
movl $-1, %esi #,
movl $.LC0, %edi #,
movl $0, %eax #,
call printf #
由于您使用常量作为strcmp的参数,编译器能够执行常量折叠并在编译时调用编译器内部并生成
-1
,而不必在运行时调用strcmp
-time 在标准库中实现,并且将具有不同的实现,然后可能更简单的编译时间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 #
打开更多优化选项后,优化器能够确定
a
和 b
也指向编译时已知的常量,并且还可以在编译时计算这种情况下 strcmp
的结果。
我们可以通过使用
-fno-builtin 标志进行构建并观察所有情况都会生成对
gcc
的调用来确认 strcmp
正在使用内置函数。
clang
略有不同,因为它根本不会使用 -O0
折叠,但会在 -O1
及以上折叠。
注意,任何负面结果都是完全一致的,我们可以通过查看 C99 标准草案部分来看到
7.21.4.2
strcmp 函数上面写着(强调我的):
int strcmp(const char *s1, const char *s2);
strcmp 函数返回大于、等于、或小于的整数 大于零,因此当 s1 指向的字符串大于时, 等于或小于 s2 指向的字符串。
technosurus 指出
strcmp
被指定为将字符串视为由 unsigned char 组成,这在 C99 的 7.21.1
下有所介绍,其中表示:
对于本子条款中的所有功能,每个字符应 解释为好像它具有 unsigned char 类型(因此每个 可能的对象表示是有效的并且具有不同的值)。
我认为您认为
strcmp
返回的值应该以某种方式取决于以函数规范未定义的方式传递给它的输入字符串。这是不正确的。例如,请参见 POSIX 定义:
http://pubs.opengroup.org/onlinepubs/009695399/functions/strcmp.html
完成后,如果 s1 指向的字符串大于、等于或小于 s2 指向的字符串,strcmp() 将分别返回大于、等于或小于 0 的整数。
这正是您所看到的。该实现不需要对“精确”返回值做出任何保证 - 只需根据情况小于零、等于零或大于零即可。