1 行: 未定義の動作を示す初期化されていないポインターを逆参照している最初のコードと、アドレスの値にアクセスできる初期化されたポインターを逆参照している 2 番目のコード。
少し説明:
最初に、ポインターは整数に過ぎないことを認識する必要があります。これ*var
により、変数の内容var
(その中の整数) をアドレスとして使用して、そのアドレスの値をフェッチすることをコンパイラーに伝えます。同様に、変数の格納された値を使用してアドレスの値をフェッチし、このフェッチされた値をアドレスとして使用して、そこに格納されている値をフェッチする**var
ことをコンパイラに伝えます。var
したがって、最初の宣言では次のようになります。
+----------+ +----------+
| garbage | | garbage |
+----------+ +----------+
| a | | b |
+----------+ +----------+
| addr1 | | addr2 |
+----------+ +----------+
次に、に格納されている値をアドレスとして使用しようとしa
ます。a
ガベージが含まれている場合、任意の値にすることができますが、どのアドレスの場所にもアクセスできません。したがって、次の瞬間に*a
、保存されa
た値がアドレスとして使用されます。格納される値は何でもよいため、何でも起こり得ます。
location にアクセスする権限がある場合、コードはセグメンテーション エラーなしで実行され続けます。アドレスがたまたまヒープ簿記構造、またはコードがヒープまたはスタックから割り当てた他のメモリ領域からのアドレスである場合、そのアドレス*a = 10
の既存の値を単純に消去10
します。これは、コンテキストがメモリの実際の権限を持っていることを知らずに何かを変更したため、未定義の動作につながる可能性があります。メモリへのアクセス許可がない場合は、単純にセグメンテーション エラーが発生します。これは、初期化されていないポインターの逆参照と呼ばれます。
次のステートメントは、 ina = &b
のアドレスを割り当てるだけです。前の行が初期化されていないポインターを逆参照しているため、これは役に立ちません。b
a
次のコードでは、3 番目のステートメントの後に次のようなものがあります。
+----------+ +----------+
| addr2 |---+ | garbage |
+----------+ | +----------+
| a | +--> | b |
+----------+ +----------+
| addr1 | | addr2 |
+----------+ +----------+
b
3 番目のステートメントは、 のアドレスをinto に割り当てますa
。before thata
は逆参照されないため、初期化前に格納されたガベージ値a
がアドレスとして使用されることはありません。知識の有効なアドレスを に割り当てるとa
、逆参照a
により、 が指す値にアクセスできるようになりますa
。
答えを拡張すると、ポインターに有効なアドレスを割り当てた場合でも、ポインターを逆参照するときに、ポインターが指すアドレスの有効期限が切れていないことを確認する必要があることに注意する必要があります。たとえば、ローカル変数を返します。
int foo (void)
{
int a = 50;
return &a; //Address is valid
}//After this `a' is destroyed (lifetime finishes), accessing this address
//results in undefined behaviour
int main (void)
{
int *v = foo ();
*v = 50; //Incorrect, contents of `v' has expired lifetime.
return 0;
}
ヒープから解放されたメモリ位置にアクセスする場合も同様です。
int main (void)
{
char *a = malloc (1);
*a = 'A'; //Works fine, because we have allocated memory
free (a); //Freeing allocated memory
*a = 'B'; //Undefined behaviour, we have already freed
//memory, it's not for us now.
return 0;
}