トップ 検索 一覧 差分 ソース ヘルプ RSS ログイン

gcc option

gcc option

MinGW上で頻繁にgccを利用しているくせにすぐに忘れるのでメモ。-D,-I,-i,-L,-l等の一般的で分かり切ってるものは書かない。随時追加。

なお、正確にはGCCのオンラインマニュアルが存在するのでそちらを参照すべき。オプションの一覧(リンク先は4.5.xのもの)もある。だが、あまりに膨大すぎるのでこの記事では「よく使用するが意味を忘れがちなオプション」に関してのメモとする。

-f(no-)strict-aliasing

厳密な別名規約(aliasing rule)に則っているとみなすか、則っていないとみなすか。intの変数に対してshort*でアクセスするような行儀の悪いコードが無いと宣言できるなら-fstrict-aliasingにする。

#include <stdio.h>
int main(int argc, char* argv[]){
    int x = 0;
    short* p = (short*)&x;  // strict aliasing rule に則っていない
    *p = 1;                 // 例えばここで1を設定しても…
    printf("x = %d", x);    // 最適化によりこれが"x = 0"になる可能性がある
    return 0;
}

このように、本来の型ではない型を使用することをtype-punning(型もじり)という。型もじりの全てが悪いわけではなく、それ自体は必要な場合があるテクニックだが、一部の(といってもかなり多くの)型もじりはコンパイラの最適化と相俟ってバグになってしまう。

自信がないなら-fno-strict-aliasingにしておいた方が無難。gcc3.x以上(?)では-O2以上で-fstrict-aliasingであると仮定され、最適化がなされる。-Wstrict-aliasingを使用すれば-fstrict-aliasingで問題がある場合に警告("dereferencing type-punned pointer will break strict-aliasing rules")させることができる。-Wstrict-aliasingは-Wstrict-aliasing=3の略で、2、1と数が減るに連れて、より問題ないかもしれないケースでも警告として報告するようになる。

基本的には-fstrict-aliasingにしたいが、どうしても一部の変数に対してだけstrict-aliasing-ruleを破りたい場合、GCC3.3以降(多分)では

int x __attribute__((may_alias));

等として、strict-aliasing-ruleに則っていない(そのため危険な最適化をすべきではない)ことを明示することが可能になっている。

type-punの何が問題なのか、簡単に説明しておこう。賢いコンパイラは、x86で言えばPentium以降で搭載されているスーパースカラのような、実行ユニットが複数あるプロセッサを認識し、それに向けた最適化を行う。連続する2つの命令が別の実行ユニットで並列・同時に実行されるよう、命令の順番を入れ替える(再配置)テクニックはその筆頭と言える。この再配置は当然、演算の依存関係に注意して処理の意味(内容)が変わらないように行わなければならない。が、変数に型の違う別名(エイリアス)を付けられてしまうと、コンパイラもその依存関係を追いきれなくなる(特にポインタ演算を含む場合)。本来は依存関係により再配置してはならない命令を再配置してしまうと、一見問題ないコードなのに不正な挙動を行う、発見しづらいバグになる。インライン関数の展開を含んだりすると、猶一層わかりづらい。

再配置機能自体はどちらかと言えば、ハードウェア、つまりCPU側の、いわゆるアウトオブオーダ実行機能として有名だ。CPU側では比較的近い範囲でのアウトオブオーダ実行しか実現できないため、より広域ではコンパイラがコンパイル時に意識するほうが、より効率のよい(高速な)コードになる。むしろ、IA-64のようなVLIW・EPICアーキテクチャでは複雑化するCPU側でのアウトオブオーダ実行を諦め、コンパイラに全てを任せる方向ですらある。VLIW・EPICの思想はPCレベルでは市場に受け入れられたとは言いがたいが、命令の再配置によって高速化する機能はCPUだけのものではない。

なお、intに対するunsigned int等の共通性がある型や、共用体でアクセスする分には問題なし。char*でのアクセスは本来は違反にしたいのだろうが、バイト単位でアクセスしたいケースはあまりに多く、既存のコードで問題が出すぎるためか、OKになっている。ただし、元々共用体型でない変数を共用体のポインタで受けるようにしたり、共用体メンバを別のポインタに受け直してしまうとこれも違反になる。英文が読めるなら、一度くらいはGCCのドキュメント原文(リンクはGCC4.4.3のもの)を読んでおくと良い。

