ここにCコードがあり、gccでコンパイルしました
char *a="a";
char *d="d";
printf("%d\n", strcmp("a", "d"));
printf("%d\n", strcmp(a, "d"));
printf("%d\n", strcmp(a, d));
-O
出力でコンパイルすると
-1
-3
-1
なしでコンパイルすると-O
、出力は
-1
-3
-3
出力が異なる理由と のコードはstrcmp
何ですか?
出力が異なる理由
重要なのは、戻り値の符号(正、負、またはゼロ) だけだからです。strcmp()
+1 または -1 を返す必要はなく、一貫した値を返す必要もありません。strcmp()
最初と 3 番目のケースでは、コンパイラが への呼び出しを最適化し、戻り値の場所に -1 を入れていると思われます。2番目のケースでは、関数が実際に呼び出されていると思います。
strcmpのコードは何ですか?
最初の異なる文字の文字コード間の違いを返すように見えるという事実から推測すると、これはglibcのものだと思いますstrcmp()
:
int
strcmp (p1, p2)
const char *p1;
const char *p2;
{
register const unsigned char *s1 = (const unsigned char *) p1;
register const unsigned char *s2 = (const unsigned char *) p2;
unsigned char c1, c2;
do
{
c1 = (unsigned char) *s1++;
c2 = (unsigned char) *s2++;
if (c1 == '\0')
return c1 - c2;
}
while (c1 == c2);
return c1 - c2;
}
編集: @AndreyT は私を信じていないので、GCC 4.2 が私のために生成したアセンブリを次に示します (OS X 10.7.5 64 ビット Intel、デフォルトの最適化レベル - フラグなし):
.section __TEXT,__text,regular,pure_instructions
.globl _main
.align 4, 0x90
_main:
Leh_func_begin1:
pushq %rbp
Ltmp0:
movq %rsp, %rbp
Ltmp1:
subq $32, %rsp
Ltmp2:
leaq L_.str(%rip), %rax
movq %rax, -16(%rbp)
leaq L_.str1(%rip), %rax
movq %rax, -24(%rbp)
movl $-1, %ecx ; <- THIS!
xorb %dl, %dl
leaq L_.str2(%rip), %rsi
movq %rsi, %rdi
movl %ecx, %esi
movq %rax, -32(%rbp)
movb %dl, %al
callq _printf ; <- no call to `strcmp()` so far!
movq -16(%rbp), %rax
movq %rax, %rdi
movq -32(%rbp), %rsi
callq _strcmp ; <- strcmp()
movl %eax, %ecx
xorb %dl, %dl
leaq L_.str2(%rip), %rdi
movl %ecx, %esi
movb %dl, %al
callq _printf ; <- printf()
movq -16(%rbp), %rax
movq -24(%rbp), %rcx
movq %rax, %rdi
movq %rcx, %rsi
callq _strcmp ; <- strcmp()
movl %eax, %ecx
xorb %dl, %dl
leaq L_.str2(%rip), %rdi
movl %ecx, %esi
movb %dl, %al
callq _printf ; <- printf()
movl $0, -8(%rbp)
movl -8(%rbp), %eax
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
addq $32, %rsp
popq %rbp
ret
Leh_func_end1:
.section __TEXT,__cstring,cstring_literals
L_.str:
.asciz "a"
L_.str1:
.asciz "d"
L_.str2:
.asciz "%d\n"
.section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
EH_frame0:
Lsection_eh_frame:
Leh_frame_common:
Lset0 = Leh_frame_common_end-Leh_frame_common_begin
.long Lset0
Leh_frame_common_begin:
.long 0
.byte 1
.asciz "zR"
.byte 1
.byte 120
.byte 16
.byte 1
.byte 16
.byte 12
.byte 7
.byte 8
.byte 144
.byte 1
.align 3
Leh_frame_common_end:
.globl _main.eh
_main.eh:
Lset1 = Leh_frame_end1-Leh_frame_begin1
.long Lset1
Leh_frame_begin1:
Lset2 = Leh_frame_begin1-Leh_frame_common
.long Lset2
Ltmp3:
.quad Leh_func_begin1-Ltmp3
Lset3 = Leh_func_end1-Leh_func_begin1
.quad Lset3
.byte 0
.byte 4
Lset4 = Ltmp0-Leh_func_begin1
.long Lset4
.byte 14
.byte 16
.byte 134
.byte 2
.byte 4
Lset5 = Ltmp1-Ltmp0
.long Lset5
.byte 13
.byte 6
.align 3
Leh_frame_end1:
.subsections_via_symbols
そして元のソースコード:
#include <stdio.h>
#include <string.h>
int main()
{
const char *a = "a";
const char *d = "d";
printf("%d\n", strcmp("a", "d"));
printf("%d\n", strcmp(a, "d"));
printf("%d\n", strcmp(a, d));
return 0;
}
そして、それが生成した出力 (より良い証明のためのスクリーンショット):
C 標準では、実装で負の値を返すことができます。また、結果が標準に従う限り、実装がライブラリ関数呼び出しの最適化を行うこともできます...したがって、実装はstrcmp
、関数を呼び出す代わりにインラインマシン命令を生成するなどして関数を最適化できます。引数が定数の場合、追加の最適化が可能です。したがって、結果が異なる理由は、オプティマイザーがたまたまいくつかのケースで異なるコードを生成するためです。準拠するプログラムは、どの負の値が返されるかを気にすることはできません。
編集:
現時点での私のシステムでは、出力は
-1
-3
-3
これらの結果を生成するためにコンパイラが生成したコードを次に示します (gcc -S で取得)。
movl $-1, 4(%esp)
movl $LC2, (%esp)
call _printf
movl $LC1, 4(%esp)
movl 28(%esp), %eax
movl %eax, (%esp)
call _strcmp
movl %eax, 4(%esp)
movl $LC2, (%esp)
call _printf
movl 24(%esp), %eax
movl %eax, 4(%esp)
movl 28(%esp), %eax
movl %eax, (%esp)
call _strcmp
movl %eax, 4(%esp)
ご覧のとおり、strcmp
呼び出しは 2 つしかありません。コンパイラは "a" が "d" より小さいことを認識しているため、最初の比較の -1 の結果はコンパイル時に生成されます。-O を使用すると、次のコードが生成されます。
movl $-1, 4(%esp)
movl $LC0, (%esp)
call _printf
movl $-1, 4(%esp)
movl $LC0, (%esp)
call _printf
movl $-1, 4(%esp)
movl $LC0, (%esp)
call _printf
私は得ています
-1
-3
-1
-O4
Linux 上の GCC 4.1.2 で最適化された ( ) ビルドの出力。コンパイラが生成するコードは次のとおりですmain
main:
.LFB25:
subq $8, %rsp
.LCFI0:
movl $-1, %esi
xorl %eax, %eax
movl $.LC0, %edi
call printf
movzbl .LC1(%rip), %edx
movzbl .LC2(%rip), %eax
movl %edx, %esi
subl %eax, %esi
jne .L2
movzbl .LC1+1(%rip), %esi
movzbl .LC2+1(%rip), %eax
subl %eax, %esi
.L2:
movl $.LC0, %edi
xorl %eax, %eax
call printf
movl $-1, %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
xorl %eax, %eax
addq $8, %rsp
ret
これは、最初と最後の比較が実際には最適化されていないことを意味しますが、中間の比較は実際には減算によって本質的に実装されていました (これが生成された理由です-3
)。この選択的な動作には何のロジックも見当たらないので、おそらくオプティマイザーの癖に過ぎません。
ところで、最適化なしでは、同じ GCC 4.1.2 が生成します
-1
-1
-1
を呼び出すので出力しますstrcmp
。strcmp
この標準ライブラリでは、次のように実装されています
<strcmp> mov (%rdi),%al
<strcmp+2> cmp (%rsi),%al
<strcmp+4> jne <strcmp+19>
<strcmp+6> inc %rdi
<strcmp+9> inc %rsi
<strcmp+12> test %al,%al
<strcmp+14> jne <strcmp>
<strcmp+16> xor %eax,%eax
<strcmp+18> retq
<strcmp+19> mov $0x1,%eax
<strcmp+24> mov $0xffffffff,%ecx
<strcmp+29> cmovb %ecx,%eax
<strcmp+32> retq
つまり、次善と見なされる場合でも、意図的に-1
、0
またはを返すように実装されています。+1
strcmp
< 0
文字列が等しくない場合に返します。
これは、文字列内で一致しない最初の文字の値が 2 番目の文字列の方が高いことを示しています。正確な正確な値はUnspecifiedです。
定義されている唯一のことは、出力が次のとおりであるかどうかです。