読者です 読者をやめる 読者になる 読者になる

Let's write β

趣味で書いたこととか、RustとLispが好き

GCCのコンパイル力を知った

GCCでswitchを使ってると、明確に確定値の場合はswitchが自動的に必要最小限の
演算に展開さていることを知った。
まずは子のCのコード

#include <stdio.h>

main()
{
    switch('a') {
        case 'a': printf("hoge");
        case 'b': printf("fuge");
    }   
}

みてのとおりswitch整数式中に定数 'a'を指定して,case中にも'a'の条件がある。
すると

	.file	"test.c"
	.section	.rodata
.LC0:
	.string	"hoge"
.LC1:
	.string	"fuge"
	.text
.globl main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$.LC0, %eax
	movq	%rax, %rdi
	movl	$0, %eax
	call	printf
	movl	$.LC1, %eax
	movq	%rax, %rdi
	movl	$0, %eax
	call	printf
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.4.6 20120305 (Red Hat 4.4.6-4)"
	.section	.note.GNU-stack,"",@progbits

このようにjmp命令のないコードが出力される。「あれ比較してねぇぞ」と思ったので
テストのために以下のようなコードを

#include <stdio.h>

main()
{
	switch('b') {
		case 'a': printf("hoge");
		case 'b': printf("fuge");
	}
}

こんどはbの方を指定しました。すると

	.file	"test.c"
	.section	.rodata
.LC0:
	.string	"fuge"
	.text
.globl main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$.LC0, %eax
	movq	%rax, %rdi
	movl	$0, %eax
	call	printf
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.4.6 20120305 (Red Hat 4.4.6-4)"
	.section	.note.GNU-stack,"",@progbits

このように綺麗さっぱりaの式もhogeの文字列領域も確保されていません。
おそるべしGCC!!コンパイル時に判定可能なものは判定してしまって不必要なコードをは自動的に削除されています。

#include <stdio.h>

main()
{
    switch('a') {
        case 'a': printf("hoge");
                  break;
        case 'b': printf("fuge");
    }   
}

breakを入れると..

	.file	"test.c"
	.section	.rodata
.LC0:
	.string	"hoge"
	.text
.globl main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$.LC0, %eax
	movq	%rax, %rdi
	movl	$0, %eax
	call	printf
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.4.6 20120305 (Red Hat 4.4.6-4)"
	.section	.note.GNU-stack,"",@progbits

breakの分の命令は増えたもののbのための式は確実に存在しません。ちゃんとbreakも処理しているようですね。
scanfにしてちゃんと実行時にしか判定できなくすると

#include <stdio.h>

main()
{
	char c;

	scanf("%c", &c);

	switch(c) {
		case 'a': printf("hoge");
		case 'b': printf("fuge");
	}
}

ときちんとcmpとjmpが出現します。

	.file	"test.c"
	.section	.rodata
.LC0:
	.string	"%c"
.LC1:
	.string	"hoge"
.LC2:
	.string	"fuge"
	.text
.globl main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	$.LC0, %eax
	leaq	-1(%rbp), %rdx
	movq	%rdx, %rsi
	movq	%rax, %rdi
	movl	$0, %eax
	call	__isoc99_scanf
	movzbl	-1(%rbp), %eax
	movsbl	%al, %eax
	cmpl	$97, %eax
	je	.L3
	cmpl	$98, %eax
	je	.L4
	jmp	.L6
.L3:
	movl	$.LC1, %eax
	movq	%rax, %rdi
	movl	$0, %eax
	call	printf
.L4:
	movl	$.LC2, %eax
	movq	%rax, %rdi
	movl	$0, %eax
	call	printf
.L6:
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.4.6 20120305 (Red Hat 4.4.6-4)"
	.section	.note.GNU-stack,"",@progbits

以上のようにGCCは適切にコンパイル時に判断して最適なコードを出力しているようです。
すごい!

僕が働いているAzit.incでは一緒に働けるエンジニアを募集しています!
採用情報 — 株式会社アジット|Azit Inc.