一応strict aliasing ruleの根拠を示しておくと、ISO/IEC 9899:1999は6.5 Expressionsの7で

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

-a type compatible with the effective type of the object,

-a qualified version of a type compatible with the effective type of the object,

-a type that is the signed or unsigned type corresponding to the effective type of the object,

-a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,

-an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or

-a character type.

としている。ネット上を検索してみると根拠を6.5.16.1 Simple assignmentの3に求める向きもあるようだが、そちらは同じメモリ領域同士での代入(レジスタでいえば"mov ax, ax"的なもの)に関して述べているもので、strict aliasing ruleのことではないと思う。

-f(no-)omit-frame-pointer

関数のスタックフレームに関する処理を省略可能にするか。

x86で言えば、スタック操作は通常ebp経由で行われるため、関数先頭で以下のアセンブリコードが出力される。

# AT&T形式
    push %ebp       # ebpを待避
    mov  %esp,%ebp  # スタックアドレスをebpで固定
# intel形式
    push ebp        # ebpを待避
    mov  ebp,esp    # スタックアドレスをebpで固定

逆に関数の末尾ではpop ebpが行われ、スタック(esp)とebpを復元する処理が入る。

これらを省くことで、特にスタックフレームの生成コードが割合的に大きくなるような、小さな関数ではコードが速く小さくなることがある。この場合スタックにはesp経由でアクセスするようになるが、espのアドレッシングモードが豊富ではないことから、却って遅く大きくなることもあるので注意。

CPUが異なれば効果も異なり、-O2で自動的に仮定されるプラットフォームもある模様。

-mpreferred-stack-boundary=N

スタックのアラインを指定。メモリ使用量と実行速度に関係してくる。通常使用すべきではない。

少なくともx86では、gccはデフォルトで16byteアライン(N=4)するが、メモリ資源の少ない環境などで任意の2^Nに揃えることができる。N=1は32bit CPUではアホなので最低N=2にすべき。

これでコンパイルされた関数から別の関数を呼ぶとそちらのアラインが崩れるので、最下層の関数にしか使用すべきではない。またはglibcを含めた全ての物を同じアラインでコンパイルし直すべき。これを避けるため、ライブラリ側ではespから16の倍数を引いてスタックを確保するのではなく、espにand演算で強制的に16byteアラインにすることもある模様。

デフォルトの16byteアラインの場合、リターンアドレスとスタックフレームポインタ(x86で言えばebp)のpushで計8byte消費するので、引数なしの関数と、引数が8byteまでの関数では同じ量のスタックが確保されることになる。

-fcall-(saved|used)-<reg>

関数内で破壊可能・不能なレジスタの指定。通常使用すべきではない。

通常、関数呼び出しのABIはCPUメーカが基準を示す。で、x86の場合cdecl呼び出し規約が標準となっていて、eax,ecx,edxが関数で破壊してもよい(呼び出され側で保護しなくて良い=呼び出し側でどこかに保存すべき)レジスタ、ebx,esi,ediが破壊してはいけない(呼び出され側で保護すべき=呼び出し側は放置でよい)レジスタになっている。

例えば-fcall-saved-ecxとすると、ecxは破壊不能レジスタになり呼び出され側関数はecx使用時にecxをpush/popし、呼び出し側関数は何もしない。-fcall-used-ebxとすると、ebxは破壊可能レジスタになり、呼び出され側関数は何もせずにebxを使用し、呼び出し側関数はebxをどこかに保存する。

saved|usedっていう表現が呼び出し側から見てなのか、呼び出され側からみてなのかわかりにくいのだと思う。呼び出し側から見て、saved(保存される)、used(破壊される)ということ。

-mfpmath

浮動小数点に自動的にSIMD命令を使用する。

x86では-mfpmath=sse等とするとSSEが使用されるようなのだが、アラインメントとかの制約がどうなるのかは具体的に検証してない。この指定を行う場合は下記-mmx等もセットで指定すべき。

