第1章
GCC の最適化オプション

GCC が出力するオブジェクト・ファイルを，より高速動作するように，またはよりサイズが小さくなるようにすることを「最適化」と呼ぶ．通常は最適化と言った場合は実行速度を高めるものを指す．
GCC はコンパイル時にさまざまな最適化オプションを指定することができる．しかし，最適化オプションの多くは，プログラミングや CPU アーキテクチャに精通していなければ適切に使用することができない．いい加減に指定するとバグを誘発したり，最悪の場合には動作しなくなる．
この章では最適化オプションについて詳しく紹介する．

まず，簡単なソースをコンパイルして，どのようなア

GCC を手動で最適化してみる

センブラ・ソースを吐き出すのかを見てみましょう．
ここでは，x86 用のコード生成を例に説明します．

GCC の Web サイトのトップ・ページを図 1 に示します．GCC に関しては，日本語化パッチなどは必要ないため，最新情報はつねにここから入手できます．

入力するソースをリスト 1 に示します．Emacs などを使用して入力してください．
gcc -S test1.cと入力してください．吐き出されるアセンブラのコードはリスト 2 のようになります．
このアセンブラ・ソースを見る限りデフォルトでは
int 型変数はスタックされ，レジスタに入らないようです．
では，リスト 3 のようにソースを変更して同じようにアセンブラ・ソースを作成してみてください（リスト 4）．
リスト 2 とリスト 4，二つのアセンブラ・ソースを見比べればわかりますが，変数を意図してレジスタ変リスト 2 test1.s

図 1 GCC の Web サイトのトップ・ページ（http://www.gnu.org/software/gcc/gcc.html）

リスト 1 test1.c
int f()
{
int a
=
int b
=
return a-b;
}

100;
200;

リスト 3 test2.c
int f(){
register int a
register int b
return a-b;
}

10

=
=

100;
200;

.file "test2.c".version"01.01"
gcc2_compiled.:.text.align 4.globl f.type f,@functionf:pushl %ebp
movl %esp,%ebp
subl $24,%esp
movl $100,-4(%ebp)
movl $200,-8(%ebp)
movl -4(%ebp),%eax
movl -8(%ebp),%ecx
movl %eax,%edx
subl %ecx,%edx
movl %edx,%eax
jmp .L2.p2align 4,,7
.L2:movl %ebp,%esp
popl %ebp
ret.Lfe1:.size f,.Lfe1-f.ident"GCC: (GNU) 2.95.3 20010315 (release)"

第1章
リスト 4 test2.s

リスト 6 test3.s.file "test3.c".version"01.01"
gcc2_compiled.:.text.align 4.globl f.type f,@functionf:pushl %ebp
movl %esp,%ebp
pushl %ebx
movl $100,%ecx
movl $200,%edx
movl %ecx,%ebx
subl %edx,%ebx
movl %ebx,%eax
jmp .L2.L2:
movl -4(%ebp),%ebx
movl %ebp,%esp
popl %ebp
ret.Lfe1:.size f,.Lfe1-f.ident"GCC: (GNU) 2.95.3 20010315 (release)".file "test4.c".version"01.01"
gcc2_compiled.:.text.align 4.globl f.type f,@functionf:pushl %ebp
movl %esp,%ebp
pushl %ebx
movl $100,%ecx
movl $200,%edx
movl %ecx,%ebx
subl %edx,%ebx
movl %ebx,%eax
jmp .L2.L2:
movl -4(%ebp),%ebx
movl %ebp,%esp
popl %ebp
ret.Lfe1:.size f,.Lfe1-f.ident"GCC: (GNU) 2.95.3 20010315 (release)"

リスト 7 共通の部分式を置き換える

リスト 5 test3.c
int f(){
register int a asm("ecx")=
register int b=
200;
return a-b;}

GCC の最適化オプション

100;

数に指定した後者のソースでは，たしかにレジスタに数値が入力されています．

int main(int argc, char* argv[]){
int x;
int y;
int z;
int res;
x
=
10;
y
=
20;
z
=
x
+
y;
/************ 他の処理 *****************/
res =
10
*
(x
+
y);
returnres;}①

このように，プログラマが自らソースのコンパイル
方法を指定することも最適化の一つです．この機能はグローバル変数，局所変数のどちらにも使用可能ですが，CPU に依存します．
また，リスト 5，リスト 6 のように，どのレジスタに入れるかという指定もできます．この例では ecx レ
ジスタに入れています．ただし，先のコードに比べてより一層 CPU に依存するので注意が必要です．

コードを書き換える¡命令実行の高速化
命令の実行回数を削減するため，以下のようなコードの書き換えが行われます．

● 例1

リスト 7 のようなコードが考えられます．このコードで①の部分は，
res

最適化とは

共通の部分式を置き換える= 10 * z;と置き換えることができます．
しかし，並列処理や変数 z のアドレスがハードウェ

最適化とは，ソースからオブジェクト・コードを生

アのアドレスとリンクしていた場合，その最適化処理

成する過程において，効率の良いオブジェクト・コー

はバグを作り出してしまいます．

ドを生成するための手法です．GCC ではソース・プ● 例2

ログラムを字句解析，構文解析，意味解析した後，中定数を置き換える

リスト 8 のようなコードが考えられます．このコー間ファイルとしてアセンブラのコードを出力します．

ドは単純にリスト 9 のように書き換えられます．

その次にアセンブラのコードを見直して最適化します．

● 例3

基本はプログラムの実行効率の向上を図ることです．最適化の方法として，以下のものが挙げられます．
¡命令の実行回数を削減する¡RISC チップの場合，1 命令に変換できる場合には式のループ外への移動

