C/C++ でグローバル変数/定数を定義するいくつかの方法の違い

転載元: https://www.cnblogs.com/catkins/p/5270388.html

まとめ:


1. グローバル変数はヘッダー ファイルで定義すべきではありません。グローバル変数はヘッダー ファイルでのみ宣言できます。定義はソース ファイル内で行う必要があります。 2. グローバル変数を他のファイルで使用したい場合、最も安全な方法は次のとおりです。common.h で宣言します。common.cpp で定義され、他のファイルにはグローバル変数を使用するための「common.h」がインクルードされます
3. static および const によって変更されたグローバル変数のスコープは独自のコンパイル単位のみであるため、必要なのはこのファイル内でグローバル変数を使用します。ソース ファイル内で const または static を使用して変更します
。 4. グローバル ドメインでは変数のみを宣言および初期化できますが、操作は実行できません。

 

グローバル変数について説明する前に、まずいくつかの基本概念を理解する必要があります。

1. コンパイル単位 (モジュール):

    今日、IDE 開発ツールの普及により、多くの人がコンパイルの概念についてよく理解できなくなりました。多くのプログラマーが最も恐れているのは、リンク エラー (LINK ERROR) の処理です。これは、コンパイル エラーのようなプログラム エラーが発生しないためです。特定の場所, この種のエラーにイライラすることはよくありますが、Linux または組み込みで開発作業を行うために gcc や makefile などのツールをよく使用する場合は、コンパイルと接続の違いをよく理解できるかもしれません。VC などの開発ツールでコードの作成を終了し、コンパイル ボタンをクリックして exe ファイルを生成する準備をすると、VC は実際に2 つのステップを実行します。最初のステップは、各 .cpp (.c) と対応する .h ファイルをコンパイルすることです。 obj ファイル。2 番目の手順では、プロジェクト内のすべての obj ファイルをリンクして、最終的な .exe ファイルを生成します。その後、2 つの場所でエラーが発生する可能性があります。1 つはコンパイル エラー (主に構文エラー)、もう 1 つはコンパイル エラーです。接続エラー。エラーは主に変数の繰り返し定義などによって発生します。コンパイル ユニットと呼ばれるものは、コンパイル フェーズ中に生成される各 obj ファイルを指します。obj ファイルはコンパイル単位です。つまり、cpp (.c) とそれに対応する .h ファイルが一緒になってコンパイル単位を形成します。プロジェクトは多数のコンパイル単位で構成されており、各objファイルには変数格納先の相対アドレスなどが含まれています。

2. 宣言と定義の違い
    : 関数または変数が宣言されるとき、実際の物理メモリ領域は与えられません。これにより、プログラムが正常にコンパイルされることが保証される場合がありますが、関数または変数が定義されるとき、それはメモリ内にあります。実際の物理スペースは限られており、コンパイル済みモジュールで参照する外部変数がプロジェクト全体のどこにも定義されていない場合、コンパイル中に渡すことができたとしても、プログラムがそれを見つけることができないため、リンク中にエラーが報告されます。メモリ内でこの変数に! このように理解することもできます。同じ変数または関数は複数回宣言できますが、定義は 1 回だけです。

3. extern extern の役割には
    2 つの役割があります1 つ目は、 extern "C" void fun(int a, int b); のように、"C" と一緒に使用される場合、関数 fun をコンパイルするようにコンパイラに指示します。関数名を翻訳するときは、C++ ではなく C の規則に従って、対応する関数名を翻訳します。C++ の規則により、関数名を翻訳するときに fun という名前が認識を超えて変更されます。fun@aBc_int_int#%$ などになる可能性がありますこれはコンパイラの「気質」に依存します (異なるコンパイラは異なるメソッドを採用します)。なぜこれを行うのですか? C++ が関数のオーバーロードをサポートしているためです。ここではこの問題についてはあまり説明しません。オンラインで検索すれば、満足のいく説明が得られると思います!
    ヘッダー ファイルなどで変数または関数を変更するために extern を "C" と一緒に使用しない場合: extern int g_Int; その機能は、関数またはグローバルを宣言することです。変数。スコープ キーワード、それが宣言する関数と変数は、このモジュールまたは他のモジュールで使用できます。これは宣言であり、定義ではないことに注意してください! つまり、モジュール B (コンパイル ユニット) がモジュール (コンパイル ユニット) を参照している場合、グローバル変数または関数の場合、モジュール A のヘッダー ファイルをインクルードするだけで済みます。コンパイル段階では、モジュール B が関数または変数を見つけられなくても、エラーは報告されません。ターゲットが生成されます。接続時にモジュール A からコードを取得します。この関数は にあります。

 上記の概念をすでによく理解している場合は、次のグローバル変数/定数の使用の違いを見てみましょう。

1. extern で変更されたグローバル変数:
extern の役割は上で説明しましたが、例を挙げてみましょう。たとえば、test1.h には次のステートメントがあります。

#ifndef TEST1H
#define TEST1H

extern char g_str[]; // 声明全局变量g_str
void fun1();

#endif

test1.cpp内

 #include "test1.h"
    
 char g_str[] = "123456"; // 定义全局变量g_str
    
 void fun1()
 {
     cout << g_str << endl;
 }

上記は test1 モジュールです。コンパイルと接続は成功します。test2 モジュールもあり、g_str を使用したい場合は、元のファイルでそれを引用するだけで済みます。

#include "test1.h"