gcc4.4.0のマニュアルによると、-mfpmathは387とsseの指定のみ受け付け、sseを指定された場合に下記の-msse2が指定されていればSSE2も使用されるらしい。"-mfpmath=sse,387"(またはsse+387やboth)という指定も有効で、この場合SSEと387のレジスタが物理的に分かれていれば、両方を使用してより効率的なコードを生成することが可能だが、まだ試験的機能である模様。

MMXは整数演算のみ、SSEは単精度(float:32bit)浮動小数点のみなので、倍精度(double:64bit)演算にはSSE2対応CPU(Pentium4以降)が必要になる。x87は内部で80bit精度(IEEE754の拡張倍精度である79bit以上)なので、精度的にはSSE2でもx87に劣るのだが、一般的には十分な精度なはず。

-mmmx, -msse, -msse2/3/4/4.1/4.2, -m3dnow 等

それぞれ対応する命令の使用を許可する。

同時に、対応する命令がC言語の関数形式で呼び出せるようになる。-marchを必要とする場合(AMD系)あり。この指定を行うと、上記-mfpmathの指定が無い場合でも、当該SIMD命令を発行することがあるらしい。

Pentium IIIはさすがに廃れてきたと思うので、2009年現在なら-msse2あたりが実用的かもしれない。

-m(no-)ms-bitfields

確かANSIではビットフィールドの配置方法は実装に任されており、実際のメモリ上にどのように配置されるかは仕様として確定していない。案の定、gccとマイクロソフトのVC++では異なるようで、gcc側にビットフィールドをマイクロソフト流に配置するオプションが付加されたのがこれ。

ビットフィールドの配置がコンパイラごとに異なっていても、そのビットフィールドが同じコンパイラで生成されたコード内でのみ使用されるなら問題はない。しかしWindowsのAPIに渡すようなビットフィールドがあった場合には、API側はMS流を想定し、アプリケーション側はgcc流を想定するので問題になる(ABI: Application Binary Interfaceが異なる)。

まぁ個人的には、移植性を考える製品レベルの実装の場合、インターフェースにビットフィールドを使用すべきではないと思う。ビットフィールドを使用した方が可読性が向上しコンパイラの最適化も効きやすいのだが、動作が本当に様々だ。残念ながら、ビットシフトと論理演算を組み合わせるしかない。

どうやらGTKのビルドでよく必要になるらしいのだが、ソース中で

__attribute__((__ms_struct__))

または

#pragma ms_struct [on|off|reset]

の宣言でも実現できるよう(参照1参照2,参照3)なので、自分で書く場合にはこちらを使った方が間違いが無いように思う。

なお、使用するには-mwindowsが必要になる場合がある模様だが、具体的な環境は不明。

-gstabs, -gstabs+

MinGW環境でgdbが'n'でステップオーバーではなくステップインしてしまう現象があるらしい。らしいと言うか、そういえば昔、そんな現象に遭遇した覚えがある。

その時のgccとgdbのバージョンを覚えていないのだが、前述のpostではgcc-4.2.1-sjljとgdb-6.6で発生したとのこと。これを回避するために、ワークアラウンド(次善策:最良ではないがひとまず問題を回避する方策)としてこれらのオプションを使用する。これにより、GCCが標準の-gや-gdbとは異なる形式(本来はBSD系のDBXというデバッガ用のstabsという形式らしい)でデバッグ情報を出力し、gdbで普通に'n'が効くようになるらしい。

-gstabs+の方は、stabsにさらにgdb専用の拡張情報を含む形式とのこと。どうせgdbでしかデバッグしないので、使えるのならこちらの方が良いかも知れない。

-f(no-)threadsafe-statics

C++において、ローカルスコープのstatic変数は、その宣言部分を最初に通過する際に初期化されるらしい(ISO/IEC 14882:2003では多分6.7 Declaration statementの4)。

class ClassA{
...
}
void foo(){
    static ClassA a; // これはfoo()が初めて呼ばれる際に初期化される
}

マルチスレッドプログラムではこの初期化が(ほぼ)同時に発生しうるので、危険である。これをg++が自動的になんとかしてくれるのが-fthreadsafe-statics。

最終更新時間:2010年03月08日 11時00分48秒