Cプログラミング
 Computer >> コンピューター >  >> プログラミング >> Cプログラミング

GCCの-SオプションでC/C++コードをアセンブリ言語に変換する方法

この記事では、gccを使用してCまたはC++のソースコードからアセンブリ言語の出力を生成する方法を解説します。コンパイラがどのような機械語レベルのコードを生成しているのかを確認したい場合に非常に役立つテクニックです。

-Sオプションの基本

gccには、コンパイル過程の中間出力をすべて取得できる便利な機能が備わっています。アセンブリ出力を得るには、「-S」オプションを指定します。このオプションを付けると、コンパイルまでは実行されますが、アセンブラへ渡す前の段階で処理が停止し、アセンブリコードが出力されます。コマンドの構文は以下の通りです。

gcc -S program.cpp

このコマンドを実行すると、同じディレクトリに「program.s」というアセンブリファイルが生成されます。なお、最適化レベルを変更したい場合は「-O0」(最適化なし)や「-O2」(高レベル最適化)などのオプションを組み合わせると、最適化によるコードの違いを比較できて勉強になります。

サンプルプログラム

それでは、実際に出力がどのようになるか見てみましょう。ここではシンプルなプログラムを用意しました。2つの数値を変数xとyに格納し、その合計を別の変数に保存した後、結果を出力するだけのプログラムです。

C++コード例

#include <iostream>
using namespace std;
main() {
    int x, y, sum;
    x = 50;
    y = 60;
    sum = x + y;
    cout << "Sum is: " << sum << endl;
}

アセンブリ出力結果

上記のプログラムに対して「gcc -S test_cpp.cpp」を実行すると、以下のようなアセンブリコードが得られます(環境:Ubuntu / GCC 7.3.0)。

.file "test_cpp.cpp"
.text
.section .rodata
.type _ZStL19piecewise_construct, @object
.size _ZStL19piecewise_construct, 1
_ZStL19piecewise_construct:
.zero 1
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.LC0:
.string "Sum is: "
.text
.globl main
.type main, @function
main:
.LFB1493:
.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 $50, -12(%rbp)
movl $60, -8(%rbp)
movl -12(%rbp), %edx
movl -8(%rbp), %eax
addl %edx, %eax
movl %eax, -4(%rbp)
leaq .LC0(%rip), %rsi
leaq _ZSt4cout(%rip), %rdi
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
movq %rax, %rdx
movl -4(%rbp), %eax
movl %eax, %esi
movq %rdx, %rdi
call _ZNSolsEi@PLT
movq %rax, %rdx
movq _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rax
movq %rax, %rsi
movq %rdx, %rdi
call _ZNSolsEPFRSoS_E@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1493:
.size main, .-main
.type _Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB1982:
.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 %edi, -4(%rbp)
movl %esi, -8(%rbp)
cmpl $1, -4(%rbp)
jne .L5
cmpl $65535, -8(%rbp)
jne .L5
leaq _ZStL8__ioinit(%rip), %rdi
call _ZNSt8ios_base4InitC1Ev@PLT
leaq __dso_handle(%rip), %rdx
leaq _ZStL8__ioinit(%rip), %rsi
movq _ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
movq %rax, %rdi
call __cxa_atexit@PLT
.L5:
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1982:
.size _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
.type _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB1983:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $65535, %esi
movl $1, %edi
call _Z41__static_initialization_and_destruction_0ii
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1983:
.size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I_main
.hidden __dso_handle
.ident "GCC: (Ubuntu 7.3.0-16ubuntu3) 7.3.0"
.section .note.GNU-stack,"",@progbits

出力内容のポイント

一見複雑に見えますが、重要な部分を抜き出すと理解しやすくなります。

  • 「movl $50, -12(%rbp)」「movl $60, -8(%rbp)」:変数xとyにそれぞれ50と60を代入している命令です。ローカル変数はスタック上(rbpレジスタからのオフセット位置)に配置されています。
  • 「addl %edx, %eax」:2つの値を加算し、その結果をsumに相当する領域へ格納しています。
  • 「call _ZNSolsEi@PLT」など:C++では関数名がマングリング(名前修飾)されるため、coutへの出力処理がこのような長いシンボル名になっています。
  • _GLOBAL__sub_I_main:iostreamの静的初期化(ios_base::Initによる標準ストリームの初期化)を行うためのコードです。C++特有の部分ですね。

補足:Intel構文で出力したい場合

デフォルトのAT&T構文ではなく、Intel構文でアセンブリを出力したい場合は、以下のように「-masm=intel」オプションを追加します。

gcc -S -masm=intel program.cpp

このように、gccの-Sオプションを使えば、ソースコードがどのようにアセンブリレベルで実装されるのかを手軽に確認できます。コンパイラの最適化の挙動を学んだり、パフォーマンスチューニングを行ったりする際にぜひ活用してみてください。

  1. C/C++で文字列を整数配列に変換する方法

    本記事では、C/C++においてカンマ区切りの数値文字列(例:「2, 6, 3, 14」)を整数型の配列へ変換する方法を解説します。文字列操作と数値変換は、競技プログラミングや入力データの解析など、さまざまな場面で役立つ基本テクニックです。 基本的な考え方 変換の流れは以下のとおりです。 結果を格納するための新しい配列(ここではstd::vector)を用意する 与えられた文字列を先頭から1文字ずつ走査する 文字がカンマ「,」なら、それまでに組み立てた数値を配列へ追加し、次の要素へ移る 文字が数字なら「現在の値 × 10 + 新しい桁」で数値を組み立て続ける 空白などの区切り文字は読み飛ばす

  2. C/C++で学ぶAA木(AA Tree)とは?基本概念と平衡化操作を徹底解説

    AA木(AA Tree)とはコンピュータサイエンスにおいて、AA木(AA Tree)とは、順序付きデータを効率的に格納・検索するために実装された平衡木(バランスドツリー)の一種です。AA木は、赤黒木(Red-Black Tree)の変種として扱われます。赤黒木は二分探索木の一形態であり、要素の追加や削除を効率的にサポートします。赤黒木と大きく異なる点は、AA木では赤いノードを右の子としてのみ追加でき、左の子としては配置できないという制約があることです。この制約により、2-3-4木ではなく2-3木をシミュレートすることになり、その結果、木の保守操作が大幅に簡素化されます。赤黒木の平衡化アルゴリズ