void fun2()
{
    cout << g_str << endl;
}

 上記の test1 と test2 は同時にコンパイルして接続できます。興味がある場合は、ultraEdit を使用して test1.obj を開くことができます。その中に文字列「123456」が見つかりますが、test2.obj には見つかりませんこれはg_str によるものです プロジェクト全体のグローバル変数です メモリ内にコピーは 1 つだけありますコンパイル単位 test2.obj に別のコピーがある必要はありません、そうしないと接続中に定義の重複エラーが報告されます!
    グローバル変数の宣言と定義を一緒に置くことを好む人もいますが、これにより、上記の test1.h を extern char g_str[] = "123456"; // これは no extern と同等になります , そして、test1.cpp の g_str の定義を削除します。これは、test1 および test2 モジュールをコンパイルして接続すると、接続エラーが報告されます。これは、グローバル変数 g_str の定義をヘッダー ファイルの後に配置したためです。 test1.cpp モジュールには test1.h が含まれているため g_str が一度定義され、test2.cpp にも test1.h が含まれているため g_str が再定義されます このとき、コネクタは test1 と test2 を接続するときに 2 つの g_str を見つけます。g_str の定義を test1.h に配置する場合は、test2 のコードから #include "test1.h" を削除し、次のように置き換えます。

extern char g_str[];
void fun2()
{
    cout << g_str << endl;
}

この時点で、コンパイラは g_str が外部コンパイル モジュールであることを認識しているため、このモジュール内で再度定義することはありませんが、test2.cpp# include "test1.h" では使用できないため、これは非常に悪いと       言いたいのです。, その場合、 extern で変更しない限り、 test1.h で宣言された他の関数を使用することはできません。この場合、宣言された関数の長いリストがあり、ヘッダー ファイルの機能は、インターフェイスが外部に提供されることになりますしたがって、宣言はヘッダー ファイル内でのみ行うようにしてください。真実は常に単純です

2. 静的で変更されたグローバル変数:

        まず第一に、static と extern には互換性がないことをお伝えしたいと思います。つまり、extern と static は同時に変数を変更できません。第 2に、static によって変更されたグローバル変数は同時に宣言および定義されますつまり、グローバル変数がヘッダー ファイルで static を使用して宣言された後、同時に定義されることになります。最終的に、静的に変更されたグローバル変数のスコープは独自のコンパイル単位のみになります。これは、その「グローバル」であることを意味します。はこのコンパイル単位でのみ有効であり、test1.h などの他のコンパイル単位では認識できません

#ifndef TEST1H
#define TEST1H

static char g_str[] = "123456"; 
void fun1();

#endif

test1.cpp:

#include "test1.h"

void fun2()
{
    cout << g_str << endl;
}

test2.cpp:

#include "test1.h"
    
void fun2()
{
    cout << g_str << endl;
}

上記 2 つのコンパイル単位は正常に接続できます。test1.obj を開くと、その中に「123456」という文字列が見つかり、test2.obj にも文字列が見つかります。レポートなしで正常に 接続できる理由重複定義のエラーは、2 つの異なる変数に同じ値が割り当てられ、これら 2 つの変数がそれぞれのコンパイル単位で動作するのと同じように、内容は同じですが、格納されている物理アドレスが異なるためです。
       もしかしたら、もっと真剣に上記のコードをこっそりトレースしてデバッグした結果、2 つのコンパイル単位 (test1、test2) の g_str のメモリ アドレスが同じであることがわかり、静的に変更された変数も動作できると結論付けるかもしれません。他のモジュールでも実行できますが、コンパイラはあなたを騙していると言いたいのです。ほとんどのコンパイラには、メモリを節約し、実行効率を高めるターゲット プログラムを生成するという目標を達成するために、コードの最適化機能があります。コンパイラがさまざまなモジュールを接続するとき、場合によっては、上記の「123456」など、同じ内容を持つメモリのコピーが 1 つだけコピーされます。2 つのコンパイル ユニットにある変数は同じ内容を持つため、接続時にメモリにコピーが 1 つだけ存在します。 . 、上記のコードを次のように変更すると、コンパイラの嘘をすぐに暴露できます。

test1.cpp:

#include "test1.h"
    
void fun1()
{
    g_str[0] = 'a';
    cout << g_str << endl;
}

test2.cpp:

#include "test1.h"
    
void fun2()
{
    cout << g_str << endl;
}
void main()
{
    fun1(); // a23456
    fun2(); // 123456
}

このときコードを辿ってみると、2つのコンパイル単位のg_strアドレスが異なっていることが分かりますが、一か所を変更したため、コンパイラが強制的にメモリを元の状態に戻しており、メモリ内に 2 つのコピーがあり、それを 2 つのモジュールの変数にコピーします。static には上記の特徴があるため、他のモジュールに不要な情報を汚染しないように、static グローバル変数を定義する場合は、ヘッダー ファイルではなくソース ファイルに配置するのが一般的です。この原則も覚えておいてください

3. const によって変更されたグローバル定数:

const で装飾されたグローバル定数は広く使用されており、例えばソフトウェアのエラーメッセージ文字列はグローバル定数を使用して定義されます。const によって変更されたグローバル定数は static と同じ特性を持ちます。つまり、このコンパイル済みモジュール内でのみ使用できますが、 const を extern とともに使用して、その定数がextern const charなどの他のコンパイル済みモジュールで使用できることを宣言できます。g_str[]; 次に、元のファイルで定義することを忘れないでください: const char g_str[] = "123456";

       したがって、const を単独で使用する場合は static と同じでありextern と一緒に使用する場合は extern と同じ特性になりますconst について詳しく説明する必要はありません。const char* g_str = "123456" は const char g_str[] = "123465" とは異なることを思い出していただきたいと思います。前の const は g_str の代わりに char * を変更します、その g_str は定数ではなく、定義されたグローバル変数 (他のコンパイル単位で使用できる) とみなされます。そのため、 char *g_str を const グローバル定数の規則に準拠させたい場合は、次のように const を定義するのが最善です。この char* const g_str="123456"

おすすめ

転載: blog.csdn.net/SwordArcher/article/details/113512348