strcmp() 的不明确行为
Ambiguous behaviour of strcmp()
请注意,我已经检查了与此标题相关的问题,但在我看来它们与此问题无关。
最初我以为程序 1 和程序 2 会给我相同的结果。
//Program 1
char *a = "abcd";
char *b = "efgh";
printf("%d", strcmp(a,b));
//Output: -4
//Program 2
printf("%d", strcmp("abcd", "efgh"));
//Output: -1
我能发现的唯一区别是,在 program2 中我传递了字符串文字,而在程序中我传递了 char *
作为 strcmp()
函数的参数。
为什么这些看似相同的程序在行为上存在差异?
平台:Linux薄荷
编译器:g++
编辑:实际上程序1总是打印第一个不匹配字符的ascii码的差异,但是如果string2中第一个不匹配字符的ascii码大于string1 反之亦然。
确实令人惊讶 strcmp
returns 这些调用有 2 个不同的值,但它并不与 C 标准不兼容:
strcmp()
returns 如果第一个字符串按字典顺序在第二个字符串之前,则为负值。 -4和-1都是负值。
正如其他人所指出的,为不同的调用生成的代码是不同的:
- 编译器在第一个程序中生成对库函数的调用
- 编译器能够确定比较结果并为第二种情况生成显式结果
-1
,其中两个参数都是字符串文字。
为了执行此编译时评估,strcmp
必须在 <string.h>
中以微妙的方式定义,以便编译器可以确定程序引用 C 库的实现而不是替代方案那可能表现不同。在最近的 GNU libc 包含文件中追踪相应的原型有点困难,因为许多嵌套宏最终导致隐藏的原型。
请注意,gcc 和 clang 的更新版本将在这两种情况下执行优化,这可以在 Godbolt Compiler Explorer 上进行测试,但两者都没有将此优化与 printf
的优化相结合以生成偶数更紧凑的代码 puts("-1");
。他们似乎只对没有参数的字符串文字格式将 printf
转换为 puts
。
这是你的 C 代码:
int x1()
{
char *a = "abcd";
char *b = "efgh";
printf("%d", strcmp(a,b));
}
int x2()
{
printf("%d", strcmp("abcd", "efgh"));
}
这是为两个函数生成的汇编输出:
.LC0:
.string "abcd"
.LC1:
.string "efgh"
.LC2:
.string "%d"
x1:
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], OFFSET FLAT:.LC0
mov QWORD PTR [rbp-16], OFFSET FLAT:.LC1
mov rdx, QWORD PTR [rbp-16]
mov rax, QWORD PTR [rbp-8]
mov rsi, rdx
mov rdi, rax
call strcmp // the strcmp function is actually called
mov esi, eax
mov edi, OFFSET FLAT:.LC2
mov eax, 0
call printf
nop
leave
ret
x2:
push rbp
mov rbp, rsp
mov esi, -1 // strcmp is never called, the compiler
// knows what the result will be and it just
// uses -1
mov edi, OFFSET FLAT:.LC2
mov eax, 0
call printf
nop
pop rbp
ret
当编译器看到 strcmp("abcd", "efgh")
时它预先知道结果,因为它知道 "abcd"
在 "efgh"
.
之前
但是如果它看到 strcmp(a,b)
它不知道并因此生成 实际上 调用 strcmp
.
的代码
使用其他编译器或使用不同的编译器设置,情况可能会有所不同。至少在初学者的水平上,你真的不应该关心这些细节。
我相信(需要查看(和解释)机器代码)一个版本无需调用库中的代码即可运行(就好像您编写了 printf("%d", -1);
)。
请注意,我已经检查了与此标题相关的问题,但在我看来它们与此问题无关。
最初我以为程序 1 和程序 2 会给我相同的结果。
//Program 1
char *a = "abcd";
char *b = "efgh";
printf("%d", strcmp(a,b));
//Output: -4
//Program 2
printf("%d", strcmp("abcd", "efgh"));
//Output: -1
我能发现的唯一区别是,在 program2 中我传递了字符串文字,而在程序中我传递了 char *
作为 strcmp()
函数的参数。
为什么这些看似相同的程序在行为上存在差异?
平台:Linux薄荷 编译器:g++
编辑:实际上程序1总是打印第一个不匹配字符的ascii码的差异,但是如果string2中第一个不匹配字符的ascii码大于string1 反之亦然。
确实令人惊讶 strcmp
returns 这些调用有 2 个不同的值,但它并不与 C 标准不兼容:
strcmp()
returns 如果第一个字符串按字典顺序在第二个字符串之前,则为负值。 -4和-1都是负值。
正如其他人所指出的,为不同的调用生成的代码是不同的:
- 编译器在第一个程序中生成对库函数的调用
- 编译器能够确定比较结果并为第二种情况生成显式结果
-1
,其中两个参数都是字符串文字。
为了执行此编译时评估,strcmp
必须在 <string.h>
中以微妙的方式定义,以便编译器可以确定程序引用 C 库的实现而不是替代方案那可能表现不同。在最近的 GNU libc 包含文件中追踪相应的原型有点困难,因为许多嵌套宏最终导致隐藏的原型。
请注意,gcc 和 clang 的更新版本将在这两种情况下执行优化,这可以在 Godbolt Compiler Explorer 上进行测试,但两者都没有将此优化与 printf
的优化相结合以生成偶数更紧凑的代码 puts("-1");
。他们似乎只对没有参数的字符串文字格式将 printf
转换为 puts
。
这是你的 C 代码:
int x1()
{
char *a = "abcd";
char *b = "efgh";
printf("%d", strcmp(a,b));
}
int x2()
{
printf("%d", strcmp("abcd", "efgh"));
}
这是为两个函数生成的汇编输出:
.LC0:
.string "abcd"
.LC1:
.string "efgh"
.LC2:
.string "%d"
x1:
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], OFFSET FLAT:.LC0
mov QWORD PTR [rbp-16], OFFSET FLAT:.LC1
mov rdx, QWORD PTR [rbp-16]
mov rax, QWORD PTR [rbp-8]
mov rsi, rdx
mov rdi, rax
call strcmp // the strcmp function is actually called
mov esi, eax
mov edi, OFFSET FLAT:.LC2
mov eax, 0
call printf
nop
leave
ret
x2:
push rbp
mov rbp, rsp
mov esi, -1 // strcmp is never called, the compiler
// knows what the result will be and it just
// uses -1
mov edi, OFFSET FLAT:.LC2
mov eax, 0
call printf
nop
pop rbp
ret
当编译器看到 strcmp("abcd", "efgh")
时它预先知道结果,因为它知道 "abcd"
在 "efgh"
.
但是如果它看到 strcmp(a,b)
它不知道并因此生成 实际上 调用 strcmp
.
使用其他编译器或使用不同的编译器设置,情况可能会有所不同。至少在初学者的水平上,你真的不应该关心这些细节。
我相信(需要查看(和解释)机器代码)一个版本无需调用库中的代码即可运行(就好像您编写了 printf("%d", -1);
)。