リスト 10 のようなコードが考えられます．このコードはリスト 11 のように書き換えられます．
やはり並列処理や変数 y のアドレスがハードウェアのアドレスとリンクしていた場合，その最適化処理は11

リスト 8 定数を置き換える
int main(int argc, char* argv[]){
int x;
int y;
int z;
x
=
10
+
30;
y
=
20;
z
=
x
+
y;
/************ ほかの処理 *****************/
returnz;}

リスト 9 定数を置き換えた結果
int main(int argc, char* argv[]){/************ ほかの処理 *****************/
return10 + 30 +20;}

リスト 11 式をループ外へ移動した結果
y=200;
for(ix=0;ix<_Max;ix++)
{
・・・・・

}

リスト 12 冗長なループを変換する
for(ix=0;ix<_Max;ix++){
a[ix] =
0;}
for(ix=0;ix<_Max;ix++){b[ix] =
0;}

リスト 13 冗長なループを変換した結果
リスト 10 式のループ外への移動
for(ix=0;ix<_Max;ix++)
{
x
=
10;
y
=
x
*

for(ix=0;ix<_Max;ix++){
a[ix] =
0;b[ix] =
0;
}

20;

・・・・・

}

-0 を指定することでコード・サイズと実行時間を小バグを作り出してしまいます．

さくしようとしています．また，全機種で-fthread-● 例4jumps と-fdefer-pop を有効にします．遅延スロッ

冗長なループを変換する

リスト 12 のようなコードが考えられます．このコードはリスト 13 のように書き換えられます．

● 例5演算の置き換え

次のようなコード，y = x ^ 2;

トのある機種では-fdelayed-branch を ON にし，
フレーム・ポインタなしでもデバッグをサポートできる機種では-fomit-frame-pointer を ON にしています．機種によっては，ほかのオプションを ON にするものもあります．
フレーム・ポインタに関して補足します．C/C++は，
y = x * x と等価で，このほうが効率良く処理できるため書き換えられます．

では関数が呼び出された際に，スタックに積まれた引き数を指し示すためのフレーム・ポインタを設定してあります．しかし，このフレーム・ポインタは省略可

最適化のオプション

能であり，そうすることによってバイナリの最適化が図れます．ただし，デバッガを使ってのバイナリ・

コンパイラ・オプションによる自動最適化について解説します．

● -0，-01

ファイルのデバッグは不可能になります．

● -02
コンパイラは，ほとんどすべての最適化を実行しま

このオプションでは基本的な最適化を行います．

す．-02 を指定した場合，ループ展開や関数のインラ

たとえば register の不要な移行を行わない，メモイン展開を行いません．-0 と比べると，このオプショ

リ・アクセスを減らすために register に値を保存してンはコンパイル時間が増えますが，生成コードの効率

おく，などの最適化を行います．

が良くなります．

最適化を行うと，コンパイルに時間がかかるうえ，-02 を指定すると，ループ展開と関数のインライン

大きな関数についてはたくさんのメモリを余計に使い展開，それに厳密なエイリアシングを除いたすべてのます．

最適化が有効になります．また，すべての機種は-0 を指定しないと，register 宣言した変数しかレジスタに割り当てられません．

12-fforce-mem オプションを付け，デバッグと干渉しない機種ではフレーム・ポインタの削除を行います．

第1章
● -03

GCC の最適化オプション

が良いと思います．どの最適化オプションでもそうな-03 は，-02 で指定されるすべての最適化を行い，のですが，可搬性と実行速度を秤にかけることになりかつ inline-functions オプションを行います．

ます．ただし，ROM 上で動くものを作る場合，可搬inline-functions オプションについては後述し性を犠牲にしてサイズと実行速度を追求することになます．

るだろうと思います．そんなときに試行錯誤し，吐き● -00

出したアセンブラ・ソースを見て最適化を検討するこ

最適化を行いません．gdb が正しく動作しない場合とが必要となります．

には，このオプションを付けることでデバッグができ● -fno-defer-pop

るようになります．

● -0s

関数呼び出しのたびに，つねにその関数から戻るとすぐ引き数を POP します．関数呼び出しの後に引きサイズについて最適化します．-02 の最適化をすべ数を POP しなければならない機種では，GCC は普通て有効にし，結果としてサイズが大きくなる最適化をは複数の関数呼び出しについてスタックに引き数を蓄行わなければならない場合には最適化を中止します．

積し，それらをすべて一度に POP します．

-0 オプションを複数指定した場合は，最適化レベ

これはハードウェアのアドレスを共有して実行する

ルの数字が付いていてもいなくても，最後に指定したような場合や，そのオブジェクトの永続性が短い関数

オプションが有効になります．

から戻った場合などに指定するべきオプションです．

-fflag という形のオプションは機種独立のフラグを指定します．ほとんどのフラグには，肯定形と否定● -fforce-mem
メモリ・オペランドについて算術演算を行う前に，形があります．-ffoo の否定形は-fno-foo です．

そのメモリ・オペランドをレジスタに強制的にコピー● -ffloat-store

させます．この結果，すべてのメモリ参照を潜在的な浮動小数点変数をレジスタにストアしないように

共通部分式とすることにより，生成コードが良くなる

し，浮動小数点値をレジスタもしくはメモリから取り可能性があります．これらのメモリ参照が共通部分式

出すかどうかを変更する可能性のあるオプションを禁でない場合は，命令組み合わせフェーズが独立したレ

止します．
このオプションを指定すると，68000 のようなプロセッサで好ましくない余分な精度を使わないようにな

ジスタへのロードを削除しなければなりません．-02を指定すると，このオプションが有効になります．
筆者には，このオプションは可搬性に有害なだけでります．68000 では，浮動小数点レジスタは double

最適化の意味があまりないように思えます．

がもつと想定されているよりも余分の精度を保持して● -fforce-addr

