注意
この記事を書いている人は、日常的に C を書いているわけではありません。
普段は Python とか Java とか書いてます。
もしかすると見当違いの内容があるかもしれません。
補足・ツッコミ・愛のマサカリは、ぜひお願いします…。
経緯
去年、情報系の専門学校に通う学生(以下、その子)から、 C 言語のソースコードをみせてもらって、添削することがあった。
C 言語を使いこなせてるとは、お世辞にもいえない私が見ても、その子のコードはいろいろと厳しい感じだった。
その子とよく話してみると 変数や関数の宣言をしないことが、資源の節約になると思い込んでいる ようだった。
その時は「節約にならない」という内容を口頭で伝えたのだけど、コードにしたほうがわかりやすいので、検証して記事を書いた。
今回の例示に使うコンパイラとバージョン
私の OS X に入ってた LLVM の cc を使った。
$ cc --version
Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.1.0
Thread model: posix
ファイル
24 と出力するだけの C 言語のソースが 3 つある。
この 3 つのファイル a.c, b.c, c.c をコンパイルしよう。
#include <stdio.h>
int main(void)
{
printf("%d\n", 24);
return 0;
}
#include <stdio.h>
int main(void)
{
int i = 1;
int j = 2;
int k = 3;
int l = 4;
int m = i * j * k * l;
printf("%d\n", m);
return 0;
}
#include <stdio.h>
static int x2(int x)
{
return x * 2;
}
static int x3(int x)
{
return x * 3;
}
static int x4(int x)
{
return x2(x2(x));
}
int main(void)
{
int x = x4(x3(x2(1)));
printf("%d\n", x);
return 0;
}
- a.c は値をほぼそのまま表示してる
- b.c は変数を何個も使ってる。
- c.c は関数を定義して何個も呼び出している
コンパイル
これをそれぞれ最適化オプション -O3
でコンパイルする
$ cc -c -O3 a.c b.c c.c
比較
バイナリを比較するため、 cmp
コマンドを使う。
$ cmp a.o a.o
$ cmp a.o d.o
a.o d.o differ: char 1153, line 1
ファイルを 2 つ指定し、 2 つの中身が同じなら何も出力せず、
違う場合はその違いを表示するコマンド。
さっき作ったファイルを比較してみると
$ cmp a.o b.o
$ cmp b.o c.o
$ cmp c.o a.o
実は 3 つとも全く同じ実行ファイルになる !
アセンブラを見てみる
-S オプションをつけるとアセンブリのファイルを出力する
$ cc -O3 -S a.c
.section __TEXT,__text,regular,pure_instructions
.globl _main
.align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp2:
.cfi_def_cfa_offset 16
Ltmp3:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp4:
.cfi_def_cfa_register %rbp
subq $16, %rsp
leaq L_.str(%rip), %rdi
movl $24, %esi
movl $0, -4(%rbp)
movb $0, %al
callq _printf
movl $0, %esi
movl %eax, -8(%rbp) ## 4-byte Spill
movl %esi, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "%d\n"
.subsections_via_symbols
コンパイラが、見事に全部 24
にして、不要な代入などは消去してるのが分かる。
最適化オプション -O3
では、変数も関数も名前もなくなってしまった。
gcc で同じ実験をする場合の注意
$ cc -c -O3 -S a.c
gcc は生成するファイルに元ファイルの情報を含むので、上の例は生成されるファイルには必ず差分が発生してしまう。
-S
オプションをつけて、アセンブリの diff をとれば、ファイル名以外には相違ないことが確かめられる。
その子に伝えたいこと
-
-O3
オプションをつけたり、inline
キーワードをつけることで、コンパイラが頑張ってくれるので、人間が手で変数や関数を減らす必要はない。 - 結果的に生成される実行ファイルが同じなら、変数や関数を減らすことは、「実行する機械資源の節約」にはならないよね。
- それよりも、読みやすいスタイル・デバッグしやすいスタイルで書くことが「人間の労力を節約」になるんじゃないかな。