理解しなければならないのは、C には C++ のような「参照渡し」の概念がないということです。つまり、コンパイラは、変数の型に関係なく、渡された変数のコピーを作成します。コピーが保存される場所は、コンパイラ、アーキテクチャ、最適化レベルなどによって異なります。一般に、どこに保存されたかを気にする必要も、知る必要もありません。本当に重要なのは、関数内でコピーを取得することです。
理論的には、次のようなことを行うことで、メモリの任意のアドレスにアクセスできます。
int *var = ((int*) 0xdeadbeef);
*var = 3;
意味: 値0xdeadbeef
を整数ポインターにキャストし、.csv に保存しvar
ます。次に、値 3 を から始まるブロック (x86 では 4 バイト長) に保存します0xdeadbeef
。これは正しいCですが、今日のオペレーティングシステムはメモリコントローラーを使用して限られたアドレス/ブロックのセットにアクセスできるため、セグメンテーション違反が発生することは間違いありません.
これを行う場合:
int var = 10;
コンパイラは値 10 をどこかに格納します (レジスタに格納される場合もありますが、コンパイラが RAM に格納すると仮定します)。C プログラマーは、通常、それがどこにあるかは気にしません。リンカは正しいアドレスを処理します。
このコードを見てみましょう
void foo(int f)
{
/* do something with f */
}
void bar(int g)
{
int f1 = 2;
f1 += g;
foo(f);
}
コンパイラはそれをどのようにアセンブラに変換しますか? このように見えるかもしれません
foo:
0xa0000000: load in register 1 the 4-byte value @ 0x12abcd00
0xa0000004: /* do something with register 1 */
0xa0000008: /* do something else with register 1 */
...
bar:
0xa0000c00: load in register 3 the 4-byte value @ 0x12ad0004
0xa0000c04: load in register 4 the value 2
0xa0000c08: add register 3, register 4 (result saved in 'result register')
0xa0000c0b: save the content of 'result register' @ 0x12abcd00 (look at the first line)
0xa0000c10: jump 0xa0000000 (call foo)
同じことがポインターにも当てはまります。ポインターは多かれ少なかれ整数変数です。ポインタによって格納される値はアドレスです。整数との違いは、そのアドレスに格納されている値にアクセス (= 逆参照) できることです。*
演算子を使用してこれを行います(最初の例を参照*var = 3
)。&
演算子は、変数のアドレスを返します。
だからあなたが持っているとき
void foo(int *x)
{
*x = 3;
}
void bar(void)
{
int i = 9;
int *i_ptr = &i;
foo(i_ptr);
/* i is 3 */
}
C は のコピーを作成しますが、コピーi_ptr
の格納された値がi_ptr
のアドレスと同じである場合は、で定義されていなくてもi
、それfoo
を逆参照して の値を変更できます。i
i
foo
では、C ではいつポインターを使用するのでしょうか。一般に、ポインターを使用する場合の最も一般的なケースは次のとおりです。
- 関数で宣言していない変数を変更したい
- 動的に割り当てられたメモリを使用します (
malloc
、realloc
、を参照calloc
) 。
- 私たちは文字列を扱います