います．インテルの CPU についても同様です．いく

メモリ中のアドレス定数を，それについて算術演算

つかのプログラムは IEEE 浮動小数点の厳密な定義をを行う前にレジスタにコピーします．処理速度が多少想定しており，そういうプログラムに対しては，適当速くなる場合もあります．

な中間計算結果をすべて変数に格納するようにプログ

メモリ中の数値を演算するよりも，レジスタに格納

ラムを修正しておけば，-ffloat-store を使うことされた数値を演算するほうがもちろん速くなります．

ができます．

このオプションは試してみる価値があると思います．

C の double 型は 64 ビットを符号 1 ビット/指数部● -fomit-frame-pointer

11 ビット/仮数部 52 ビットとして浮動小数点形式を表

フレーム・ポインタを必要としない関数は，フレー

現しています．double 型では，仮数部は 2 の 53 乗の

ム・ポインタをレジスタにもちません．
これによって，数を表現することができますが，たいていの CPU でフレーム・ポインタをセーブ，設定，リストアする命

は，浮動小数点レジスタの精度は 80 ビットです．そ令をなくすことができ，多くの関数で利用可能なレジの分の精度がむだだと思う場合は，-ffloat-store

スタが一つ増えます．しかし，機種によってはデバッのオプションを選択します．

グが不可能になってしまいます．

x86 アーキテクチャでこのオプションを選択しない

フレーム・ポインタを扱わないことについてユーザ場合，メモリ・アクセスが多少増加する可能性があるがわかっているのであれば，コードのむだが省かれる

ため，筆者としてはこのオプションは指定しないほうので，試す価値のあるオプションだと思います．

13リスト 14 specs*asm:%{v:-V} %{Qy:} %{!Qn:-Qy} %{n} %{T} %{Ym,*} %{Yd,*} %{Wa,*:%*}*asm_final:%|*cpp:%(cpp_cpu) %{fPIC:-D__PIC_ _ -D_ _pic_ _} %{fpic:-D_ _PIC_ _ -D_ _pic_ _} %{posix:-D_POSIX_SOURCE} %{pthread:-D_REENTRANT}*cc1:%(cc1_cpu) %{profile:-p}*cc1plus:*endfile:%{!shared:crtend.o%s} %{shared:crtendS.o%s} crtn.o%s*link:-m elf_i386 %{shared:-shared}*lib:%{shared: -lc}%{!shared:%{!ibcs:%{!static:%{rdynamic:-export-dynamic}%{!dynamic-linker:-dynamic-linker /lib/ld-linux.so.2}}%{static:-static}}}%{!shared: %{mieee-fp:-lieee} %{pthread:-lpthread}%{profile:-lc_p} %{!profile: -lc}}*libgcc:-lgcc*startfile:%{!shared:%{pg:gcrt1.o%s} %{!pg:%{p:gcrt1.o%s}%{!p:%{profile:gcrt1.o%s}%{!profile:crt1.o%s}}}}crti.o%s %{!shared:crtbegin.o%s} %{shared:crtbeginS.o%s}*switches_need_spaces:*signed_char:%{funsigned-char:-D__CHAR_UNSIGNED_ _}*predefines:-I/usr/include -D__ELF__ -Dunix -D_ _i386_ _ -Dlinux -Asystem(posix)*cross_compile:0*version:
2.95.3● -fno-inline
キーワード inline を無視します．このオプション
は，関数のインライン展開をいっさい行わせないため

り，可搬性も高まると思います．

● -fkeep-inline-functions
ある関数に対する呼び出しが全部統合され，その関に使われます．最適化を行わない場合には，どの関数数が static と宣言されている場合でも，実行時に呼もインライン展開されません．こうした最適化の方法

び出し可能な関数を別個に出力します．このオプショ

は，ソースに書かれたものを無視するので混乱の元にンは，extern inline 宣言された関数には影響しまなる可能性があります．

せん．

キーワード inline を無視したいのであれば，ソー

このオプションに関しては，混乱を招きかねないのス上で実際にキーワード inline を削除したほうが可で注意が必要です．ソース上でプログラマが意図して読性も高まり，可搬性も高まるでしょう．

いるのが static であるなら，それにしたがわないと● -finline-functions

障害が起きる可能性があります．

単純な関数をすべて呼び出し元のコードに置きま● -fno-function-cse

す．ある関数に対するすべての呼び出しが統合される関数のアドレスをレジスタに置かないようにしま場合，その関数が static と宣言されていれば，普通す．ある関数を呼び出す命令は，それぞれ関数のアドはその関数はアセンブラ・コードとして出力されまレスを明示的に保持するようにします．

せん．

このオプションは効率の低いコードを生成します

このオプションで指定するのも良いと思いますが，が，アセンブラ出力ソースを書き換えるようなことをキーワード inline を指定したほうが可読性も高ま行う場合には，このオプションを使用しないと混乱し

14

第1章

