ポインタ
ポインタは C 言語で最も重要な概念の 1 つですが、理解するのが最も難しい概念の 1 つでもあります。
導入
ポインタとは何ですか?まず、メモリのアドレスを表す値であるため、ポインタはあるメモリのアドレスを指す道しるべに相当します。
文字「」はポインタを表し、通常は type キーワードの後に続き、ポインタが指す値のタイプを示します。たとえば、「char」は文字へのポインタを表し、「float*」は「float」型の値へのポインタを表します。
int* intPtr;
上記の例では、ポインタである変数 intPtr
を宣言しており、指すメモリ アドレスには整数が格納されています。
アスタリスク*
は変数名とtypeキーワードの間の任意の位置に記述できます。以下の記述方法が有効です。
int *intPtr;
int * intPtr;
int* intPtr;
この本では、type キーワード (つまり、「int* intPtr;」) の直後にアスタリスクを使用しています。これは、ポインタ変数が通常の変数であるが、その値がメモリ アドレスであることを反映している可能性があるためです。
この書き方で注意点が1つあり、同じ行で2つのポインタ変数を宣言する場合は以下のように記述する必要があります。
// 正しい
int * foo、* bar;
// 間違い
int* foo、bar;
上記の例では、2 行目の実行結果は、foo
が整数ポインタ変数、bar
が整数変数であること、つまり、*
は最初の変数にのみ影響します。
ポインタはポインタを指すこともできますが、その場合は 2 つのアスタリスク **
で表す必要があります。
int** foo;
上の例は、変数 foo
がポインターであり、ポインターを指しており、2 番目のポインターが整数を指していることを示しています。
* 演算子
ポインタを表すことに加えて、「*」記号は、ポインタ変数が指すメモリ アドレスの値を取得する演算子としても使用できます。
void インクリメント(int* p) {
*p = *p + 1;
}
上記の例では、関数 increment()
のパラメータは整数ポインタ p
です。関数本体の *p
は、ポインタ p
が指す値を表します。 *p
に値を代入するということは、ポインタが指すアドレスの値を変更することを意味します。
上記の関数の機能は、パラメータ値に「1」を加算することです。この関数にはアドレスが渡されるため戻り値がありませんが、関数本体内のアドレスに含まれる値の操作は関数の外部に影響を与えるため、値を返す必要はありません。実はC言語では関数内のポインタを介して外部に値を渡すのが一般的な手法です。
変数値の代わりに変数アドレスを関数に渡すことには、別の利点があります。多くの記憶領域を必要とする大きな変数の場合、変数値をコピーして関数に渡すことは時間と領域の無駄であり、ポインタを渡すことほど効率的ではありません。
& 演算子
「&」演算子は変数のメモリアドレスを取得するために使用されます。
int x = 1;
printf("x のアドレスは %p\n", &x);
上の例では、「x」は整数変数、「&x」は「x」の値が格納されているメモリ アドレスです。 printf()
の %p
はメモリ アドレスのプレースホルダであり、メモリ アドレスを出力できます。
前のセクションで、パラメータ変数に '1' を加算する関数は次のように使用できます。
void インクリメント(int* p) {
*p = *p + 1;
}
int x = 1;
インクリメント(&x);
printf("%d\n", x);
上の例では、increment()
関数を呼び出した後、変数 x
の値が 1 増加します。これは、変数 x
のアドレス &x
が関数に渡されているためです。
&
演算子と *
演算子は相互に逆演算であり、次の式が常に成り立ちます。
int i = 5;
if (i == *(&i)) // 正しい
ポインタ変数の初期化
ポインタ変数を宣言した後、コンパイラはポインタ変数自体にメモリ空間を割り当てますが、このメモリ空間の値はランダムです。つまり、ポインタ変数が指す値はランダムです。このとき、ポインタ変数が指すアドレスの読み書きは行わないでください。そのアドレスはランダムなアドレスであり、重大な結果を引き起こす可能性があります。
int* p;
*p = 1; // エラー
'p' が指すアドレスがランダムであるため、上記のコードは間違っています。このランダムなアドレスに '1' を書き込むと、予期しない結果が生じます。
正しいアプローチは、ポインタ変数が宣言された後、まず割り当てられたアドレスをポイントし、次に読み取りと書き込みを行う必要があります。これをポインタ変数の初期化と呼びます。
int* p;
int i;
p = &i;
*p = 13;
上の例では、「p」はポインタ変数です。この変数を宣言すると、「p」はランダムなメモリアドレスを指します。このとき、すでに割り当てられているメモリ アドレスを指す必要があります。上記の例では、整数変数 i
を宣言し、コンパイラは i
にメモリ アドレスを割り当て、p
をそのアドレスに割り当てます。 i
のメモリアドレス (p = &i;
)。初期化が完了すると、p
が指すメモリ アドレスを割り当てることができます (*p = 13;
)。
初期化されていないポインタ変数の読み書きを防ぐために、初期化されていないポインタ変数を「NULL」に設定する習慣を身に付けることができます。
int* p = NULL;
「NULL」は C 言語の定数で、アドレス「0」のメモリ空間を表します。このアドレスは使用できません。このアドレスを読み書きするとエラーが報告されます。
ポインタ操作
ポインタは基本的に、メモリ アドレスを表す符号なし整数です。演算を実行できますが、ルールは整数演算のルールではありません。
(1) ポインタと整数値の加減算
ポインタと整数値間の演算はポインタの移動を表します。
短い* j;
j = (short*)0x1234;
j = j + 1; // 0x1236;
上記の例では、「j」はメモリ アドレス「0x1234」を指すポインタです。 0x1234
自体は整数型 (int
) であり、j
の型 (short*
) と互換性がないため、0x1234
を short*
に変換するには型キャストを使用する必要があります。 「j + 1」は「0x1235」に等しいと思うかもしれませんが、正解は「0x1236」です。その理由は、「j + 1」はポインタがメモリアドレスの上位ビットに1ユニット移動することを意味し、1ユニットの「short」型は2バイトの幅を占有するため、2バイトをメモリアドレスの上位ビットに移動することと同等であるためです。ハイビット。同様に、「j - 1」の結果は「0x1232」になります。
ポインタが移動する単位は、ポインタが指すデータ型に関係します。データ型が単位ごとに占めるバイト数を移動します。
(2) ポインタとポインタの追加
ポインタは整数値に対してのみ加算および減算できます。2 つのポインタを加算することは不正です。
unsigned short* j;
unsigned short* k;
x = j + k; // 不正です
上記の例では 2 つのポインターが追加されていますが、これは不正です。
(3) ポインタとポインタの減算
同じ型のポインタでは減算が可能で、それらの間の距離、つまりそれらがいくつ離れているかというデータ単位を返します。
上位アドレスから下位アドレスを減算すると正の値が返され、下位アドレスから上位アドレスを減算すると負の値が返されます。
このとき、減算によって返される値は、符号付き整数型のエイリアスである ptrdiff_t
型に属します。具体的な型はシステムによって異なります。このタイプのプロトタイプは、ヘッダー ファイル stddef.h
で定義されます。
ショート* j1;
ショート* j2;
j1 = (short*)0x1234;
j2 = (short*)0x1236;
ptrdiff_t dist = j2 - j1;
printf("%td\n", dist); // 1
上記の例では、j1
と j2
は short 型への 2 つのポインタであり、変数 dist
はそれらの間の距離です。型は ptrdiff_t
で、値は 1
です。これは、その違いが正確であるためです。 2 バイトの short 型の値を格納します。
(4) ポインタとポインタの比較演算
ポインタ間の比較演算では、それぞれのメモリ アドレスのどちらが大きいかを比較し、戻り値は整数 '1' (true) または '0' (false) になります。
作者: wangdoc
アドレス: https://wangdoc.com/
ライセンス: クリエイティブ・コモンズ 3.0