手元の gcc でどんなシンボルが自動定義されているかを調べるには
-v
スイッチをつけて gcc を実行します。私の場合は以下のようになり
ました。
$ echo 'main(){printf("hello world\n");}' | gcc -E -v -
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
gcc version 2.7.2
/usr/lib/gcc-lib/i486-box-linux/2.7.2/cpp -lang-c -v -undef
-D__GNUC__=2 -D__GNUC_MINOR__=7 -D__ELF__ -Dunix -Di386 -Dlinux
-D__ELF__ -D__unix__ -D__i386__ -D__linux__ -D__unix -D__i386
-D__linux -Asystem(unix) -Asystem(posix) -Acpu(i386)
-Amachine(i386) -D__i486__ -
Linux 特有の機能に依存したコードを書いている場合はその部分を以下のよう に囲っておくと良いでしょう。
#ifdef __linux__
/* ... funky stuff ... */
#endif /* linux */
この目的には __linux__
を用います。 linux
は用いるべきでは
ありません。後者は POSIX 準拠ではないからです。
コンパイラのスイッチに関する文書は gcc の info に書かれています。
(Emacs からは C-h i
として `gcc' オプションを選びます)。インス
トールに用いたバイナリ配布パッケージによってはこれが入っていなかったり
古かったりすることがあります。その場合は gcc のソースアーカイブを
ftp://prep.ai.mit.edu/pub/gnu
やミラーサイトから入手し、その中の info をコピーして使いましょう。
gcc の man ページ gcc.1
は、一言で言ってしまうと内容が古いです。
これは man ページそのものの中でも警告されています。
gcc のコマンドラインに -O
n をつけると出力される
コードを最適化することができます。 n は整数です(省略すると 1 と
みなされます)。意味のある n の値とそれぞれの値に対する実質的な効
果はコンパイラのバージョンによって変わりますが、通常は 0 (最適化なし)
から 2(たくさん)あるいは 3(とてもたくさん)までが意味を持ちます。
gcc 内部ではこれらの値は -f
や -m
オプション群に展開されます。
-O
オプションのそれぞれのレベルにどのようなオプションが対応してい
るかを調べるためには gcc を -v
と -Q
オプションをつけて実行
します(後者のオプションはマニュアルには載っていません)。例えば私の場
合 -O2
に対しては以下のようになります。
enabled: -fdefer-pop -fcse-follow-jumps -fcse-skip-blocks
-fexpensive-optimizations
-fthread-jumps -fpeephole -fforce-mem -ffunction-cse -finline
-fcaller-saves -fpcc-struct-return -frerun-cse-after-loop
-fcommon -fgnu-linker -m80387 -mhard-float -mno-soft-float
-mno-386 -m486 -mieee-fp -mfp-ret-in-387
使っているコンパイラでの最も高い最適化レベルよりも高い数値を指定した場
合(例えば -O6
など)の動作は、最高レベルの最適化を指定したのと同
じになります。しかし配布するコードにこのようなやり方で最適化を指定する
のは良いアイディアとは言えません。もし将来リリースされるコンパイラで更
なる最適化が導入された場合、コードがうまく動かなくなる可能性があるから
です。
gcc 2.7.0 および 2.7.2 のユーザは、これらの版の -O2
にはバグがあ
ることに気をつけて下さい。具体的には strength reduction が動作しないの
です。 gcc を再コンパイルする場合はパッチを当てることによってこの問題
は解決できます。そうしない場合はコンパイルの際に常に
-fno-strength-reduce
を指定するようにして下さい。
-O
オプションのどのレベルでも指定はされませんが、 -m
は
有用なフラグ群です。その最たるものは -m386
と -m486
で、それ
ぞれ 386 と 486 に有利なコードを出力するように指定します。これらのオプ
ションを指定してコンパイルしたコードは、それぞれ他のチップでも動作しま
す。 486 のコードは大きくなりますが、386 の上でも遅くなることはありま
せん。
現在はまだ -mpentium
あるいは -m586
というオプションは存在し
ません。 Linus によれば -m486 -malign-loops=2 -malign-jumps=2
-malign-functions=2
を使うとアラインメントのための大きなギャップを作
ることなく 486 のコードを最適化できるそうです(Pentium ではそも
そもアラインメントが必要とされません)。 Cygnus の Michael Meissner は
こう言っています。
個人的には-mno-strength-reduce
を x86 のコードに指定すると速度は 向上すると思う(strength reduction のバグのことを言っているわけではな いことに注意されたい。それはまた別の話である)。 x86 CPU ではレジスタ が不足しやすいため、 gcc で用いている手法(レジスタ群を spill レジスタとそれ以外へグループ分けする)と相性が悪いからである。 strength reduction では乗算を加算で置き換える際により多くのレジスタを 使用する。私は-fcaller-saves
も同様に性能低下の原因になると考える。
もう一つ私見を。-fomit-frame-pointer
は有利に働く場合も 不利に働く場合もあると思う。このオプションは他のレジスタを割り当て可能 にする。一方 x86 が命令セットを解釈するやり方から考えて、スタック相対 アドレスはフレーム相対アドレスよりも大きなスペースを必要とする。したがっ てプログラムで利用できる I キャッシュが少なくなってしまう。同様に-fomit-frame-pointer
を指定するとコンパイラはスタックポインタを命 令コールのたびに再配置するが、フレームがある場合はスタックアキュムレー タを数命令で使いきってしまうことになる。
この話題の最後は再び Linus の言葉で締めくくりましょう。
最高の性能を得るには私を信じちゃいけません。テストして下さい。 gcc コ ンパイラにはたくさんのオプションが在り、その組み合せのうちの一つがあな たにとってのベストな最適化となるはずです。
Internal compiler error: cc1 got fatal signal 11
シグナル 11 は SIGSEGV または「セグメント違反」を意味します。通常こ れはプログラム中でポインタが混乱し、プログラムで管理していないメモリ領 域に書き込みを行おうとした結果です。したがってこれは gcc のバグである 可能性もあります。
しかし gcc (のほとんどの部分)は細部までテストされた信頼すべきソフト
ウェアと言えます。一方 gcc では数多くの複雑なデータ構造や無数のポイン
タを用いています。つまり通常手に入る中で最も優秀な RAM のテスターであ
るとも言えるのです。もしバグが再現されなければ(コンパイルを再び行なっ
たときに同じところで止まるのでなければ)それは多分間違いなく使っている
ハードウェア(CPU、メモリ、マザーボードまたはキャッシュ)の障害です。
システムが電源投入時のチェックをパスするからといって、あるいは Windows
で問題なく動作するからといってこの障害をバグと言ってはいけません。これ
らの『テスト』は一般に価値が無いとみなされているからです(正当な判断と
言えます!)。またカーネルのコンパイルがいつも `make zImage
' の途
中で停止するからといって、これをバグだと言ってこないでください --- そ
りゃ確かにバグかもしれませんけどね。 `make zImage
' はおそらく 200
以上のファイルをコンパイルします。我々が知りたいのはもう少し小さな範囲
なのです。
もしバグが再現できたら、また(より望むらくは)バグを引き起こす短いプロ グラムがあったら、その問題に関するバグレポートを FSF か linux-gcc メー リングリストに送りましょう。 gcc の文書を良く読んで、彼らが必要とする 情報に関して理解してからにしましょう。
最近では『もし Linux に移植されていないプログラムがあったとしたら、
それはそもそも移植されるべき価値が無いのだ』とも言われています。 :-)
もう少し真面目に。しかし一般に Linux の 100% POSIX 準拠を満たすには ソースを少々変更するだけで良いはずです。行なった変更はプログラムの原著 者にフィードバックすると良いでしょう。以降は `make' だけで実行ファイ ルができるようにしてもらえるかもしれません。
bsd_ioctl
、 daemon
および <sgtty.h>
)プログラムは -I/usr/include/bsd
をつければコンパイルでき、
また -lbsd
をつければリンクできます(つまり Makefile の
CFLAGS
に -I/usr/include/bsd
を加え、 LDFLAGS
に
-lbsd
を加えるわけです)。 BSD 形式のシグナルの振る舞いを用い
るために -D__USE_BSD_SIGNAL
を加える必要はもうありません。
-I/usr/include/bsd
を加えて <signal.h>
をインクルー
ドすれば自動的に選択されます。
SIGBUS
, SIGEMT
, SIGIOT
, SIGTRAP
, SIGSYS
など )Linux は POSIX に準拠しています。これらは POSIX で定義されているシ グナルではありません。 ISO/IEC 9945-1:1990 (IEEE Std 1003.1-1990) の B.3.3.1.1 から引用します。
「SIGBUS、 SIGEMT、 SIGIOT、 SIGTRAP、 SIGSYS の各シグナルは POSIX.1 から削除されます。これらのシグナルの振舞いは実装によって異なっており、 適当な分類ができないからです。 POSIX 準拠の実装でもこれらのシグナル を発行することは許されていますが、発行される状況は文書化しなければなり ませんし、これらシグナルの発行に関するあらゆる制限を記述しておく必要が あります。」
これを回避する安直な方法はこれらのシグナルを SIGUNUSED
として
再定義することです。正しい方法はこれらを扱っているコードを適当な
#ifdef
の組で囲うことです。
#ifdef SIGSYS
/* ... non-posix SIGSYS code here .... */
#endif
GCC は ANSI のコンパイラです。しかし現在存在する C のコードはほと
んどが ANSI 準拠ではありません。 K & R のコードに関して GCC ができ
ることはコンパイラのフラグに -traditional
を付けることぐらいです。
もう少し精密なコントロールをすることも可能ですが、これらをエミュレート
するのは各種の頭痛の種になるでしょう。詳しくは gcc の info を参照して
下さい。
-traditional
は gcc の文法を変えるだけでなく、副作用を生じること
に注意して下さい。例えば -traditional
によって
-fwritable-string
が有効になります。このスイッチにより文字列定数
はデータ領域に書き込まれます(スイッチがないとプログラムによる変更が行
われないテキスト領域に書き込まれます)。これにより、プログラムのメモリ
使用量が増加します。
ありがちなのは、汎用の関数が Linux のヘッダファイルでもマクロとして定
義されているため、プリプロセッサがコード中の同様なプロトタイプ宣言を
認めなくなるという問題です。良くあるのは atoi()
と
atol()
です。
sprintf()
(特に SunOS から移植する際に)気をつけなければならないのは、
sprintf(string, fmt, ...)
の戻り値は多くの Unix では string
へのポインタであるのに対して、 Linux では(ANSI に従い)文字列へ書き込
まれた文字数になっていることです。
fcntl
など。 FD_*
の定義はどこにあるの?<sys/time.h>
で定義されています。 fcntl
を用い
る場合は <unistd.h>
も一緒にインクルードする必要があるでしょ
う。実際のプロトタイプはここで定義されています。
一般に、関数に必要な #include
は man ページの SYNOPSIS セクショ
ンに記述されています。
select()
が一度タイムアウトするとプログラムがウェイトしなくなる昔は select()
の timeout 引数は変更されませんでした。し
かし当時でもマニュアルには以下のように書かれていました。
select() は与えられた timeout から(もしあれば)残った時間を、time の 値を置き換えることによって返すべきです。これはシステムの将来のバージョ ンでインプリメントされるでしょう。従って timeout のポインタが select() の呼び出しによって変更されないことを仮定したコードを書くのは良くありま せん。
その将来が来たわけです、少なくともここでは。
select()
から戻るとき、 timeout 引数には待ち時間の残りがセットさ
れます。データが最後まで到着しなけ
れば timeout は 0 になりますので、 timeout 構造体をそのままにしてもう
一度 select()
を呼ぶと、すぐに制御が返って来てしまいうというわけ
です。
この問題を修正するには select()
を呼ぶ度にタイムアウトの値を
timeout 構造体に代入してやれば良いのです。今までのコードが以下のような
ものだとしたら、
struct timeval timeout;
timeout.tv_sec = 1; timeout.tv_usec = 0;
while (some_condition)
select(n,readfds,writefds,exceptfds,&timeout);
このように変えて下さい。
struct timeval timeout;
while (some_condition) {
timeout.tv_sec = 1; timeout.tv_usec = 0;
select(n,readfds,writefds,exceptfds,&timeout);
}
Mosaic のあるバージョンではこの問題が残っていたことがありました。回転 する地球のアニメーションが、ネットワークから到着するデータの速度と反比 例した速さで回転したのです!
プログラムが Ctrl-Z で停止されてから再開される(あるいはシグナルを 発生する他の状況: Ctrl-C による中断や子プロセスの終了など)と、プログ ラムが "interruputed system call" や "write: unknown error" と言ったようなメッセージを出します。
POSIX のシステムでは古い UNIX よりもシグナルチェックをする局面が多 くなっています。 Linux は以下のようなシグナルハンドラを実行します。
/proc
のファイルに対する select()
、 pause()
、 connect()
、 accept()
、 read()
。write()
。open()
。ioctl()
。fcntl()
への F_SETLKE
コマンド。wait4()
、 syslog()
、 その他全ての TCP および NFS 動作。他の OS では以下のようなシステムコールが対象になる場合もあります。
creat()
、 close()
、 getmsg()
、 putmsg()
、
msgrcv()
、 msgsnd()
、 recv()
、 send()
、
wait()
、 waitpid()
、 wait3()
、 tcdrain()
、
sigpause()
、 semop()
。
もしプログラムがハンドラを持っているシグナルがシステムコールの
途中で発生すると、そのシグナルハンドラが呼び出されます。ハンドラからの
制御が(システムコールに)戻ると、システムコールは自分に対する割り込み
を検知し、ただちに -1 を返すとともに errno = EINTR
を
セットします。プログラムはこのようなことが起こるとは思っていませんから、
異常終了します。
対処法は二つあります。
(1) 導入したシグナルハンドラごとに SA_RESTART
を sigaction フラグに追加します。例えば
signal (sig_nr, my_signal_handler);
のようなものを以下のように書き換えます。
signal (sig_nr, my_signal_handler);
{ struct sigaction sa;
sigaction (sig_nr, (struct sigaction *)0, &sa);
#ifdef SA_RESTART
sa.sa_flags |= SA_RESTART;
#endif
#ifdef SA_INTERRUPT
sa.sa_flags &= ~ SA_INTERRUPT;
#endif
sigaction (sig_nr, &sa, (struct sigaction *)0);
}
これはほとんどのシステムコールに適用できますが、 read()
、
write()
、 ioctl()
、 select()
、 pause
、
connect()
の各システムコールに対しては EINTR
のチェックをプ
ログラム中で行なう必要があります。以下を参考にして下さい。
(2) EINTR
をプログラム中で明示的にチェックする。
read()
と ioctl()
に対する二つの例を示します。
まず read()
の場合です。
int result;
while (len > 0) {
result = read(fd,buffer,len);
if (result < 0) break;
buffer += result; len -= result;
}
のようなコードを以下のように書き換えます。
int result;
while (len > 0) {
result = read(fd,buffer,len);
if (result < 0) { if (errno != EINTR) break; }
else { buffer += result; len -= result; }
}
次は ioctl()
の例です。
int result;
result = ioctl(fd,cmd,addr);
これを以下のように書き換えます。
int result;
do { result = ioctl(fd,cmd,addr); }
while ((result == -1) && (errno == EINTR));
BSD Unix のバージョンによっては、デフォルトでシステムコールをやり直す
ことになっていることもあります。この場合システムコールを中断するには、
SV_INTERRUPUT
か SA_INTERRUPT
フラグを用いる必要があります。
GCC はユーザを信頼しており、文字列定数はあくまで定数として扱われる ものとみなしています。従って GCC では文字列定数はテキスト(コード)領 域に保持されます。ここはプログラムのディスクイメージにページングされま す(スワップ領域に take up される代わりに)ので、この文字列定数を書き 換えようとするとセグメント違反となります。これは仕様です!
古いプログラムの場合ではこれが問題になることがあります。例えば
mktemp()
を文字列定数を引数にして呼び出す場合などです。
mktemp()
は引数を書き換えようとするためです。
修正するには二つの方法があります。 (a) -fwritable-string
を付けて
コンパイルして gcc に定数をデータ領域に保持するよう伝える。 (b) 問題と
なる部分を定数でない文字列に strcpy して、こちらを用いる。
execl()
が失敗する間違った呼び出し方をしているからです。 execl
の最初の引数は実
行したいプログラムです。2番目以降の引数は呼び出すプログラムの
argv
配列になります。ここで argv[0]
はプログラムの
パスそのものであることに注意して下さい。従ってexecl
の呼び出しは
以下のように書く必要があります。
execl("/bin/ls","ls",NULL);
単に以下のように書くのは間違いです。
execl("/bin/ls", NULL);
少なくとも a.out の場合は、引数を全くセットせずにプログラムを実行する と、依存しているダイナミックライブラリを表示するようになっています。 ELF ではまた違った動作となります。
もしこのライブラリの情報が必要でしたら、もっと簡単なインターフェースが
あります。ダイナミックロードの節か ldd
の man ページを見て下さい。