GCC の最適化オプション*multilib:. ;*multilib_defaults:*multilib_extra:*multilib_matches:*linker:collect2*cpp_486:%{!ansi:-Di486} -D__i486 -D_ _i486_ _*cpp_586:%{!ansi:-Di586 -Dpentium}*cpp_k6:%{!ansi:-Di586 -Dk6}-D_ _i586 -D_ _i586_ _ -D_ _pentium -D_ _pentium_ _-D_ _i586 -D_ _i586_ _ -D_ _k6 -D_ _k6_ _*cpp_686:%{!ansi:-Di686 -Dpentiumpro}-D_ _i686 -D_ _i686_ _ -D_ _pentiumpro -D_ _pentiumpro_ _*cpp_cpu_default:%(cpp_586)*cpp_cpu:-Acpu(i386) -Amachine(i386) %{!ansi:-Di386} -D_ _i386 -D_ _i386_ _ %{mcpu=i486:%(cpp_486)} %{m486:%(cpp_486)}%{mpentium:%(cpp_586)} %{mcpu=pentium:%(cpp_586)} %{mpentiumpro:%(cpp_686)} %{mcpu=pentiumpro:%(cpp_686)}%{mcpu=k6:%(cpp_k6)} %{!mcpu*:%{!m486:%{!mpentium*:%(cpp_cpu_default)}}}*cc1_cpu:%{!mcpu*: %{m386:-mcpu=i386 -march=i386} %{m486:-mcpu=i486 -march=i486} %{mpentium:-mcpu=pentium}%{mpentiumpro:-mcpu=pentiumpro}}*link_command:%{!fsyntax-only: %{!c:%{!M:%{!MM:%{!E:%{!S:%(linker) %l %X %{o*} %{A} %{d} %{e*} %{m} %{N} %{n}%{r} %{s} %{t} %{u*} %{x} %{z} %{Z}%{!A:%{!nostdlib:%{!nostartfiles:%S}}}%{static:} %{L*} %D %o%{!nostdlib:%{!nodefaultlibs:%G %L %G}}%{!A:%{!nostdlib:%{!nostartfiles:%E}}}%{T*}}}}}}}てしまいます．

● -ffast-math
このオプションを指定すると，実行速度を最適化するという観点から，ANSI や IEEE の規則や仕様をある面で破ることをコンパイラに許可します．
たとえば，その場合，/usr/lib/gcc-lib/i586-pc-Linux
/2.95.3（TurboLinux7 の場合）のディレクトリ下にある specs を編集してください．specs の例をリス
ト 14 に示します．リスト 14 にあるように，*cc1:

このオプションを指定すると，コンパイラは，sqrtと，デフォルト・フラグは何もありません．もし，C関数の引き数が負にならないとか，浮動小数点値がコードのコンパイルでいつも -m486 を使いたけれ

NaN になることはないという仮定を行います．

ば，下記のように変更を加えます．

このオプションは大前提を崩す恐れがあるので，使用しないほうが良いでしょう．

*cc1:- -m486
このようにすれば，一つのマシンでプログラム開発

オプション実行のカスタマイズを数名で行う際の整合性がとれます．

プログラム開発作業でマシンを使用する数人のユーザ

GCC の最適化オプション―― C とアセンブラの比較

が，同一の環境でコンパイルしたいというときに，オ

GCC の最適化オプションについて，さらに詳しく

プションはこれを使ってくださいと伝えるのも，使い説明します．コンパイルされたアセンブラ・ソースと勝手が悪くミスを誘発する元になります．

元の C ソースを比較して，どのような最適化が行われいくつか最適化の例を挙げました．そのほかにも，

15リスト 15 test00.c

いますが，セットしたくない場合もあります．その場
合，オプションを-ffloat-store にすると，リスト#include <stdio.h>main(int argc,char* argv[]){
register double i=4503599627370496e+11;
printf("%e¥n",i);return;}17 のようなアセンブラ・コードを出力します．

● -fno-defer-pop
次に-fno-defer-pop オプションを付けた場合について検証してみましょう．
検証用のコードをリスト 18 に示します．

ているか見てもらいたいと思います．

このコードをオプションなしでコンパイルすると，最適化オプションの出力

リスト 19 のようなアセンブラ・コードを出力します．
関数の引き数が参照渡しの場合，関数呼び出し後に● -ffloat-store

無条件に POP されますが，この場合は関数呼び出し

まず，最適化オプション-ffloat-store を指定

後に値をスタックしているのがわかります．
では，-fno-defer-pop オプションを付けた場合すると吐き出したアセンブラ・コードがどのようにをリスト 20 に示します．

変化するのかを見てみましょう．
検証用のコードをリスト 15 に示します．
このコードをオプションなしでコンパイルすると，
リスト16のようなアセンブラ・コードを吐き出します．

関数呼び出し後に a レジスタの値を b レジスタに蓄積しています．

● -fforce-mem

ここでは bp レジスタに浮動小数点値をセットして次に，-fforce-mem オプションを付けた場合につ

リスト 16 test00.s
3.file "test00.c".version"01.01"
gcc2_compiled.:.section.rodata.LC1:.string"%e¥n".align 8.LC0:.long 0xe8000000,0x45774876.text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
subl $24,%esp
fldl .LC0
fstpl -8(%ebp)
addl $-4,%esp
pushl -4(%ebp)
pushl -8(%ebp)
pushl $.LC1
call printf

addl $16,%esp
jmp .L2.L2:
movl %ebp,%esp
popl %ebp
ret.Lfe1:.size
main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)"

リスト 17 test00.s（-ffloat-store）.file "test00.c".version"01.01"
gcc2_compiled.:.section .rodata.LC1:.string"%e¥n".align 8.LC0:.long 0xe8000000,0x45774876.text.align 4.globl main

.type
main,@function
main:pushl %ebp
movl %esp,%ebp
subl $24,%esp
fldl .LC0
fstpl -8(%ebp)
addl $-4,%esp
fldl -8(%ebp)
subl $8,%esp
fstpl (%esp)
pushl $.LC1

call printf
addl $16,%esp
jmp .L2.p2align 4,,7
.L2:movl %ebp,%esp
popl %ebp
ret.Lfe1:.size
main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)"

リスト 18 test01.c#includemain(int
{
void
void
void
void
int
int
int
int

16

<stdio.h>argc,char* argv[])
func01(int x,int y,int z);
func02(int x,int y,int z);
func03(int x,int y,int z);
func04(int x,int y,int z);
z1
= 0;
z2
= 0;
z3
= 0;
z4
= 0;

