#include <string.h>
#include <stdio.h>
int main() {
char a[] = {"a"}, z[] = {"z"};
printf("array: %d\n", memcmp(a, z, 1));
printf("literal: %d\n", memcmp("a", "z", 1));
}
このコードの実行結果は以下の通り。
array: -25
literal: -1
memcmp
を2回呼んでおり、どちらも引数としてa
とz
という文字列が渡されている。
コンパイルされた実行ファイルをオブジェダンプして中身を見てみた。
実行環境
- OS: MacOS
- CPU: x86-64
- コンパイラ: clang
Disassembly of section __TEXT,__text:
0000000100003f00 _main:
100003f00: 55 pushq %rbp
100003f01: 48 89 e5 movq %rsp, %rbp
100003f04: 48 83 ec 10 subq $16, %rsp
100003f08: 48 8d 75 fc leaq -4(%rbp), %rsi
100003f0c: 48 8d 7d fe leaq -2(%rbp), %rdi
100003f10: 66 8b 05 7d 00 00 00 movw 125(%rip), %ax
100003f17: 66 89 45 fe movw %ax, -2(%rbp)
100003f1b: 66 8b 05 74 00 00 00 movw 116(%rip), %ax
100003f22: 66 89 45 fc movw %ax, -4(%rbp)
100003f26: ba 01 00 00 00 movl $1, %edx
100003f2b: e8 34 00 00 00 callq 52 <dyld_stub_binder+0x100003f64>
100003f30: 48 8d 3d 61 00 00 00 leaq 97(%rip), %rdi
100003f37: 89 c6 movl %eax, %esi
100003f39: b0 00 movb $0, %al
100003f3b: e8 2a 00 00 00 callq 42 <dyld_stub_binder+0x100003f6a>
100003f40: 48 8d 3d 5c 00 00 00 leaq 92(%rip), %rdi
100003f47: be ff ff ff ff movl $4294967295, %esi
100003f4c: 89 45 f8 movl %eax, -8(%rbp)
100003f4f: b0 00 movb $0, %al
100003f51: e8 14 00 00 00 callq 20 <dyld_stub_binder+0x100003f6a>
100003f56: 31 c9 xorl %ecx, %ecx
100003f58: 89 45 f4 movl %eax, -12(%rbp)
100003f5b: 89 c8 movl %ecx, %eax
100003f5d: 48 83 c4 10 addq $16, %rsp
100003f61: 5d popq %rbp
100003f62: c3 retq
callq
が3回しか呼ばれていない。
一方でesi
に-1
が直接入れられているのがわかる。
memcmp
を呼び出すのではなく、実行ファイルの生成時点で結果が埋め込まれているらしい。
文字列リテラルは、constであるためコンパイル時に値が判明している。 そのため、コンパイラは実行時に計算するのではなく、コンパイル時に計算し実行ファイルに値を埋め込む最適化を行う。
LLVMのドキュメントにそれっぽい説明があった。
memcmp
はビルトインとして埋め込まれるらしい。
https://clang.llvm.org/docs/LanguageExtensions.html#string-builtins
この最適化を行わないようにするためには、-fno-builtin
オプションをコンパイル時につける。
$ gcc -fno-builtin main.c && ./a.out
array: -25
literal: -25
想定通りの値が取得できた。
memcmp
の結果が-1
, 0
, 1
のいずれかになる理由はよくわからない。
フラグだけ見て、dec
命令とか使ってるのかな。