C ++に移植するCプロジェクトで次の構造に出くわしました。
enum TestEnum
{
A=303,
B=808
} _TestEnum;
int foo()
{
_TestEnum = B;
}
GCCでコンパイルし、生成されたコードを見ると、次のようになります。
nils@doofnase ~ $ gcc -std=c90 -O2 -c ./test.c -o test.o
nils@doofnase ~ $ size test.o
text data bss dec hex filename
59 0 0 59 3b test.o
したがって、ゼロバイトのデータまたはBSSセグメントが使用されます。
一方、C ++でコンパイルすると、次のようになります。
nils@doofnase ~ $ g++ -std=c++11 -O2 -c ./test.c -o test.o
nils@doofnase ~ $ size test.o
text data bss dec hex filename
59 0 4 63 3f test.o
予想どおり、BSSに4バイトのストレージが割り当てられています。
また、Cプロジェクトでは、列挙型定義は実際には複数のcファイルに含まれるヘッダーファイルにあります。プロジェクトは正常にコンパイルおよびリンクされます。コンパイルしてC ++としてリンクすると、コンパイラは_TestEnumが複数のオブジェクトで定義されていると文句を言います(そうです!)。
ここで何が起こっているのですか?古風なC言語の特殊なケースを見ていますか?
編集:完全な目的のために、これはgccバージョンです:
nils@doofnase ~ $ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609
デフォルトでは、GCCコンパイラはC拡張を有効にします(-pedantic
フラグが有効な場合でも)。これにより、変換ユニット全体でオブジェクトの複数の外部定義が可能になります。
C11(N1570)J.5.11を参照複数の外部定義(参考セクション):
キーワードの明示的な使用の有無にかかわらず、オブジェクトの識別子には複数の外部定義が存在する場合があり
extern
ます。定義が一致しない場合、または複数が初期化されている場合、動作は未定義です(6.9.2)。
この動作に依存するアプリケーションは、ISOC言語に厳密に準拠していないことに注意してください。より具体的には、C11 6.9 / p5外部定義は次のように述べています(私の強調):
外部定義は、関数(インライン定義以外)またはオブジェクトの定義である外部宣言です。外部リンケージで宣言された識別子が式で使用される場合(結果が整数定数である演算子
sizeof
または_Alignof
演算子のオペランドの一部として以外)、プログラム全体のどこかに、識別子の外部定義が1つだけ存在する必要があります。それ以外の場合は、1つしか存在しないものとします。161)
技術的には、そのルールに違反すると、未定義の動作が発生します。つまり、実装が診断メッセージを発行する場合としない場合があります。
この拡張機能がnm
次のコマンドで有効になっていることを確認できます。
nm test.o
0000000000000000 T foo
0000000000000004 C _TestEnum
によるとman nm
:
「C」記号は一般的です。一般的なシンボルは初期化されていないデータです。リンクすると、複数の共通記号が同じ名前で表示される場合があります。シンボルがどこかに定義されている場合、共通のシンボルは未定義の参照として扱われます。
この拡張機能を無効にするには、-fno-common
フラグを使用できます。GCCドキュメントから:
Unix Cコンパイラは、従来、共通ブロック内の初期化されていないグローバル変数にストレージを割り当ててきました。これにより、リンカは、異なるコンパイル単位内の同じ変数のすべての暫定定義を同じオブジェクトまたは非暫定定義に解決できます。これは、で指定された動作
-fcommon
であり、ほとんどのターゲットでのGCCのデフォルトです。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加