このセクションでは、 GCCのユーザに影響を及ぼす既知の問題について説明します。 これらの問題のほとんどは、 それ自体はGCCのバグではありません。 もしバグであれば、 私たちはそれを修正したでしょう。 ただし、 これらの問題がユーザに対して及ぼす結果は、 バグがもたらす結果と同じようなものかもしれません。
これらの問題のいくつかは、 他のソフトウェアのバグによるものです。 また、 それを追加するにはあまりにも労力のかかるような機能の欠如が問題であることもあります。 さらに、 何が最善であるかについて人々の意見が一致しない領域が問題となることもあります。
fixincludes
スクリプトとオートマウンタとの相性は良くありません。
システム・ヘッダ・ファイルの存在するディレクトリがオートマウントされていると、
そのディレクトリは、
fixincludes
の実行中にアンマウントされてしまう傾向があります。
これはオートマウンタのバグであるように思われます。
これをうまく回避する方法を、
私たちは知りません。
fixproto
スクリプトはときどき、
jmp_buf
型が定義されるよりも前に、
jmp_buf
型を参照するsigsetjmp
関数やsiglongjmp
関数のプロトタイプを追加することがあります。
これを回避するためには、
問題を引き起こすファイルを編集して、
プロトタイプの前にtypedefを記述してください。
以下は、 GCCをインストールする際に発生する問題 (および、 実際には何か問題があることを意味しているわけではない、 見せかけだけの問題) の一覧です。
CC
のような特定の環境変数を定義すると、
make
の動作の妨げになることがあります。
fixincludes
の実行において問題が発生するかもしれません。
これらの問題は、
結果的には、
`sys/types.h'の中にあるsize_t
の宣言の修正失敗につながります。
size_t
が有符号の型であり、
型のミスマッチが発生しているのであれば、
このことが原因である可能性があります。
解決策は、
GCCを構築するのにこのようなディレクトリを使わないことです。
gcc
ドライバ・プログラムは、
as
とld
を見つけるためにいくつかのディレクトリを探索していました。
例えば、
`/usr/local/lib/gcc-'で始まる名前のファイルを探していました。
GCCバージョン2は、
これらを、
ディレクトリ`/usr/local/lib/gcc-lib/target/version'において探します。
したがって、
例えばgas
やGNU ld
のような、
システムのデフォルトではないas
やld
を使うためには、
それらを上記のディレクトリに置かなければなりません
(あるいは、
上記のディレクトリからリンクを設定しなければなりません)。
make
がこれを無視することがあります。
多くの場合ファイルが見つからないことが原因であるこれらの異常終了は、
予期されたものであり、
無視しても問題はありません。
make
がコンパイラの一部を再コンパイルすることがあります。
このようなケースで、
調査の結果make
のバグであることが判明したものが1つあります。
この問題は無視するか、
あるいは、
GNU makeに切り替えてください。
enquire
のリンク時にエラーが発生することがあるかもしれません。
この問題を修正するには、
purifyがインストールするファイルreal-ld
を削除して、
GCCがそれを使おうと試みることのないようにしてください。
__GNU_LIBRARY__
条件式を`#if 1'に変更してください。
enquire
がハングするために、
コンパイラのビルドが永久に完了しないことがあります。
マザーボードが、
カーネルに対して浮動小数点例外を誤って報告するのです。
enquire
を実行するコマンドをパッチによりはずすことによって、
`float.h'を除くGCCをインストールすることができます。
代わりのマザーボードを手に入れることで、
この問題を本当に修正してしまうこともできるかもしれません。
この問題は、
MicronicsマザーボードのRevision Eで発生し、
Revision Fで修正されました。
また、
MYLEX MXA-33マザーボードでも発生しました。
もしこの問題に出会ったら、
コンパイルをしている間、
ソケットからFPUをはずすことを検討するとよいかもしれません。
SCO UNIXを使っている場合には、
別の方法として、
リブートしてFPUを強制的に無視させることもできます。
これをするには、
`hd(40)unix auto ignorefpu'を実行します。
ln /etc/emulator.rel1 /etc/emulatorをスーパー・ユーザとして実行し、 そのあとでシステムをリブートしてください (デフォルトのエミュレータ・ファイルは、 `emulator.dflt'という名前で残ります)。 SCOのシステムでこのような問題が発生する場合には、 `/etc/emulator.att'を使ってみてください。 このような問題を持つ別のシステムにEsixがあります。 Esixが正しく動作する代替エミュレータを持っているかどうかを私たちは知りません。 NetBSD 0.8では、 これと類似の問題が、 以下のようなエラー・メッセージが出力されることで表面化します。
enquire.c: In function `fprop': enquire.c:2328: floating overflow
genflags
やgenoutput
というプログラムがクラッシュするのに気が付くかもしれません。
これはsh
のバグのためであると言われています。
手作業でgenflags
やgenoutput
を実行してからmake
を再試行することによって、
おそらくこの問題を回避することができるでしょう。
pkginfo
コマンドを使ってください。
また、
ある任意選択パッケージを追加するには、
pkgadd
を使ってください。
さらに詳しい情報については、
Solarisのドキュメントを参照してください。
Solarisの2.0と2.1では、
GCCは6個のパッケージを必要とします。
それらは、
`SUNWarc'、
`SUNWbtool'、
`SUNWesu'、
`SUNWhea'、
`SUNWlibm'、
`SUNWtoo'です。
Solaris 2.2では、
GCCはさらに7個目のパッケージとして`SUNWsprot'を必要とします。
PATH
から`/usr/ucb'を取り除くことです。
add.d
のような浮動小数点命令を埋め込むと、
アセンブラが文句を言ってくるでしょう。
fixincludes
によって修正されない限り、
GCCとの組み合わせでは使えません。
これは、
GCCのビルドにおいて問題を引き起こします。
GCCがインストールされてしまえば、
それ以上問題は発生しません。
この問題を回避するには、
ステージ1のコンパイラを作成する際に、
以下のオプションをmakeに対して指定してください。
GCC_FOR_TARGET="./xgcc -B./ -I./include"また、 ステージ2とステージ3を作成する際には、 以下のオプションを指定してください。
CFLAGS="-g -I./include"
alloca
を使うコードをリンクする際に、
いくつかのバージョンのMIPSリンカは診断(assertion)失敗を通知してきます。
これはリンカのバグであり、
将来のリビジョンで修正されるべきものです。
この問題を防ぐために、
ユーザが明示的に`-shared'や`-call_shared'を渡さない限り、
GCCはリンカに対して`-non_shared'を渡します。
ld fatal: failed to write symbol name something in strings table for file whateverこれは、 ディスクが満杯になってしまったか、 ULIMITの制限のためにファイルが必要なサイズにまで大きくなることができないということを示唆しています。 この問題は、 カーネル・パラメータ
MAXUMEM
が小さすぎるために引き起こされることもありえます。
この場合には、
カーネルを再構築してその値をずっと大きくしなければなりません。
デフォルトの値は1024とのことです。
この値を32768にすれば正しく動作すると言われています。
これより小さい値でも正しく動作するかもしれません。
/usr/local/lib/bison.simple: In function `yyparse': /usr/local/lib/bison.simple:625: virtual memory exhaustedのようなエラーが出るのであれば、 これもまた、 ディスク・スペース、 ULIMIT、
MAXUMEM
に問題のあることを示唆しています。
MAXUMEM = 4096
_floatdisf cc1: warning: `-g' option not supported on this version of GCC cc1: warning: `-g1' option not supported on this version of GCC ./xgcc: Internal compiler error: program as got fatal signal 11
altdorf.ai.mit.edu
から
`archive/cph/hpux-8.0-assembler'という名前のファイルをanonymous ftpすることによって、
パッチをあてたアセンブラが入手できます。
HP社とソフトウェア・サポート契約を締結していれば、
直接HP社からこのパッチを入手することもできます。
このパッチについては、
以下の引用に示すような説明がされています。
このパッチは、 PHCO_4484としても知られています。これは、 浮動小数点定数があるとアセンブラがアボートするという問題SR#1653-010439を解消するために パッチをあてたアセンブラです。
このバグは実際にはアセンブラの中にあるのではなく、 関数"cvtnum(3c)"の共用ライブラリ版にあります。 "cvtnum(3c)"のバグはSR#4701-078451です。 添付されているアセンブラでは、 "cvtnum(3c)"のアーカイブ・ライブラリ版を使っているので、 このバグが現れることはありません。 (17)
fixproto
はシステムのシェルのバグを引き起こします。
バージョン8.07以降のバージョンではこのようなことは起こりません。
この問題に出会ったら、
オペレーティング・システムをアップグレードするか、
fixproto
を実行するのにBASH(GNUシェル)を使ってください。
muldi3
をコンパイルする際にGCCがクラッシュするのであれば、
この問題が存在するということです。
バージョン1のGCCを入手、
インストールして、
それを使ってバージョン2のGCCをコンパイルすれば、
うまくいく可能性があります。
Pyramid Cコンパイラのバグは、
バージョン1のGCCには影響を及ぼさないようです。
va_arg
の再定義に関する警告やエラーが出るでしょう。
この場合には、
ほとんどのプログラムをライブラリ`iclib.a'とリンクする必要があります。
また、
`stdio.h'を以下のように修正しなければなりません。
#if defined(__i860__) && !defined(_VA_LIST) #include <va_list.h>という2行の前に、
#if __PGC__という1行を追加し、 さらに、
extern int vprintf(const char *, va_list ); extern int vsprintf(char *, const char *, va_list ); #endifという3行の後ろに、
#endif /* __PGC__ */という1行を追加します。 これらの問題は、 バージョン1.1のオペレーティング・システムには存在しません。
./fixproto: sh internal 1K buffer overflowこれを修正するには、 fixproto スクリプトの最初の行が以下のようになるよう変更します。
#!/bin/ksh
いくつかの理由により、 特定のマシン上においてクロス・コンパイル処理を行う際に問題に直面することがあるかもしれません。
REAL_VALUE_TYPE
のようなマクロを定義することによって、
これらの問題を克服することが可能です。
しかし、
これを行うのは、
個々のターゲット・マシンについて相当な量の作業となります。
@xref{Cross-compilation}.
このセクションでは、 GNU CやGNU C++を他のコンパイラと一緒に使う場合や、 特定のシステム上のアセンブラ、 リンカ、 ライブラリ、 デバッガと一緒に使う場合に直面する様々な問題を一覧にして示します。
fixincludes
によって修正されない限り、
GCCとの組み合わせでは正しく機能しません。
fixincludes
によって修正されたヘッダ・ファイルは、
新規ディレクトリに移されます。
GCCは、
`/usr/include'よりも前にこのディレクトリを探索します。
`-I/usr/include'を使うと、
修正されたヘッダ・ファイルよりも前に`/usr/include'を探索するよう
GCCに対して指示することになります。
結果的に、
未修正のヘッダ・ファイルが使われることになります。
`-I/usr/include'ではなく、
(Cのプログラムをコンパイルする際には)
以下のオプションを使うべきです。
-I/usr/local/lib/gcc-lib/target/version/include -I/usr/includeGCCは、 C++のプログラムについても、 標準Cサブルーチンに対するC++インターフェイスを定義する特別なディレクトリを使います。 このディレクトリもまた、 それが優先されるよう、 他の標準インクルード・ディレクトリより先に探索されることになっています。 C++プログラムをコンパイルする際にインクルード・ディレクトリを明示的に指定するのであれば、 まず以下のオプションを最初に指定して、 そのあとで上記の2つのオプションを指定してください。
-I/usr/local/lib/g++-include
double
型の値を8バイト境界に境界整列します。
また、
すべてのdouble
型の値が8バイト境界に境界整列されているものと期待しています。
Sun社のコンパイラは通常、
double
型の値を8バイト単位に境界整列しますが、
1つだけ例外があります。
double
型の関数引数は境界整列されない可能性があります。
この結果、
Sun CCによりコンパイルされたある関数がdouble
型の引数のアドレスを取り、
このdouble *
型のポインタをGCCによりコンパイルされた関数に渡すと、
そのポインタを使ってそのポインタにより指し示される実体にアクセスしようとしたときに
致命的なシグナルの発生する可能性があります。
この問題を解決するための1つの方法は、
プログラム全体をGCCを使ってコンパイルするということです。
別の解決策として、
Sun CCによりコンパイルされる関数を、
引数を局所変数にコピーするように変更するという方法もあります。
局所変数は常に正しく境界整列されます。
第3の解決策として、
ポインタを使う関数において直接`*'を使うのではなく、
下に示す関数access_double
を経由して実体にアクセスするよう変更するという方法もあります。
inline double access_double (double *unaligned_ptr) { union d2i { double d; int i[2]; }; union d2i *p = (union d2i *) unaligned_ptr; union d2i u; u.i[0] = p->i[0]; u.i[1] = p->i[1]; return u.d; }ポインタを使って値を格納する場合も、 同じ共用体を使って同様に行うことができます。
malloc
関数は、
4バイト境界にしか境界整列されていないメモリを割り当てることがあります。
Sparc上のGCCはdouble型が8バイト境界に境界整列されることを想定していますので、
`libmalloc.a'ライブラリにより割り当てられたメモリ内にdouble型の値が存在すると、
致命的なシグナルがその結果として発生する可能性があります。
解決策は、
`libmalloc.a'ライブラリを使わないことです。
その代わりとして、
`libc.a'のmalloc
関数やこれに関連する関数を使ってください。
こちらの関数群にはこのような問題はありません。
_dlclose
、
_dlsym
、
_dlopen
というシンボルが未定義になるようであれば、
MIT版X Windowの`mit/util/misc/dlsym.c'というファイルを使ってコンパイルとリンクを行ってください。
cc
がGCCを正しくコンパイルしません。
今のところ原因は分かっていませんが、
9.01よりも前のバージョンのHP-UX上でコンパイルされたGCCは、
HP-UX 9.01上でも正しく動作しますし、
9.01上でGCC自身をコンパイルすることができます。
alloca
や可変サイズの配列を使う関数に対して正しく動作しなくなります。
原因は、
GCCが、
このような関数に対してHP-UXのアンワインド記述子(unwind descriptor)を生成しないということにあります。
これを生成することは不可能ですらあるかもしれません。
(warning) Use of GR3 when frame >= 8192 may cause conflict.これらの警告は無害ですので、 無視しても問題ありません。
as -u < /dev/nullこのコマンドが正常に終了すれば、 修正版のアセンブラは既にインストールされています。 アセンブラが、 "-u"は未知のフラグであると文句を言ってくるようであれば、 修正版を要求する必要があります。
foo
は未定義シンボルであるとリンカが報告してきます。
extern int foo; ... foo ... static int foo;このような振る舞いは、 他のほとんどのシステム上での振る舞いとは異なりますが、 バグではありません。
extern
変数をstatic
として再定義した場合の振る舞いは、
ANSI Cでは未定義となっているからです。
size_t
のtypedefが2つあるためにエラーになります。
size_t
の定義のまわりに以下の行を追加することによって、
`sys/types.h'を変更しなければなりません。
#ifndef _SIZE_T #define _SIZE_T typedefはここに置く #endif
-fcall-saved-r2 -fcall-saved-r3 -fcall-saved-r4 -fcall-saved-r5
-L/usr/local/lib/gcc-lib/we32k-att-sysv/2.8.1 -lgcc -lc_s最初のオプションは、 `-lgcc'オプションにより指定されたライブラリ`libgcc.a'を見つけるべき場所を指定しています。 GCCは、
cc
と同様、
ld
を起動することによってリンク処理を行います。
ld
を起動するのに使ったコンパイル処理プログラムの種類によってリンク処理に違いの出る理由がありません。
誰かがこの問題の原因を追求すれば、
おそらく簡単に問題を解決することができるでしょう。
ecvt
、
fcvt
、
gcvt
のバグによるものです。
これらの関数は、
正当な浮動小数点数が与えられたにもかかわらず、
`NaN'(18)を出力することがあります。
プログラムによっては、 コンパイルに問題のあることがあります。
#ifdef __STDC__ #define NeedFunctionPrototypes 0 #endif
-traditional -Dvolatile=__volatile__ -I/usr/include/sun -I/usr/ucbinclude -fpcc-struct-returnこのほとんどは、 GCC 2.4.5以降のバージョンでは必要ありません。 `config.sh'の中で、
ccflags
を
(元のオプションの中の`-traditional'により暗黙のうちに指定される)
`-fwritable-strings'に、
cppflags
を空にそれぞれ設定した後に、
`./doSH; make depend; make'を実行することで、
正しく動作するPerlを作成することができます。
MALLOC=/usr/local/lib/libgmalloc.aあるいは、 Emacs 19の`gmalloc.c'をコンパイル済みであれば、 そのオブジェクト・ファイルを`gmalloc.o'にコピーして、 GCCを再リンクする際に以下のオプションを使ってください。
MALLOC=gmalloc.o
GNU Cと既存の (ANSIに対応していない) ほとんどのCとの間には、 注目すべき非互換性がいくつかあります。 `-traditional'オプションを使うと、 GNU Cに対して他のCコンパイラと似た振る舞いをするよう指示が行われ、 これらの非互換性の多くが取り除かれます。 しかし、 すべての非互換性が取り除かれるわけではありません。
mktemp
を呼び出すことができません。
関数mktemp
は常に、
引数が指し示す文字列を書き換えます。
もう1つの結果として、
いくつかのシステムでは、
書式文字列や入力として文字列定数が渡された場合にsscanf
が正しく動作しません。
これは、
sscanf
が誤って文字列定数の中に書き込みを行おうとするからです。
fscanf
やscanf
についても同様のことが言えます。
これらの問題に対する最善の解決策は、
プログラムを変更して、
このような目的では文字列定数を使わず、
初期化文字列を指定したchar
の配列変数を使うようにすることです。
これができない場合は、
`-fwritable-strings'フラグを使うことができます。
このフラグは、
ほとんどのCコンパイラと同様の方法で文字列定数を取り扱うよう、
GCCに対して指示するものです。
他のオプションでは、
`-traditional'もまたこのような効果を持っています。
-2147483648
は正の数です。
これは、
2147483648がint
型に収まらないため、
(ANSI Cのルールにしたがい)
そのデータ型がunsigned long int
となるからです。
この値の符号を負に変えても、
再び2147483648という値になります。
#define foo(a) "a"この場合GCCは、 引数aが何であろうと
"a"
を出力します。
`-traditional'オプションは、
このような場合に旧式の
(非ANSIの)
方法で処理するようGCCに指示します。
setjmp
とlongjmp
を使うと、
有効であり続けることが保証される唯一の自動変数は、
volatile
宣言されたものだけです。
これは、
自動レジスタ割り当てによってもたらされる結果です。
以下の関数を考えてみてください。
jmp_buf j; foo () { int a, b; a = fun1 (); if (setjmp (j)) return a; a = fun2 (); /*ここで、longjmp (j)
may occur infun3
. */ return a + fun3 (); }
longjmp
が呼び出されたときに、
a
はその最初の値に復元されることもありますし、
されないこともあります。
a
がレジスタの中に割り当てられていれば、
その最初の値が復元されます。
これ以外の場合は、
最後に格納されていた値が保持されます。
`-O'オプションとともに`-W'オプションを使うと、
そのような問題の発生する可能性があるとGCCがみなした場合には、
警告が出力されます。
`-traditional' オプションは、
setjmp
を呼び出す関数の中の変数を、
レジスタの中にではなく、
デフォルトでスタックに割り当てるようGNU Cに指示します。
これにより、
伝統的なCコンパイラのような振る舞いをするようになります。
foobar ( #define luser hack)ANSI Cはこのような構文を認めていません。 `-traditional'が使われた場合にこれをサポートするというのは道理には合っているでしょうが、 それを実装するのはあまりにも大変な作業です。
extern
宣言は、
たとえそれがあるブロックの中で行われたとしても、
ファイルの残りの部分全体に影響を与えます。
`-traditional'オプションは、
伝統的なコンパイラのように、
すべてのextern
宣言を広域宣言として扱うようGNU Cに指示します。
long
等をtypedefされた名前と結合させることができます。
typedef int foo; typedef long foo bar;ANSI Cでは、 このようなことは許されません。
long
やほかの型修飾子は、
明示的にint
を必要とします。
この基準はCのコードとしてではなくBisonの文法規則として表現されているため、
`-traditional'フラグを使ってもこれを変更することはできません。
#if 0 You can't expect this to work. #endifこのような問題に対する最善の解決策は、 `/*...*/'によって境界の定められた本当のCのコメントの中に このテキストを入れてしまうことです。 また、 `-traditional'を使うと、 このようなエラー・メッセージは出力されなくなります。
time
を実際には宣言していませんでした。
したがって、
ユーザ・プログラムがこの関数の戻り値の型をどう宣言しようが、
問題はありませんでした。
しかし、
ANSI Cヘッダを持つシステムでは、
time
はtime_t
型の戻り値を持つものとして宣言されています。
time_t
型がlong
型と同じではないのであれば、
`long time ();'という宣言は誤りです。
解決策は、
time
の戻り値の型としてtime_t
を使うようユーザ・プログラムを変更することです。
float
を戻り値とする関数をPCCでコンパイルすると、
PCCはそれをdoubleに変換します。
GCCは実際にfloat
を返します。
PCCとの互換性が気になるのであれば、
double
を返すよう関数を宣言するべきでしょう。
そして、
そのようにしたことの意図を明記しておくのがよいでしょう。
STRUCT_VALUE
とSTRUCT_INCOMING_VALUE
が、
このアドレスをどこに入れて渡すべきかをGCCに対して通知します。
これに対して、
ほとんどのターゲット・マシン上のPCCが構造体や共用体を返す方法は、
構造体や共用体のサイズにかかわりなくそのデータを静的な記憶領域にコピーして、
その記憶域のアドレスをあたかもそれがポインタ値であるかのように返す、
というものです。
呼び出し側は、
そのメモリ領域から、
値が要求されている領域へデータをコピーしなければなりません。
この方法は速度も比較的遅く、
再入可能でもないので、
GCCはこの方法を使っていません。
いくつかの比較的新しいマシン上では、
構造体や共用体を戻り値として返すすべての場合において、
PCCは再入可能な規約を使っています。
このようなマシンのほとんどにおいてGCCは、
構造体や共用体をメモリ上に置いて返す場合にはこれと互換性のある規約を使用していますが、
サイズの小さい構造体や共用体はやはりレジスタに入れて返しています。
オプション`-fpcc-struct-return'を使うことによって、
構造体や共用体を戻り値として返すすべての場合において互換性のある規約を使うよう
GCCに指示することができます。
GCCは、 いくつかのシステム・ヘッダ・ファイルを訂正したものをインストールしておくことを必要とします。 これは、 ほとんどのターゲット・システムのヘッダ・ファイルには、 未修正のままではGCCと組み合わせて使うことができないものがあるからです。 バグを持つものもありますし、 ANSI Cと非互換のものもあります。 また、 他のコンパイラの特殊な特徴に依存しているものもあります。
GCCをインストールすると、
fixincludes
と呼ばれるプログラム
(あるいは、
あるターゲット・マシンでは、
例えばfixinc.svr4
のような代わりのプログラム)
を実行することにより、
修正されたヘッダ・ファイルが自動的に作成され、
インストールされます。
通常ユーザは、
このことに注意を払う必要はありません。
しかし、
このプログラムが自動的には正しいことをしてくれないケースもあります。
fixincludes
スクリプトの実行が失敗するからです。
このことは、
システム・ヘッダ・ファイルのバグを原因とする問題にユーザが直面するであろうことを意味しています。
これがGCCの責任ではないということは何の慰めにもならないかもしれませんが、
これについて私たちにできることは何もないということです。
GCCは、 ISO/ANSI C標準がconforming freestanding implementation (適合する自立した実装) と呼ぶところのものに、 単独でなることを試みています。 このことは、 すべてのANSI C言語の特徴だけでなく、 `float.h'、 `limits.h'、 `stdarg.h'、 `stddef.h'の内容すべてが利用可能であることを意味しています。 これ以外のCライブラリは、 オペレーティング・システムのベンダによって提供されます。 そのCライブラリがC標準に適合しないのであれば、 ユーザ・プログラムは予期しない警告を (特に`-Wall'を使っている場合に) 受けるかもしれません。
例えば、
Cの標準ではsprintf
関数はint
を戻り値とすることになっていますが、
SunOS 4.1.3上のsprintf
関数はchar *
を戻り値とします。
fixincludes
プログラムが、
この関数のプロトタイプを標準に合うように変更することは可能でしょうが、
この場合でもその関数自体は依然としてchar *
を返すのですから、
このような変更は正しくありません。
標準に準拠したライブラリが必要であれば、
それを自分で探す必要があります。
GCCは標準に準拠したライブラリを提供していません。
(glibc
と呼ばれる)GNU Cライブラリは、
いくつかのオペレーティング・システムに移植されており、
ANSI/ISO、
POSIX、
BSD、
SystemVとの互換性を提供しています。
あるいは、
より新しいライブラリが利用可能になっているかどうかを、
オペレーティング・システムのベンダに問い合わせてみることもできるでしょう。
以下の問題が存在することは残念ではありますが、 実際にこれを回避するための方法を私たちは1つも知りません。
int foo (struct mumble *); struct mumble { ... }; int foo (struct mumble *x) { ... }このソース・コードは本当に誤りです。 何故なら、 プロトタイプ中の
struct mumble
のスコープは、
このstruct mumble
を含む引数リストの中に限定されているからです。
このstruct mumble
は、
その直後にファイル・スコープをもって定義されているstruct mumble
を参照しません。
これらは、
異なるスコープの中で類似の名前を持つ、
互いに無関係な2つの型です。
しかし、
foo
の定義においては、
ファイル・スコープを持つ型を受け継ぐことができますから、
それが使われます。
このために定義とプロトタイプが一致しないので、
エラーとなるのです。
このような振る舞いはばかげていると思われるかもしれませんが、
ANSI標準がこれを規定しているのです。
struct mumble
の定義をプロトタイプよりも上に移動すれば、
このソース・コードは正しく機能するようになりますが、
これは極めて簡単なことです。
上記のような例においてエラーになることを回避するためだけの目的で、
ANSI Cとは非互換にする値打ちはありません。
include
を削除してから、
再度`make install'を実行します。
double
型に収まらないからです。
コンパイルされたコードは、
メモリと浮動小数点レジスタとの間で都合のいいように値を移動します。
値をメモリへ移動すると、
その値は切り捨てられます。
この問題は、
`-ffloat-store'オプションを使うことにより部分的に回避することができます
(最適化を制御するオプションを参照)。
C++は複雑な言語であり、 しかも、 進化しつつある言語です。 その標準定義 (ISO C++標準) はつい最近完成したばかりです。 その結果、 C++コンパイラがときどきユーザを驚かすことがあります。 C++コンパイラの振る舞いが正しいような場合にすらそのようなことが起こります。 このセクションでは、 この種の質問が頻繁に持ち上がるようなところについて議論します。
あるクラスが静的なデータ・メンバを持つ場合には、 その静的メンバを宣言するだけでは十分ではなく、 その定義もしなければなりません。 例えば、
class Foo { ... void method(); static int bar; };
この宣言は単に、
クラスFoo
がFoo::bar
という名前のint
型のメンバと
Foo::method
という名前のメンバ関数を持つ、
ということをはっきりさせるだけです。
まだ、
method
とbar
の両方をどこかほかのところで定義する必要があります。
ANSIのドラフト標準によれば、
1つの(ただ1つの)ソース・ファイルの中で初期化子を提供しなければなりません。
例えば、
以下のようになります。
int Foo::bar = 0;
他のC++コンパイラは、
この標準の振る舞いを正しく実装していないかもしれません。
その結果、
このようなコンパイラからg++
に切り替えた人は、
以前は正しく動作していたように見えたプログラムが、
実際には標準に従っていないということに気が付くことになるかもしれません。
g++
は、
定義されていない静的データ・メンバを見つけると、
それを未定義シンボルとして報告してきます。
テンポラリ・オブジェクトの一部分に対するポインタや参照を使うことは危険です。
コンパイラが、
予想よりも早くそのオブジェクトを削除してしまい、
ポインタがゴミ(ガーベッジ)を指すようになってしまうことが当然ありえます。
この問題がもっともよく発生するのは、
string
クラスのようなクラスです。
特に、
char *
型やconst char *
型への変換関数が定義されているクラスにおいて顕著です。
標準のstring
クラスにおいてc_str
メンバ関数を呼び出すことが要求されるのは、
1つにはこのためです。
何らかの内部構造に対するポインタを返すクラスはいずれも、
潜在的にこのような問題を持つ可能性があります。
例えば、
あるプログラムが、
string
オブジェクトを返す関数strfunc
と
char
型へのポインタを操作する別の関数charfunc
を使うものとしましょう。
string strfunc (); void charfunc (const char *); void f () { const char *p = strfunc().c_str(); ... charfunc (p); ... charfunc (p); }
このような状況において、
繰り返しc_str
を呼び出すのではなく、
c_str
メンバ関数の返したC文字列へのポインタを待避しておいて、
そのポインタを繰り返し使うのは道理に合ったことのように見えるかもしれません。
しかし、
strfunc
の呼び出しによって作成されたテンポラリstringオブジェクトはp
の初期化の後に破棄され、
その時点で、
p
は解放されたメモリ域を指すことになります。
ほかのコンパイラでは、 このようなコードもうまく動くことがあるかもしれません。 特に、 通常の局所変数とともにテンポラリ・オブジェクトを削除する、 時代遅れのcfrontベースのコンパイラであればそうでしょう。 しかし、 GNU C++の振る舞いは標準に適合しています。 テンポラリ・オブジェクトが遅いタイミングで削除されるということに依存するプログラムには、 移植性がありません。
このようなコードを安全に記述する方法は、 テンポラリ・オブジェクトに名前を付けることです。 こうすれば、 テンポラリ・オブジェクトはその名前のスコープの終端に達するまで存在し続けます。 例えば、 以下のようにします。
string& tmp = strfunc (); charfunc (tmp.c_str ());
ある基底クラスが仮想基底クラスである場合、 個々のオブジェクトは、 その基底クラスのサブオブジェクトを1つだけ持ちます。 また、 生成関数と消去関数はただ1回だけ、 継承階層の最下位にあるクラスから実行されます。 しかし、 このようなオブジェクトも、 代入された場合の振る舞いは規定されていません。 例えば、 以下の例を考えてみましょう。
struct Base{ char *name; Base(char *n) : name(strdup(n)){} Base& operator= (const Base& other){ free (name); name = strdup (other.name); } }; struct A:virtual Base{ int val; A():Base("A"){} }; struct B:virtual Base{ int bval; B():Base("B"){} }; struct Derived:public A, public B{ Derived():Base("Derived"){} }; void func(Derived &d1, Derived &d2) { d1 = d2; }
C++標準の規定によれば、 Derivedオブジェクトを生成またはコピー生成する際に、 `Base::Base'はただ1回だけ実行されます。 (この例における`func'のように) Derivedオブジェクトのコピー代入(copy-assignment)が暗黙のうちに実行されるときに、 `Base::operator='が1回以上呼び出されるかどうかについての規定はありません。
g++は、
コピー代入に関して「直観的」アルゴリズムを実装しています。
すなわち、
直接の基底クラスをすべて代入して、
その後に、
すべてのメンバを代入します。
このアルゴリズムでは、
仮想基底クラスのサブオブジェクトに何回も出くわす可能性があります。
上記の例では、
コピー処理は、
`val'、
(strdup
による)`name'、
`bval'、
再び`name'の順番で進行します。
アプリケーション・コードがコピー代入に依存するのであれば、 ユーザ定義のコピー代入演算子を定義することによって、 不確実性を取り除くことができます。 アプリケーションは、 ユーザ定義のコピー代入演算子を定義することによって、 仮想基底クラスのサブオブジェクトの代入の有無とその方法を定義することができます。
protoize
の使用に関する警告
変換プログラムprotoize
とunprotoize
は、
再修正をしないと正しく機能しなくなるような変更を
ソース・ファイルに対して加えてしまうことがときどきあります。
protoize
は、
型が定義されるよりも前に、
その型の名前への参照やその型のタグへの参照を挿入することがあります。
また、
型が定義されていないファイルの中において、
その型の名前への参照やその型のタグへの参照を挿入することもあります。
このようなことが起こった場合でも、
コンパイラのエラー・メッセージによって、
新しく挿入された参照がどこにあるかが分かるはずですので、
そのファイルを手作業で修正するのは複雑なことではありません。
protoize
には理解することのできないCの構文があります。
例えば、
関数ポインタ変数を宣言する際の引数の型を決定することはできません。
これは手作業で行わなければなりません。
protoize
は、
そのような変数を見つけるたびに`???'という文字列を含むコメントを挿入します。
したがって、
このような変数はすべて、
この文字列を検索することによって見つけることができます。
ANSI Cでは、
関数ポインタ型において引数の型を宣言することは必須ではありません。
unprotoize
を使うと、
簡単にバグが入り込んでしまうことがあります。
プロトタイプの作用により引数の型変換が行われることをあてにしているプログラムでは、
プロトタイプがなくなればこのような変換は行われなくなってしまいます。
unprotoize
を使うことが安全であると確信できる1つのケースは、
protoize
により生成されたプロトタイプを除去する場合です。
プロトタイプが生成される以前からプログラムが正しく動作していたのであれば、
プロトタイプを除去した後も正しく動作するでしょう。
この問題が発生する可能性のある箇所はすべて、
`-Wconversion'オプションを使ってプログラムをコンパイルすることによって見つけることができます。
これにより、
引数が変換されるところでは常に警告が表示されます。
protoize
は知ることができません。
このようなことが起こると、
protoize
はそのような関数に関しては何も変更しません。
protoize
は、
このようなケースに相当する部分を検出して、
そのことに関する警告を出力しようと試みます。
一般的には、
すべての関数が変換されるまで、
コンパイル処理において毎回異なる組み合わせの`-D'オプションを指定して段階的にprotoize
を使うことによって、
この問題を回避することができます。
しかしながら、
すべての関数が正しく変換されたことを自動的に検証する方法はありません。
unprotoize
は混乱することがあります。
このような形式パラメータ名は選択されないようお勧めします。
このセクションでは、 人々から頻繁に変更するよう要求されるにもかかわらず、 変更していない点を一覧にして示します。 これらの変更要求を採用しないのは、 そのような変更を加えないGCCの方が良いと考えられるからです。
void
へのキャストを追加することによってプログラムの中を取り散らかすことには、
何の意味もありません。
int
として宣言されたビットフィールドが有符号であるか無符号であるかは実装依存であるとしています。
このことは事実上、
2つの代替的なCの方言を作ることになります。
GNU Cコンパイラは、
どちらの方言もサポートします。
有符号となる方言については`-fsigned-bitfields'を使って、
また、
無符号となる方言については`-funsigned-bitfields'を使って、
それぞれ指定することができます。
しかしこれだけでは、
デフォルトでどちらの方言を使うのかという問題は未解決のままです。
現在のところ、
デフォルトとして選択された方言ではビットフィールドを有符号にしています。
こうするのが最も簡単であるからです。
他のあらゆるコンテキストにおいて、
int
はsigned int
と同一ですので、
ビットフィールドの中でもこの両者が同一であるのが最もすっきりしています。
いくつかのコンピュータ製造業者は、
ただのビットフィールドは無符号でなければならないと指定するアプリケーション・バイナリ・インターフェイス標準を発表しました。
しかし、
このような問題についてABIの中で触れるのは誤りです。
ただのビットフィールドをどう扱うかによってCの2つの方言が区別されるからです。
これら2つのCの方言は、
どのような種類のマシン上でも意味を持ちます。
ある特定のオブジェクト・ファイルが有符号のビットフィールドを使ってコンパイルされたか無符号のビットフィールドを使ってコンパイルされたかということは、
他のオブジェクト・ファイルには無関係なことです。
他のオブジェクト・ファイルがそのデータ構造の中の、
正にそのビットフィールドをアクセスする場合ですら、
無関係です。
ある与えられたプログラムは、
これら2つの方言のいずれか一方を使って書かれます。
このプログラムは、
適切な方言を使ってコンパイルされれば、
ほとんどすべてのマシン上で動作する見込みがあります。
逆に間違った方言を使ってコンパイルされてしまうと、
正しく動作することはほとんどありえないでしょう。
多くのユーザがGNU Cコンパイラの真価を認めているのは、
それが異なるマシン間において同一の環境を提供してくれるからです。
もしこのコンパイラが、
特定のマシン上においてだけ、
ただのビットフィールドに対して異なる取り扱いをすることになれば、
こうしたユーザは不便を感じることでしょう。
ユーザはときどき、
特定のマシン・タイプだけを対象にしたプログラムを書きます。
このような場合には、
そのマシン上の他のコンパイラがサポートしているのと同一の方言をGNU Cコンパイラがデフォルトでサポートしていれば、
そのユーザにとっては有益でしょう。
しかし、
このようなアプリケーションはまれにしか存在しません。
しかも、
複数のタイプのマシン上で実行させるプログラムを書くユーザにとっては、
このような種類の互換性があっても何の得にもならないでしょう。
これが、
GCCが現在も今後も、
すべてのタイプのマシン上においてただのビットフィールドを
(デフォルトでは)
同一の方法で取り扱う理由です。
すべてのマシン上においてビットフィールドのデフォルトを無符号にしようという議論があります。
例えばこれが普遍的なデファクト標準となることがあれば、
GCCがそれに追随するのも意味のあることです。
これは、
将来検討されるべきことです。
(もちろん、
移植性に強い関心を持つユーザは、
個々のビットフィールドにおいて、
それが有符号なのか無符号なのかを明示的に示すべきです。
このようにすれば、
どちらのC方言においても同一の意味を持つプログラムを書くことができます)
__STDC__
を未定義とすること。
現在GCCは、
`-traditional'が使われない限り__STDC__
を定義しています。
このことは実際に良い結果をもたらしています。
関数プロトタイプやANSIのトークン連結のような、
ANSI Cの特定の機能を使っても安全かどうかを確認するのに、
プログラマは通常、
__STDC__
に対する条件式を使います。
`gcc'はそのままでANSI Cの機能をすべてサポートしていますので、
このような確認に対する正しい答えは
「YES(安全)」です。
ユーザの中には、
ある特定のライブラリが利用できるかどうかをチェックするのに__STDC__
を使おうとする人もいます。
ANSI Cプログラムの中でこのような使い方をするのは実際には正しくありません。
というのは、
ANSI C標準によれば、
ANSI Cに準拠する自立的な(freestanding)実装は、
たとえライブラリを提供していなくても__STDC__
を定義するべきだからです。
`gcc -ansi -pedantic'によって起動されたGCCはANSI Cに準拠する自立的な実装となるので、
ANSI Cライブラリが付属していなくても__STDC__
を定義することが要求されるのです。
ときどき、
ANSI C標準に完全には準拠していないコンパイラにおいて__STDC__
を定義することは標準に違反している、
ということが言われます。
これは非論理的です。
ここでの標準とは、
`gcc -ansi'によって起動されたGCCのように、
ANSI Cをサポートしていると主張するコンパイラに対する標準であり、
単に`gcc'によって起動されたGCCのような、
それ以外のコンパイラに対する標準ではありません。
ANSI Cで規定されていることが、
`-ansi'の指定されていない単なる`gcc'の設計にも関連してくるのは、
実用主義的な理由からであって、
要件としてではありません。
GCCは通常__STDC__
を1として定義し、
さらに、
`-ansi'オプションが指定されている場合には__STRICT_ANSI__
を定義します。
いくつかのホストでは、
システムのインクルード・ファイルがこれとは異なる規約を使っています。
そこでは、
__STDC__
は通常は0であり、
ユーザがC標準への厳密な準拠を指定した場合には1となります。
GCCは、
システムのインクルード・ファイルを処理する際にはそのホストの規約に従いますが、
ユーザ・ファイルを処理する際には通常のGNU Cの規約に従います。
__STDC__
を未定義とすること。
C++からCへのトランスレータを使ってコンパイルするよう書かれたプログラムは、
後に使われることになるCコンパイラによって定義される__STDC__
の値を受け取ります。
このようなプログラムは、
コンパイラが使うCプリプロセッサの種類を決定するのに__STDC__
をテストしなければなりません。
プログラムの中でANSI C方式でのトークン連結を行うべきか、
昔から使われている方式のトークン連結を行うべきかを決めるのにこれが必要です。
これらのプログラムは、
__STDC__
が定義されていればGNU C++でも正しくコンパイルできます。
__STDC__
が定義されていなければコンパイルできないでしょう。
さらに、
多くのヘッダ・ファイルは、
昔から使われている形式ではなくANSI C形式のプロトタイプを提供するように書かれています。
これらのヘッダ・ファイルの多くは、
__STDC__
が定義されてさえいれば、
C++においても変更することなく使えます。
__STDC__
が定義されていなければ、
これらのヘッダ・ファイルはすべてコンパイルに失敗することになり、
C++コンパイラが使われているかどうかについても明示的にテストするよう
変更する必要が出てくるでしょう。
void func (int, int); int i = 2; func (i++, i++);インクリメントがある特定の順序で評価されるという保証は (CとC++のどちらの標準言語定義においても) ありません。 どちらのインクリメントが最初に実行されてもおかしくありません。
func
は、
`2, 3'という引数を受け取る可能性もありますし、
`3, 2'という引数を受け取る可能性もあります。
場合によっては、
`2, 2'という引数を受け取る可能性すらあります。
GNUコンパイラは、 2つの種類の診断を出力することができます。 エラーと警告です。 それぞれ異なる目的があります。
警告は、 プログラムが本当に意図されたとおりのことを行っているかどうかチェックして確認するべき危険な箇所を示しているかもしれません。 あるいは、 古くなってしまった機能を使っている箇所や、 GNU CやGNU C++の標準的ではない機能を使っている箇所を示しているかもしれません。 多くの警告は、 ユーザが`-W'オプションのどれかを使ってその警告を明示的に要求した場合にのみ出力されます (例えば、 `-Wall'は様々な有用な警告を要求するオプションです)。
GCCは常に、 可能であればプログラムをコンパイルしようと試みます。 プログラムの意味するところが明確であれば、 (例えば) 単に標準に正しく準拠していないという理由だけで、 いわれもなくプログラムを拒否するようなことはしません。 しかし、 CやC++の標準によってある特定の拡張が明示的に禁止されている場合があり、 そのような場合には、 標準に準拠するコンパイラは診断メッセージを出力しなければなりません。 `-pedantic'オプションは、 このような場合に警告を出力するようGCCに通知するものです。 一方、 `-pedantic-errors'はこのような場合に警告ではなくエラーを出力するよう指示します。 このことは、 ANSIに準拠していないすべての構文に対して警告やエラーが出力されることを意味しているわけではありません。
これらの点に関する詳細、 および、 関連するコマンドライン・オプションの詳細については、 警告を要求もしくは抑制するオプションを参照してください。