CIの場合:
int num;
に何かを割り当てる前にnum
、num
不確定の値はありますか?
extern int x;
ただし、定義は常に宣言を意味します。これはC ++では当てはまりません。静的なクラスメンバー変数では、宣言はクラス定義(宣言ではなく)内にあり、定義はクラス定義の外になければならないため、宣言せずに定義できます。
CIの場合:
int num;
に何かを割り当てる前にnum
、num
不確定の値はありますか?
extern int x;
ただし、定義は常に宣言を意味します。これはC ++では当てはまりません。静的なクラスメンバー変数では、宣言はクラス定義(宣言ではなく)内にあり、定義はクラス定義の外になければならないため、宣言せずに定義できます。
回答:
静的変数(ファイルスコープと静的関数)はゼロに初期化されます。
int x; // zero
int y = 0; // also zero
void foo() {
static int x; // also zero
}
非静的変数(ローカル変数)は不確定です。値を割り当てる前にそれらを読み取ると、未定義の動作が発生します。
void foo() {
int x;
printf("%d", x); // the compiler is free to crash here
}
実際には、最初は無意味な値が含まれる傾向があります。一部のコンパイラは、特定の固定値を入力してデバッガを確認するときに明らかになる場合があります-厳密に言えば、コンパイラはクラッシュから呼び出しまで自由に実行できますあなたの鼻の通路を介して悪魔。
単に「未定義/任意の値」ではなく、未定義の動作である理由については、さまざまなタイプの表現に追加のフラグビットを持つCPUアーキテクチャがいくつかあります。最近の例はItaniumで、レジスタに「Not Thing」ビットがあります。もちろん、C標準の草案作成者は、いくつかの古いアーキテクチャを検討していました。
これらのフラグビットが設定された値を処理しようとすると、実際には失敗しないはずの操作(たとえば、整数の加算や別の変数への割り当て)でCPU例外が発生する可能性があります。また、変数を初期化せずにそのままにしておくと、コンパイラはこれらのフラグビットが設定されたランダムなガベージを取得する可能性があります。つまり、初期化されていない変数に触れると致命的になる可能性があります。
char
です。他のすべてはトラップ表現を持つことができます。あるいは、初期化されていない変数へのアクセスはとにかくUBなので、準拠するコンパイラは単にチェックを行い、問題を通知することを決定するかもしれません。
Cは常にオブジェクトの初期値について非常に具体的でした。グローバルまたはの場合static
、それらはゼロになります。の場合auto
、値は不確定です。
これはC89以前のコンパイラの場合であり、K&RおよびDMRの元のCレポートでそのように指定されていました。
これはC89の場合でした。セクション6.5.7初期化を参照してください。
自動保存期間を持つオブジェクトが明示的に初期化されていない場合、その値は不確定です。静的ストレージ期間を持つオブジェクトが明示的に初期化されない場合、算術型を持つすべてのメンバーに0が割り当てられ、ポインター型を持つすべてのメンバーにnullポインター定数が割り当てられたかのように、オブジェクトは暗黙的に初期化されます。
これはC99の場合でした。セクション6.7.8初期化を参照してください。
自動保存期間を持つオブジェクトが明示的に初期化されていない場合、その値は不確定です。静的ストレージ期間を持つオブジェクトが明示的に初期化されていない
場合、次のようになります。—ポインター型がある場合、オブジェクトはnullポインターに初期化されます。
—算術型の場合、(正または符号なし)ゼロに初期化されます。
—集合体の場合、すべてのメンバーはこれらのルールに従って(再帰的に)初期化されます。
—ユニオンの場合、最初の名前付きメンバーはこれらのルールに従って(再帰的に)初期化されます。
正確に不確定とはどういう意味かについて、私はC89について確信が持てないとC99は言います
3.17.2
不
特定の値またはトラップ表現の不定値
しかし、実際の標準では、どのような基準に関係なく、実際には各スタックページはゼロから始まりますが、プログラムがauto
ストレージクラスの値を調べると、プログラムが最後にスタックアドレスを使用したときに、自分のプログラムが残したものをすべて参照します。多数のauto
配列を割り当てると、最終的にはゼロからきれいに始まることがわかります。
あなたは疑問に思うかもしれませんが、なぜこのようになっているのですか?別のSO回答がその質問を扱います。https://stackoverflow.com/a/2091505/140740を参照してください。
indeterminate value
3.19.2にあります。
変数の保存期間によって異なります。静的ストレージ期間を持つ変数は、常に暗黙的にゼロで初期化されます。
自動(ローカル)変数については、初期化されていない変数の値は不定です。不確定な値は、とりわけ、その変数で「見る」ことができる「値」が予測不可能であるだけでなく、安定していることさえ保証されないことを意味します。たとえば、実際には(つまり、UBを1秒間無視する)このコード
int num;
int a = num;
int b = num;
は変数a
を保証せずb
、同じ値を受け取ることになります。興味深いことに、これは一部の理論的な概念ではありません。これは、最適化の結果として実際に容易に発生します。
したがって、一般に、「メモリにあるゴミで初期化される」という一般的な答えは、リモートでさえ正しくありません。初期化されていない変数の動作は、ガベージで初期化された変数の動作とは異なります。
Ubuntu 15.10、カーネル4.2.0、x86-64、GCC 5.2.1の例
十分な標準があります。実装を見てみましょう:-)
ローカル変数
標準:未定義の動作。
実装:プログラムはスタックスペースを割り当て、そのアドレスには何も移動しないので、以前そこにあったものが使用されます。
#include <stdio.h>
int main() {
int i;
printf("%d\n", i);
}
コンパイル:
gcc -O0 -std=c99 a.c
出力:
0
と逆コンパイルします:
objdump -dr a.out
に:
0000000000400536 <main>:
400536: 55 push %rbp
400537: 48 89 e5 mov %rsp,%rbp
40053a: 48 83 ec 10 sub $0x10,%rsp
40053e: 8b 45 fc mov -0x4(%rbp),%eax
400541: 89 c6 mov %eax,%esi
400543: bf e4 05 40 00 mov $0x4005e4,%edi
400548: b8 00 00 00 00 mov $0x0,%eax
40054d: e8 be fe ff ff callq 400410 <printf@plt>
400552: b8 00 00 00 00 mov $0x0,%eax
400557: c9 leaveq
400558: c3 retq
x86-64呼び出し規約に関する知識から:
%rdi
は最初のprintf引数なので、"%d\n"
アドレスの文字列0x4005e4
%rsi
は2番目のprintf引数なので、i
です。
これ-0x4(%rbp)
は、最初の4バイトのローカル変数であるから取得されます。
この時点rbp
で、スタックの最初のページはカーネルによって割り当てられているので、その値を理解するには、カーネルコードを調べて、それが何に設定されているかを調べます。
TODOカーネルは、プロセスが停止したときに他のプロセスに再利用する前に、メモリを何かに設定しますか?そうでない場合、新しいプロセスは他の終了したプログラムのメモリを読み取ることができ、データをリークします。参照:初期化されていない値はセキュリティリスクですか?
次に、独自のスタック変更を試して、次のような楽しいものを書くこともできます。
#include <assert.h>
int f() {
int i = 13;
return i;
}
int g() {
int i;
return i;
}
int main() {
f();
assert(g() == 13);
}
のローカル変数 -O3
実装分析:<値が最適化されました>はgdbで何を意味しますか?
グローバル変数
標準:0
実装:.bss
セクション。
#include <stdio.h>
int i;
int main() {
printf("%d\n", i);
}
gcc -00 -std=c99 a.c
コンパイル:
0000000000400536 <main>:
400536: 55 push %rbp
400537: 48 89 e5 mov %rsp,%rbp
40053a: 8b 05 04 0b 20 00 mov 0x200b04(%rip),%eax # 601044 <i>
400540: 89 c6 mov %eax,%esi
400542: bf e4 05 40 00 mov $0x4005e4,%edi
400547: b8 00 00 00 00 mov $0x0,%eax
40054c: e8 bf fe ff ff callq 400410 <printf@plt>
400551: b8 00 00 00 00 mov $0x0,%eax
400556: 5d pop %rbp
400557: c3 retq
400558: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
40055f: 00
# 601044 <i>
それi
はアドレスに0x601044
あり、そして:
readelf -SW a.out
含む:
[25] .bss NOBITS 0000000000601040 001040 000008 00 WA 0 0 4
これは0x601044
、.bss
セクションの真ん中にあり、0x601040
8バイト長で始まります。
ELF標準はその後、名前のセクションがあることを保証し.bss
、完全にゼロの充填されています。
.bss
このセクションは、プログラムのメモリイメージに寄与する初期化されていないデータを保持します。定義により、システムはプログラムの実行が開始されるとデータをゼロで初期化します。セクションタイプで示されるように、セクションはファイルスペースを占有しませんSHT_NOBITS
。
さらに、タイプSHT_NOBITS
は効率的で、実行可能ファイルのスペースを占有しません。
sh_size
このメンバーは、セクションのサイズをバイト単位で示します。SHT_NOBITS
セクションタイプがでない限り、セクションsh_size
はファイルのバイトを占有します。タイプのセクションはSHT_NOBITS
ゼロ以外のサイズである可能性がありますが、ファイル内のスペースを占有していません。
次に、プログラムを開始したときにメモリにプログラムをロードするときに、そのメモリ領域をゼロにするのはLinuxカーネルの責任です。
コンピュータの記憶容量は有限であるため、自動変数は通常、以前に他の任意の目的で使用された記憶要素(レジスタまたはRAM)に保持されます。値が割り当てられる前にそのような変数が使用された場合、そのストレージは以前に保持していたものをすべて保持する可能性があるため、変数の内容は予測できません。
追加のしわとして、多くのコンパイラは、関連する型よりも大きい変数をレジスタに保持する場合があります。コンパイラーは、変数に書き込まれて読み戻される値が切り捨てられたり、適切なサイズに符号拡張されたりすることを保証する必要がありますが、多くのコンパイラーは、変数が書き込まれるときにそのような切り捨てを実行し、変数が読み込まれる前に実行されます。このようなコンパイラでは、次のようになります。
uint16_t hey(uint32_t x, uint32_t mode)
{ uint16_t q;
if (mode==1) q=2;
if (mode==3) q=4;
return q; }
uint32_t wow(uint32_t mode) {
return hey(1234567, mode);
}
wow()
値1234567をそれぞれレジスタ0と1 に格納し、を呼び出すことになるでしょうfoo()
。以来x
の機能をレジスタ0にその戻り値を置くことになっているので、「foo」という内で必要とされていない、と、コンパイラは0に登録割り振ることができますq
。mode
が1または3の場合、レジスタ0にはそれぞれ2または4がロードされますが、それ以外の値の場合、その値が範囲内になくても、関数はレジスタ0にあったもの(つまり、値1234567)を返すことがあります。 uint16_tの。
初期化されていない変数がドメイン外の値を保持しないようにするための追加の作業をコンパイラーに要求しないようにし、不確定な動作を過度に詳細に指定する必要がないようにするため、標準では、初期化されていない自動変数の使用は未定義の動作であるとしています。場合によっては、この結果は、値がその型の範囲外であることよりもさらに驚くかもしれません。たとえば、次の場合:
void moo(int mode)
{
if (mode < 5)
launch_nukes();
hey(0, mode);
}
コンパイラはmoo()
、3より大きいモードで呼び出すと必ずプログラムが未定義の動作を呼び出すため、コンパイラはmode
、通常4そのような場合の核の打ち上げ。標準も現代のコンパイラ哲学も、「hey」からの戻り値が無視されるという事実を気にしないことに注意してください。それを返そうとする行為は、コンパイラに無制限のライセンスを与えて任意のコードを生成します。
ストレージクラスが静的またはグローバルの場合、読み込み中に、変数に何らかの値が最初に割り当てられない限り、BSSは変数またはメモリの場所(ML)を0に初期化します。ローカルの初期化されていない変数の場合、トラップ表現はメモリ位置に割り当てられます。したがって、重要な情報を含むレジスタがコンパイラによって上書きされると、プログラムがクラッシュする可能性があります。
ただし、コンパイラによっては、このような問題を回避するメカニズムを備えている場合があります。
char以外のデータ型の未定義の値を表すビットパターンを持つトラップ表現があることに気づいたとき、私はnec v850シリーズで作業していました。初期化されていない文字を受け取ったとき、トラップ表現のためにデフォルト値がゼロになりました。これは、necv850esを使用するany1に役立ちます。
私が行った限り、それはほとんどコンパイラに依存していますが、一般的にほとんどの場合、値はコンパイラによって0と事前に想定されています。
TCが0として値を与えている間、VC ++の場合にガベージ値を取得しました。以下のように出力します
int i;
printf('%d',i);
0
、コンパイラはおそらくその値を取得するために追加の手順を実行します(とにかく変数を初期化するコードを追加することによって)。一部のコンパイラは「デバッグ」コンパイルを行うときにこれを行いますが0
、これらの値を選択することは、コード内の障害を隠すため、悪い考えです(より適切なことは、非常にありそうもない数などを保証するため0xBAADF00D
です)。ほとんどのコンパイラは、メモリを占有するガベージを変数の値として残していると思います(つまり、一般的にはとして組み立てられません0
)。