func01(100,150,z1);
func02(300,150,z2);
func03(10,150,z3);
func04(1000,100,z4);
return;}int func01(int x,int y,int z)
{
z=
x+y;
}
int func02(int x,int y,int z)

{
z=
x-y;
}
int func03(int x,int y,int z)
{
z=
x*y;
}
int func04(int x,int y,int z)
{
z=
x/y;
}

第1章

GCC の最適化オプションています．

いて検証してみましょう．
同じく検証用のコードをリスト 21 に示します．
このコードをオプションなしでコンパイルするとリでは，-fforce-mem オプションを付けた場合の出力をリスト 23 に示します．

スト 22 のようなアセンブラ・コードを出力します．

同一のメモリ・アドレスは，同一のレジスタに一度同一のメモリ・アドレスでも別のレジスタに格納されだけ格納されています．問題は固定値で表されたメモ

リスト 19 test01.s.file "test01.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
subl $8,%esp
addl $-4,%esp
pushl $0
pushl $150
pushl $100
call func01
addl $-4,%esp
pushl $0
pushl $150
pushl $300
call func02
addl $32,%esp
addl $-4,%esp
pushl $0
pushl $150
pushl $10
call func03
addl $-4,%esp

pushl $0
pushl $100
pushl $1000
call func04
movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.align 4.globl func01.type func01,@function
func01:
pushl %ebp
movl %esp,%ebp
movl %ebp,%esp
popl %ebp
ret.Lfe2:.size func01,.Lfe2-func01.align 4.globl func02.type func02,@function
func02:
pushl %ebp
movl %esp,%ebp
movl %ebp,%esp
popl %ebp

ret.Lfe3:.size func02,.Lfe3-func02.align 4.globl func03.type func03,@function
func03:
pushl %ebp
movl %esp,%ebp
movl %ebp,%esp
popl %ebp
ret.Lfe4:.size func03,.Lfe4-func03.align 4.globl func04.type func04,@function
func04:
pushl %ebp
movl %esp,%ebp
movl %ebp,%esp
popl %ebp
ret.Lfe5:.size func04,.Lfe5-func04.ident"GCC: (GNU)2.95.3 20010315 (release)"

リスト 20 test01.s（-fno-defer-pop）.file "test01.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
subl $24,%esp
movl $0,-4(%ebp)
movl $0,-8(%ebp)
movl $0,-12(%ebp)
movl $0,-16(%ebp)
addl $-4,%esp
movl -4(%ebp),%eax
pushl %eax
pushl $150
pushl $100
call func01
addl $16,%esp
addl $-4,%esp
movl -8(%ebp),%eax
pushl %eax
pushl $150
pushl $300
call func02
addl $16,%esp
addl $-4,%esp
movl -12(%ebp),%eax
pushl %eax
pushl $150
pushl $10
call func03
addl $16,%esp
addl $-4,%esp
movl -16(%ebp),%eax
pushl %eax
pushl $100

pushl $1000
call func04
addl $16,%esp
jmp .L2.p2align 4,,7
.L2:movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.align 4.globl func01.type func01,@function
func01:
pushl %ebp
movl %esp,%ebp
movl 8(%ebp),%eax
movl 12(%ebp),%edx
leal (%edx,%eax),%ecx
movl %ecx,16(%ebp).L3:movl %ebp,%esp
popl %ebp
ret.Lfe2:.size func01,.Lfe2-func01.align 4.globl func02.type func02,@function
func02:
pushl %ebp
movl %esp,%ebp
movl 8(%ebp),%eax
movl 12(%ebp),%edx
movl %eax,%ecx
subl %edx,%ecx
movl %ecx,16(%ebp).L4:movl %ebp,%esp

popl %ebp
ret.Lfe3:.size func02,.Lfe3-func02.align 4.globl func03.type func03,@function
func03:
pushl %ebp
movl %esp,%ebp
movl 8(%ebp),%eax
imull 12(%ebp),%eax
movl %eax,16(%ebp).L5:movl %ebp,%esp
popl %ebp
ret.Lfe4:.size func03,.Lfe4-func03.align 4.globl func04.type func04,@function
func04:
pushl %ebp
movl %esp,%ebp
movl 8(%ebp),%eax
leal 12(%ebp),%ecx
cltd
idivl (%ecx)
movl %eax,16(%ebp).L6:movl %ebp,%esp
popl %ebp
ret.Lfe5:.size func04,.Lfe5-func04.ident"GCC: (GNU)2.95.3 20010315 (release)"17

リスト 21 test02.c

リスト 24 test03.c#include <stdio.h>main(int argc,char* argv[]){
int* pt=(long)0xbffffcb8;
int* pt1 =(long)0xbffffcb8;
int* pt2 =(long)0xbffffcb8;
int* pt3 =(long)0xbffffcb8;*pt =(int)1;
printf("%d¥n",pt);
printf("%d¥n",pt1);
printf("%d¥n",pt2);
printf("%d¥n",pt3);
return;}#include <stdio.h>main(int argc,char* argv[]){
register int
pt0
= 10;
register int
pt1
= 20;
register int
pt2
= 30;
register int
pt3
= 40;
register int
pt4
= 50;
register int
pt5
= 60;
register int
pt6
= 70;
register int
pt7
= 80;
register int
pt8= 100;
register int
pt9= 200;
register unsigned
long pt10
register unsigned
long pt11
register unsigned
long pt12
register unsigned
long pt13
register unsigned
long pt14
return;}リ・アドレスの内容が外部のハードウェアによって更

=
=
=
=
=

4294967290UL;
4294967290UL;
4294967290UL;
4294967200UL;
4294967290UL;

新される場合などに，最適化が意図しないものになってしまうことです．

● -fomit-frame-pointerフレーム・ポインタである EBP レジスタを使わず

