【C言語:ポインタを深く理解する1】


ここに画像の説明を挿入します

1.ポインタの存在の意味

C 言語といえば、ポインタが一番怖いですか?以下の内容を読めば、ポインタに対してそれほど臆病ではなくなるかもしれません。
まず、C 言語にポインタが存在する理由を理解する必要があります。

  • ポインタは C 言語の魂です。この言葉は誇張ではありません。ポインタは C 言語の最も基本的かつ重要な概念の 1 つであり、ポインタにより C 言語は強力で効率的かつ柔軟なプログラミング言語となります。
  • ポインタの存在C言語で複雑なメモリ操作を実行できるようにする、プログラムの動作をより適切に制御でき、効率的なデータ構造とアルゴリズムを実装することもできます。
  • 指针是一个变量,它存储了一个内存地址,而这个内存地址指向的是另一个变量或对象的位置。ポインターを介して、この場所にある変数やオブジェクトに直接アクセスしたり変更したりできるため、優れた柔軟性と制御が可能になります。

コンピュータでは、メモリ ユニットの番号をアドレスとも呼びます。 C 言語では、アドレスにポインターという新しい名前が付けられます。
したがって、次のように理解できます。 メモリユニット番号 = アドレス = ポインタ

2. ポインタ変数とアドレス

  1. C 言語では、以下に示すように、変数の作成はメモリ内のスペースを適用することになります。

ここに画像の説明を挿入します
上記のコードは整数変数 a を作成し、メモリ内の 4 バイトのスペースに適用します。各バイトには独自のアドレスがあります。
&a は a のシェアを取り除きます4 バイトのうち小さい方のバイトのアドレス。、そのアドレスがわかっているので、手がかりをたどって 4 バイトのデータにアクセスできます。

  1. 変数のアドレスを取得するにはどうすればよいですか?

アドレス演算子 (&) を通じて取得するアドレスは、0133FE28 のような数値です。場合によっては、この値も後で簡単に使用できるように保存する必要があるため、次のように指定します。アドレスはどこですか保存された値?答えは、ポインタ変数にあります。
ポインタ変数の書き方は、変数の前に *
ここに画像の説明を挿入します
を追加することです。ポインタ変数も変数の一種であり、この種類の変数はアドレスを保存するために使用される の場合、ポインタ変数に格納されている値はアドレスとして認識されます。 指针变量也有自己的地址、ここでの pa のアドレスは 0x00cffdfc です。

  1. アドレスで変数を取得するにはどうすればよいですか?

アドレス(ポインタ)さえ取得できれば、そのアドレス(ポインタ)を使って、そのアドレス(ポインタ)が指すオブジェクトを見つけることができますが、ここでは逆参照演算子(*)と呼ばれる演算子を学習する必要があります。
ここに画像の説明を挿入します

  1. ポインタ変数のサイズ
  • ポインタ変数のサイズポインタ変数の型に関係なく、アドレスのサイズに依存します。
  • 32 ビット プラットフォームでは、アドレスは 32 ビット、つまり 4 バイトです。
  • 64 ビット プラットフォームでは、アドレスは 64 ビット、つまり 8 バイトです。

ここに画像の説明を挿入します

3. ポインタ変数の型の意味

3.1 ポインタの逆参照

ここに画像の説明を挿入します
ここに画像の説明を挿入します
デバッグを通じて、最初のバイトは a の 4 バイトすべてを 0 に変更し、2 番目のバイトは a の最初のバイトだけを 0 に変更することがわかります。
結論:ポインターの型によって、ポインターを逆参照するときにどの程度の権限が与えられるかが決まります (一度に数バイトを操作できます)。

3.2 ポインタ + - 整数

ここに画像の説明を挿入します

上図より、int 型ポインタ + 1 は 4 バイト、char 型ポインタ + 1 は 1 バイトスキップすることがわかります。
結論:ポインターの種類によって、ポインターが前後に移動する距離 (距離) が決まります。

3.3ボイド*

ポインタ型の中には特別な型があり、それは void* 型であり、特定の型を持たないポインタ (または汎用ポインタ) として理解できます。这种类型的指针可以⽤来接受任意类型地址。ただし、制限があります。void* 型のポインターは、ポインター + - 整数および逆参照演算を直接実行できません。

  • void* pi はあらゆる種類のアドレスを受け入れることができます

ここに画像の説明を挿入します

  • void 型のポインタは逆参照したり、整数から加算または減算したりすることはできません。

ここに画像の説明を挿入します

4.キーワードconst

4.1const 変更された変数

変数に制限を追加したいが変更できない場合は、どうすればよいでしょうか?これが const が行うことです。

  • const が変数を変更する場合、変数の値は変更できません。

ここに画像の説明を挿入します

4.2 const 変更ポインタ

  • const は * 記号の左側にあります

ここに画像の説明を挿入します

  • const は * 記号の右側にあります

ここに画像の説明を挿入します

  • * の両側は const

ここに画像の説明を挿入します

要約::左固定値、右向き

5. ポインタ演算

5.1 ポインタ + - 整数

ここに画像の説明を挿入します

ここに画像の説明を挿入します

