web-dev-qa-db-ja.com

初期化されていないC文字列では警告なし

現在、小さなCプログラムのコンパイル/リンク中にGCCからエラーが発生しないのはなぜかと思っています。

version.hで次の文字列を宣言しました。

const char* const VERSION;

version.cで、変数の初期化を設定しました。

const char* const VERSION = "0.8 rev 213";

問題ありません。プログラムの残りの部分で文字列を使用できます。

Cファイルがない場合、コンパイル/リンク中にエラーは発生しませんが、変数にアクセスしようとすると、プログラムは(もちろん)SIGSEGVで失敗します。

変数VERSIONを設定する私の方法は正しいですか、またはより良い方法がありますか?または、コンパイル/リンク中にエラーが発生する可能性はありますか?

24
Max Senft

defined(宣言だけでなく)ヘッダーに変数があります。

このヘッダーを複数のソースファイルからインクルードする場合、動作はundefinedです。標準からの関連する引用は次のとおりです。

J.2未定義の動作

外部リンケージを持つ識別子が使用されていますが、プログラムには識別子の外部定義が1つだけ存在しないか、識別子が使用されておらず、識別子に複数の外部定義が存在します。

ここでは、重複tentativeデータ定義のマージである、GCC固有の(実際には多くのコンパイラーに共通ですが、まだ標準ではない)動作に依存しています。 -fcommonおよび-fno-common GCCコンパイルフラグのヘルプを参照してください。すべてのコンパイラがこのように動作するわけではありません。これは、Cが登場する前のFortranの動作方法であったため、歴史的にはリンカーの一般的な動作です。

この言語拡張を想定して、定義の1つ(明示的な初期化子を持つ定義)は、文字列リテラルを指すように変数を初期化します。ただし、この定義を省略すると、ゼロで初期化されたままになります。つまり、定数NULLポインターになります。あまり役に立たない。

長い話を短くするために、決してそれをしないでください。ヘッダーでグローバル変数を宣言する(定義しない)には、externを使用します。そうすると、他の場所で定義を省略しようとすると、可能性の高いリンカーエラーが発生します(標準ではこの違反の診断は必要ありませんが、すべての既知の実装は1つを生成します)。

31
n.m.

あなたの例は、仮定義6.9 .2p2 )これは、一般的ですが非標準では複数のファイルに拡張されています。

仮の定義は、externがなく、初期化子がない変数宣言です。一般的な実装(意図的なしゃれ)では、暫定的な定義により、commonシンボルと呼ばれる特別な種類のシンボルが作成されます。リンク中に、同じ名前の通常のシンボルが存在する場合、他の一般的なシンボルは通常のシンボルへの参照になります。つまり、インクルードのためにそこに作成された翻訳単位内のすべての空のVERSIONsは通常のシンボルへの参照になりますシンボルconst char* const VERSION = "0.8 rev 213";。そのような通常のシンボルがない場合、共通のシンボルは単一のゼロで初期化された変数にマージされます。

Cコンパイラでこれに対して警告を出す方法がわかりません。

この

const char* const VERSION;
const char* const VERSION = "0.8 rev 213";

私が試したものに関係なくgccで動作するようです(g++は受け入れません-C++には仮の定義機能がなく、明示的に初期化されていないconst変数が好きではありません)。ただし、-fno-common(かなり「一般的な」(および強く推奨)の非標準オプション(gcc、clang、およびtccにはすべてあります))を使用してコンパイルできます。初期化されていない場合、そして、初期化されたextern-less宣言は異なる翻訳単位にあります。

例:

v.c:

const char * VERSION;

main.c

const char* VERSION;
int main(){}

コンパイルとリンク:

gcc main.c v.c #no error because of tentative definitions
g++ main.c v.c #linker error because C++ doesn't have tentative definitions
gcc main.c v.c -fno-common #linker error because tentative defs. are disabled

(C++の例のために、この例の2番目のconstを削除しました。C++は、さらにconstグローバルを静的にするか、仮定義機能のデモンストレーションを複雑にするようなものにします。)

一時的な定義を無効にするか、C++を使用すると、ヘッダー内のすべての変数宣言にexternを含める必要があります

version.h:

extern const char* const VERSION;

また、各グローバルに対して正確に1つの定義が必要であり、その定義にはイニシャライザが必要です。または、externがありません(externを初期化された変数に適用すると警告するコンパイラもあります)。

version.c:

#include "version.h" //optional; for type checking
const char* const VERSION = "0.8 rev 213";
15
PSkocik