今度は-fomit-frame-pointer オプションを付にスタック・ポインタで指し示される領域に値が設定けた場合について検証してみましょう．
同じく検証用のコードをリスト 24 に示します．

されています．

このコードをオプションなしでコンパイルすると，ターゲット・マシンによっては，このオプションを

リスト 25 のようなアセンブラ・コードを出力します．

指定しても何も変化しません．VAX などはフレー

フレーム・ポインタである EBP レジスタに値がセ

ム・ポインタを自動的に扱うため，プログラマが意識する必要がないからです．マクロ FRAME_POINTER_

ットされています．
では，-fomit-frame-pointer オプションを付けた場合の出力をリスト 26 に示します．

REQUIRED の式の値がゼロであれば，このオプション
指定に意味はありません．

リスト 22 test02.s.file "test02.c".version"01.01"
gcc2_compiled.:.section .rodata.LC0:.string"%d¥n".text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
subl $24,%esp
movl $-1073742664,-4(%ebp)
movl $-1073742664,-8(%ebp)
movl $-1073742664,-12(%ebp)
movl $-1073742664,-16(%ebp)

movl -4(%ebp),%eax
movl $1,(%eax)
addl $-8,%esp
movl -4(%ebp),%eax
pushl %eax
pushl $.LC0
call printf
addl $16,%esp
addl $-8,%esp
movl -8(%ebp),%eax
pushl %eax
pushl $.LC0
call printf
addl $16,%esp
addl $-8,%esp
movl -12(%ebp),%eax
pushl %eax
pushl $.LC0

call printf
addl $16,%esp
addl $-8,%esp
movl -16(%ebp),%eax
pushl %eax
pushl $.LC0
call printf
addl $16,%esp
jmp .L2.p2align 4,,7
.L2:movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)"

リスト 23 test02.s（-fforce-mem）.file "test02.c".version"01.01"
gcc2_compiled.:.section .rodata.LC0:.string"%d¥n".text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
subl $20,%esp

18pushl %ebx
movl $-1073742664,%ebx
movl $1,-1073742664
addl $-8,%esp
pushl %ebx
pushl $.LC0
call printf
addl $-8,%esp
pushl %ebx
pushl $.LC0
call printf
addl $32,%esp
addl $-8,%esp
pushl %ebx

pushl $.LC0
call printf
addl $-8,%esp
pushl %ebx
pushl $.LC0
call printf
movl -24(%ebp),%ebx
movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)"

第1章

GCC の最適化オプション

リスト 25 test03.s
movl $-6,-36(%ebp)
jmp .L2

movl $10,%eax
movl $20,%edx
movl $30,%ecx
movl $40,%ebx
movl $50,%esimovl $60,%edi
movl $70,-4(%ebp)
movl $80,-8(%ebp)
movl $100,-12(%ebp)
movl $200,-16(%ebp)
movl $-6,-20(%ebp)
movl $-6,-24(%ebp)
movl $-6,-28(%ebp)
movl $-96,-32(%ebp).file "test03.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
subl $44,%esp
pushl %edi
pushl %esi
pushl %ebx

.L2:leal -56(%ebp),%esp
popl %ebx
popl %esi
popl %edi
movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)"

リスト 26 test03.s（-fomit-frame-pointer）
movl $-6,12(%esp)
jmp .L2

movl $20,%edx
movl $30,%ecx
movl $40,%ebx
movl $50,%esimovl $60,%edi
movl $70,44(%esp)
movl $80,40(%esp)
movl $100,36(%esp)
movl $200,32(%esp)
movl $-6,28(%esp)
movl $-6,24(%esp)
movl $-6,20(%esp)
movl $-96,16(%esp).file "test03.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main.type main,@function
main:subl $48,%esp
pushl %edi
pushl %esi
pushl %ebx
movl $10,%eax.L2:popl %ebx
popl %esi
popl %edi
addl $48,%esp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)"

リスト 27 test04.c#include <stdio.h>
int main(){
int iTbl[3];int max=3;
int i;
int j =
5;
int k =
6;
for(i = 0; i < max; i++){iTbl[i] = j * k;}return 0;}

より高度な最適化オプション
最適化オプションはまだあります．以下は，より高度な最適化を行いたいという場合に使うものです．

● -fstrength-reduce
ループの強度削減と繰り返し変数の削除の最適化を実行します．ループの外に出せる計算は外に出して，
ステップ数を減らします．
元の C ソースをリスト 27 に，最適化オプションなし

トしています．最適化前はそのままループの中で乗算のアセンブラ・ソースをリスト 28 に，最適化オプショ

しています．

ン付きのアセンブラ・ソースをリスト 29 に示します．

● -fthread-jumps

この例では変数 j と k の積が不変になるはずなの

条件分岐の後に別の条件分岐があり，その比較が最で，最適化後は即値 30（5 と 6 の積）をレジスタにセッ

初の比較に包括されるものかどうかを検査する最適化

リスト 28 test04.s.file "test04.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
subl $40,%esp
movl $3,-16(%ebp)
movl $5,-24(%ebp)
movl $6,-28(%ebp)
movl $0,-20(%ebp).p2align 4,,7.L3:movl -20(%ebp),%eax
cmpl -16(%ebp),%eax
jl .L6
jmp .L4.p2align 4,,7
.L6:

jmp .L3.p2align 4,,7.L4:xorl %eax,%eax
jmp .L2.p2align 4,,7
.L2:movl -20(%ebp),%eax
movl %eax,%edx
leal 0(,%edx,4),%eax
leal -12(%ebp),%edx
movl -24(%ebp),%ecx
imull -28(%ebp),%ecx
movl %ecx,(%eax,%edx)

movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)".L5:incl -20(%ebp)