5.2 ポインター - ポインター

  • ポインタ-ポインタ绝对值 は 2 つのポインタ間の要素の数です (両方のポインターが同じ空間を指している必要があります

ここに画像の説明を挿入します

5.3 ポインタサイズの比較

  • ポインタ比較はアドレス比較です。

ここに画像の説明を挿入します

6. ワイルドポインター

概念: ワイルド ポインターはポインタの指す位置が不明(ランダム、不正確、明確な制限なし)
ワイルド ポインタはどのように発生しますか?

  1. ポインタが初期化されていません

ローカル変数は初期化されず、デフォルトではランダムな値になります。
ここに画像の説明を挿入します

  1. ポインタの境界外アクセス

ここに画像の説明を挿入します

  1. ポインタが指したスペースが解放される

ここに画像の説明を挿入します

10 が出力できるのは、テスト関数のスタックフレーム空間が破壊されていないためで、再度出力するとランダムな値になります。

ポインターは優れていますが、不規則に使用すると予期せぬ結果を引き起こす可能性があるため、ワイルド ポインターの使用は避けてください。

ワイルドポインタを回避するにはどうすればよいでしょうか?

  1. ポインタの初期化
  2. 国境を越えたアクセスはしないでください
  3. ポインター変数は、使用されていないときはすぐに NULL に設定されます。
  4. 使用前にポインタが NULL かどうかを確認してください

7.assert断言

assert.h ヘッダー ファイルは、プログラムが実行時に指定された条件を満たしていることを確認するために使用されるマクロassert() を定義します。要件を満たしていない場合は、エラーが報告され、実行は中断されます。終了される。このマクロは「アサーション」と呼ばれることがよくあります。
assert() マクロは式をパラメータとして受け入れます。もし式は true (戻り値はゼロ以外)、assert() は効果がありません、プログラムは実行を続けます。もし表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写⼊⼀条错误信息,显示没有通过的表达式,以及包含这个表达式的⽂件名和⾏号,如下:

#include<stdio.h>
#include<assert.h>
int main()
{
    
    
	int* p = NULL;
	assert(p != NULL);
	printf("66666\n");
	return 0;
}

ここに画像の説明を挿入します
assert() の使用にはいくつかの利点があります。問題のファイルと行番号を自動的に特定するだけでなく、コードを変更せずにassert() をオンまたはオフにするメカニズムもあります。プログラムに問題がないことが確認され、アサーションを行う必要がない場合は、 #include <assert.h> 文の前にマクロ NDEBUG を定義してください。
ここに画像の説明を挿入します
通常、デバッグで使用でき、リリース バージョンではアサートを無効にすることを選択するだけですが、VS のような統合開発環境では、リリース バージョンで直接最適化されます。このように、デバッグ バージョンでの書き込みは、プログラマが問題を解決するのに役立ち、リリース バージョンでの書き込みは、ユーザーがプログラムを使用する際の効率には影響しません。

8. 配列名について

  • 配列名は配列の最初の要素のアドレスです。, ただし、例外が 2 つあります。

ここに画像の説明を挿入します

  • 例外 1

sizeof(配列名)、sizeof中单独放数组名,数组名表示整个数组的大小、単位はバイトです。
ここに画像の説明を挿入します

  • 例外 2

&数组名,取出的是整个数组的地址(配列全体のアドレスと配列の最初の要素のアドレスは異なります)

ここに画像の説明を挿入します
ここで、&arr[0] と arr は両方とも最初の要素であるため、&arr[0] と &arr[0]+1 は 4 バイト異なり、arr と arr+1 は 4 バイト異なることがわかります。 、+1は要素をスキップする
ただし、&arr と &arr+1 の違いは 40 バイトです。これは、&arr が配列のアドレスであり、+1 演算が 配列全体をスキップする
この時点で、誰もが配列名の意味を理解しているはずです。

9. 1次元配列を介してパラメータを渡すことの本質

まず質問から始めます。以前は関数の外で配列の要素数を計算していましたが、その関数を関数に渡して関数内で配列の要素数を求めることはできますか?
ここに画像の説明を挿入します
結果が望んでいたものではないことがわかりました。func 関数内で、sizeof(arr) のサイズが 40 ではなく 4 になっています。これはなぜですか?

  • 配列名を調べると、配列名が配列の最初の要素のアドレスであることがわかります。
  • 次に、配列にパラメータを渡すときに、渡されるのは配列名です。これは、本質的に、配列パラメータには配列の最初の要素のアドレスが渡されることを意味します。
  • アドレスのサイズの場合、そのサイズは 4/8 バイトである必要があるため、結果は 1 になります。
  • これで次のことが理解できます。一维数组传参,传递的是数组首元素的地址。受け取るパラメータが配列名の場合、それはポインタの形式で書き込むことができます。
void test(int* arr)//参数写成指针形式
{
    
    
 	printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩
}
  • コンパイラが 配列要素へのアクセスを処理するとき、配列要素は ⾸元素的地址+偏移量 にも変換されて要素のアドレスが検索され、アクセスのために逆参照されます。

おすすめ

転載: blog.csdn.net/weixin_69380220/article/details/134351238