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


岸 哲夫

 今回はgccの最適化オプションについて,さらに詳しく説明する.コンパイルされたアセンブラソースと元のCソースを比較して,どのような最適化が行われているか見てもらいたい.なお,各最適化オプションについては説明の重複を避けるため,すでに説明したものに関しては先月号を参照していただきたい.今回は,その他のオプションの説明を行う. (筆者)


最適化オプションの出力

-ffloat-store

 さっそくですが最適化オプション-ffloat-storeを指定すると吐き出したアセンブラコードがどのように変化するかを見てみましょう.

 検証用のコードをリスト1に示します.

〔リスト1〕test00.c
#include <stdio.h>
main(int	argc,char* argv[])
{
	register double	i=4503599627370496e+11;
	printf("%e\n",i);
	return;
}

 このコードをオプションなしでコンパイルすると,リスト2のようなアセンブラコードを吐きます.

〔リスト2〕test00.s
	.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)"

 ここではbpレジスタに浮動小数点値をセットしていますが,セットしたくない場合もあります.その場合,オプションを-ffloat-storeにすると,リスト3のようなアセンブラコードを吐きます.

〔リスト3〕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)"

-fno-defer-pop

 次に-fno-defer-popオプションを付けた場合について検証してみましょう.

 検証用のコードをリスト4に示します.

〔リスト4〕test01.c
#include	<stdio.h>
main(int	argc,char* argv[])
{
	void	func01(int	x,int y,int z);
	void	func02(int	x,int y,int z);
	void	func03(int	x,int y,int z);
	void	func04(int	x,int y,int z);
	int	z1	= 0;
	int	z2	= 0;
	int	z3	= 0;
	int	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;
}

 このコードをオプションなしでコンパイルすると,リスト5のようなアセンブラコードを吐きます.

〔リスト5〕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)"

 関数の引き数が参照渡しの場合,関数呼び出し後に無条件にPOPされますが,この場合は関数呼び出し後に値をスタックしているのがわかります.

 では,-fno-defer-popオプションを付けた場合をリスト6に示します.

〔リスト6〕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)"

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

-fforce-mem

 次に,-fforce-memオプションを付けた場合について検証してみましょう.

 同じく検証用のコードをリスト7に示します.

〔リスト7〕test02.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;
}

 このコードをオプション無しでコンパイルするとリスト8のようなアセンブラコードを吐きます.同一のメモリアドレスでも別のレジスタに格納されています.

〔リスト8〕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)"

 では,-fforce-memオプションを付けた場合の出力をリスト9に示します.

〔リスト9〕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
	pushl %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)"

 同一のメモリアドレスは,同一のレジスタに一度だけ格納されています.問題は固定値で表されたメモリアドレスの内容が外部のハードウェアによって更新される場合などに最適化が意図しないものになってしまうことです.

-fomit-frame-pointer

 今度は-fomit-frame-pointerオプションを付けた場合について検証してみましょう.

 同じく検証用のコードをリスト10に示します.

〔リスト10〕test03.c
	#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	=	4294967290UL;
	register unsigned	long	pt11	=	4294967290UL;
	register unsigned	long	pt12	=	4294967290UL;
	register unsigned	long	pt13	=	4294967200UL;
	register unsigned	long	pt14	=	4294967290UL;
	return;
}

 このコードをオプションなしでコンパイルすると,リスト11のようなアセンブラコードを吐きます.

〔リスト11〕test03.s
	.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
	movl $10,%eax
	movl $20,%edx
	movl $30,%ecx
	movl $40,%ebx
	movl $50,%esi
	movl $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)
	movl $-6,-36(%ebp)
	jmp .L2
.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)"

 フレームポインタであるEBPレジスタに値がセットされています.

 では,-fomit-frame-pointerオプションを付けた場合の出力をリスト12に示します.

〔リスト12〕test03.s(-fomit-frame-pointer)
	.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
	movl $20,%edx
	movl $30,%ecx
	movl $40,%ebx
	movl $50,%esi
	movl $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)
	movl $-6,12(%esp)
	jmp .L2
.L2:
	popl %ebx
	popl %esi
	popl %edi
	addl $48,%esp
	ret
.Lfe1:
	.size	 main,.Lfe1-main
	.ident	"GCC: (GNU) 2.95.3 20010315 (release)"

 フレームポインタであるEBPレジスタを使わずにスタックポインタで指し示される領域に値が設定されています.

 ターゲットマシンによっては,このオプションを指定しても何も変化しません.VAXなどはフレームポインタを自動的に扱うので,プログラマが意識する必要がないからです.マクロFRAME_POINTER_REQUIREDの式の値がゼロであれば,このオプション指定に意味はありません.


記事内インデックス    連載インデックスはこちら   Interfaceのトップ
最適化オプションの出力
より高度な最適化オプション
出力の制御オプション

Copyright 2002 岸 哲夫

Copyright 1997-2017 CQ Publishing Co.,Ltd.