19

リスト 29 test04.s（-fstrength-reduce）

リスト 30 test05.c#include <stdio.h>
int main(){
int res=
int i =
0;
if
(i
==
{
if
(i
{
res
}
if
(i
{
res
}
if
(i
{
res
}
}
returnres;}

xorl %eax,%eax
movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)"subl $24,%esp
movl $30,%edx
movl $2,%ecx
leal -4(%ebp),%eax.p2align 4,,7
.L21:
movl %edx,(%eax)
addl $-4,%eax
decl %ecx
jns .L21.file "test04.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp

リスト 32 test05.s（-fthread-jumps）.file "test05.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
xorl %eax,%eax
movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU) 2.95.3 20010315 (release)"

0;
0)
<=

0

=

1;

==

0

=

1;

>=

0

=

0;

)

)

)

リスト 31 test05.s.file "test05.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
subl $24,%esp
movl $0,-4(%ebp)
movl $0,-8(%ebp)
cmpl $0,-8(%ebp)

movl -4(%ebp),%edx
movl %edx,%eax
jmp .L2.p2align 4,,7

jne .L3
cmpl $0,-8(%ebp)
jg .L4
movl $1,-4(%ebp)
.L2:

.L4:

movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)"

cmpl $0,-8(%ebp)
jne .L5
movl $1,-4(%ebp).L5:
cmpl $0,-8(%ebp)
jl .L3
movl $0,-4(%ebp)
.L6:
.L3:

を実行します．もし包括されるものなら，最初の条件

ち，その条件分岐先にほかの経路を通って到達するこ分岐先は，二番目の条件分岐先かその直後のどちらかとがないものを探し出します．

に，二番目の比較条件の真偽が既知かどうかにしたがって変更されます．
元の C ソースをリスト 30 に，最適化オプションなし

元の C ソースをリスト 33 に，最適化オプションなしのアセンブラ・ソースをリスト 34 に，最適化オプショ
ン付きのアセンブラ・ソースをリスト 35 に示します．

のアセンブラ・ソースをリスト 31 に，最適化オプショ

この例では変数 i は必ずゼロです．最適化後は単純ン付きのアセンブラ・ソースをリスト 32 に示します．

明快に 1 をレジスタにセットして戻しています．最適

この例では変数 i は必ずゼロです．ネストした条件

化前はそのまま冗長な処理，条件分岐を繰り返していの真中にしか分岐しません．最適化後は単純明快に 1

ます．

をレジスタにセットして戻しています．最適化前はそ● -fcse-skip-blocksのまま比較を繰り返しています．

● -fcse-follow-jumps
共通部分式削除（CSE）の際に，条件分岐命令のう

20

上記の項目に似ています．絶対に到達しない条件分岐ブロックを無視し，コードを作成しません．
元の C ソースをリスト 36 に，最適化オプションなし

第1章
リスト 33 test06.c#include <stdio.h>
int main(){
int res=
int i =
0;
if
(i
==
{
if
(i
{
res
}
else
{
res
}
}
else
{
res =
}
returnres;}

GCC の最適化オプション

リスト 34 test06.s

0;
0)
==

0

=

1;

=

0;

.L4:

.file "test06.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
subl $24,%esp
movl $0,-4(%ebp)
movl $0,-8(%ebp)
cmpl $0,-8(%ebp)
jne .L3
cmpl $0,-8(%ebp)
jne .L4
movl $1,-4(%ebp)
jmp .L5.p2align 4,,7)0;

movl $0,-4(%ebp).L5:
jmp .L6.p2align 4,,7.L3:movl $0,-4(%ebp).L6:movl -4(%ebp),%edx
movl %edx,%eax
jmp .L2.p2align 4,,7
.L2:movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)"

リスト 36 test07.c
リスト 35 test06.s（-fcse-follow-jumps）.file "test06.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main.type main,@function
main:pushl %ebp

#include <stdio.h>
int main(){
int res
=
int i
=
if
(i
!=
{
res =
}
returnres;}

movl %esp,%ebp
movl $1,%eax
movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)"

0;
0;
0)
1;

リスト 37 test07.s.file "test07.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp

subl $24,%esp
movl $0,-4(%ebp)
movl $0,-8(%ebp)
cmpl $0,-8(%ebp)
je .L3
movl $1,-4(%ebp).p2align 4,,7
.L2:movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)".L3:
movl -4(%ebp),%edx
movl %edx,%eax
jmp .L2

リスト 38 test07.s（-fcse-skip-blocks）.file "test07.c".version"01.01"
gcc2_compiled.:.text.align 4.globl main

popl %ebp
ret.Lfe1:.size main,.Lfe1-main.ident"GCC: (GNU)2.95.3 20010315 (release)".type main,@function
main:pushl %ebp
movl %esp,%ebp
xorl %eax,%eax
movl %ebp,%espのアセンブラ・ソースをリスト 37 に，最適化オプショ● -frerun-loop-opt

ン付きのアセンブラ・ソースをリスト 38 に示します．

ループ最適化を徹底して行います．これは-fst

この例では変数 i は必ずゼロなので最適化後は変数

rength-reduce で行ったことです．ループ内で変化

i がゼロでない条件分岐を削除し，レジスタに 0 をセしない式やアドレス計算がチェックされます．
そして，ットして戻しています．最適化前はそのまま冗長な処

そうした計算はループの外に移され，その評価値がレ

理，条件分岐を繰り返しています．

ジスタに格納されます．

● -frerun-cse-after-loop● -fgcse

ループ最適化の実行後に共通部分式の削除をもう一

グローバル共通部分式を最後に削除するパスをコン

度実行します．-fstrength-reduce で行ったこと

パイラが実行します．元の C ソースをリスト 39 に，を徹底して行います．

最適化オプションなしのアセンブラ・ソースをリスト

21

リスト 39 test08.c

40 に，最適化オプション付きのアセンブラ・ソース
をリスト 41 に示します．

#include <stdio.h>
voidtest();int gData=8;
int i
=
1;
int main(){
int resTbl[100];test();
gData =
8+i;
for (i=0;i<100;i++){resTbl[i] =i*20;}
gData =
8+i;
test();
return0;}
void
test(){
gData =
8+i;}

この例では何度も繰り返される gData = 8+i;をひとまとめにセットしています．8+i の値も不変なのでまとめられます．

● -foptimize-register-moves，-fregmove
このオプションは，2 オペランド命令の機種で役立ちます．インテル・アーキテクチャのマシンではあまり意味をもちません．
TRON 仕様や RISC 系マイクロプロセッサをもつマ
シンに指定してレジスタの割り当てを最適化します．
TRON 仕様のマシンの 32 ビット仕様のプロセッサに

リスト 40 test08.s.file "test08.c".version"01.01"
gcc2_compiled.:.globl gData.data.align 4.type gData,@object.size gData,4gData:.long 8
.globl i.align 4.type i,@object.size i,4
i:.long 1.text
.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
subl $420,%esp
pushl %ebx
call test
movl i,%eax
addl $8,%eax

xorl %eax,%eax
jmp .L2.p2align 4,,7

movl %eax,gData
movl $0,i.p2align 4,,7
.L2:

.L3:
cmpl $99,ijle .L6
jmp .L4.p2align 4,,7.L6:movl i,%eax
movl %eax,%edx
leal 0(,%edx,4),%eax
leal -400(%ebp),%edx
movl i,%ebx
movl %ebx,%ecx
sall $2,%ecx
addl %ebx,%ecx
leal 0(,%ecx,4),%ebx
movl %ebx,(%eax,%edx).L5:incl ijmp .L3.p2align 4,,7.L4:movl i,%eax
addl $8,%eax
movl %eax,gData
call test

movl -424(%ebp),%ebx
movl %ebp,%esp
popl %ebp
ret.Lfe1:.size main,.Lfe1-main.align 4.globl test
.type test,@function
test:pushl %ebp
movl %esp,%ebp
movl i,%eax
addl $8,%eax
movl %eax,gData.L7:
movl %ebp,%esp
popl %ebp
ret.Lfe2:.size test,.Lfe2-test.ident"GCC: (GNU)2.95.3 20010315 (release)"

リスト 41 test08.s（-fgcse）.file "test08.c".version"01.01"
gcc2_compiled.:.globl gData.data.align 4.type gData,@object.size gData,4gData:.long 8
.globl i.align 4.type i,@object.size i,4
i:.long 1.text
.align 4.globl test
.type test,@function
test:pushl %ebp
movl i,%eax

22movl %esp,%ebp
addl $8,%eax
movl %eax,gData
movl %ebp,%esp
popl %ebp
ret.Lfe1:.size test,.Lfe1-test.align 4.globl main.type main,@function
main:pushl %ebp
movl %esp,%ebp
subl $420,%esp
pushl %ebx
call test
movl i,%eax
addl $8,%eax
movl %eax,gData
movl $0,ileal -400(%ebp),%ebx.p2align 4,,7

.L21:
movl i,%ecx
leal 0(,%ecx,4),%eax
leal (%ecx,%eax),%edx
sall $2,%edx
movl %edx,(%eax,%ebx)
leal 1(%ecx),%eax
movl %eax,icmpl $99,%eax
jle .L21
addl $9,%ecx
movl %ecx,gData
call test
xorl %eax,%eax
popl %ebx
movl %ebp,%esp
popl %ebp
ret.Lfe2:.size main,.Lfe2-main.ident"GCC: (GNU)2.95.3 20010315 (release)"

第1章
は 32 ビット汎用レジスタが 16 あるので，効率良く変数をレジスタに割り当てられます．

● -ffunction-sections，-fdata-sections
このオプションもまた，インテル・アーキテクチャのマシンではあまり意味をもちません．
リンカに命令空間の参照の局所性を改善する最適化を行う機能があるシステムでは，これらのオプション

GCC の最適化オプション● -funroll-loops
ループ展開最適化を実行します．これは，コンパイ
ル時か実行時に繰り返し回数が決められるループにしか行われません．-funroll-loops は，前述の-fstrength-reduce と-frerun-cse-afterloop を含みます．

● -funroll-all-loops

を使うと良いでしょう．HP-UX の稼働する HP PA プ

ループ展開最適化を実行します．これは，すべてのロセッサと Solaris2 の稼働する SPARC プロセッサに

ループに対して行われ，普通はプログラムの実行を遅は，このような最適化機能のあるリンカが存在します．

くしてしまいます．

このオプションを使うとデバッグ時に問題が生じる可● -fmove-all-movables

能性があるので，デバッグ中には使用しないほうが良

ループ中の不変な計算をすべてループの外に移動しいでしょう．しかし，付けたときと付けないときで実ます．

行形式ファイルが変わるため，試験を慎重に行わない● -freduce-all-givsとバグの元になるおそれがあります．

ループ中の一般誘導変数を削減します．

● -fcaller-saves

最適化というものは両刃の剣であり，あちらを立て

関数呼び出しにより破壊されるレジスタに値を割ればこちらが立たずという状態にもなります．せっかり当てることを可能にしますが．その結果として，く速度を追求してもまちがった最適化を行うと，サイ

命令を余分に生成し，呼び出しの前後でレジスタのズは小さくなったものの速度が遅くなってしまうようセーブとリストアを行うので汎用レジスタがふんだなこともよくあります．

んにあるマシンでないと意味がないように思います．

23