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

目次

序文

(1) メモリとアドレス

実生活から始める

住所

 メモリ

 メモリとアドレスは密接な関係がある

(2) ポインタ変数

ポインタ変数とアドレス取得演算子

ポインター変数と逆参照演算子 

ポインタのサイズ 

ポインタ演算

ポインタ +- 整数

 ポインタ-ポインタ

 ポインタの関係演算

ポインタ型の意味

 void* ポインタ

 const 変更ポインタ

ワイルドポインター

ワイルドポインタの原因

 ワイルドポインタを回避する方法


 

序文

         C言語はメモリを直接操作するプログラミング言語で、コンピュータのメモリ上のアドレス空間に直接アクセスして操作することができます。

 

        C言語に存在するポインタータイプ、ポインターはメモリ内のアドレスを指します。ポインタを介してメモリに保存されているデータにアクセスし、変更することができます。

 

         したがって、ポインタを深く理解し、メモリを理解することは、高品質のプログラムを作成したり、プログラムのエラーをデバッグしたりするのに非常に役立ちます。


 ポインタを理解する前に、まずメモリアドレスコンセプト:

(1) メモリとアドレス

実生活から始める

住所

        現実の問題から始めます - 授業に行きたいのに、学校にはたくさんの校舎があり、教室から教室まで探さなければなりません。授業が行われている教室を見つけたら、おそらく授業から出ることは終わったでしょう。

        実生活では、物を見つける効率が低いという問題をどのように解決すればよいでしょうか?

        ——私たちは物を整理整頓し、その位置を事前に把握し、さらには番号を付けることもできます。

        各教棟の各部屋には1つずつ番号を振ってありますので、授業に行きたい場合は建物番号と部屋番号さえあればすぐに教室や教室を見つけることができます。

したがって、C 言語には実例を参考にしており、アドレスという概念があります。​ 

 メモリ

        コンピューティングでは、CPU (中央処理装置) がデータを処理するときに、必要なデータがメモリから読み取られ、処理されたデータもメモリに戻されることが知られています。

        コンピュータを購入すると、コンピュータのメモリは 8GB/16GB/32GB などになります。では、このメモリ領域を効率的に管理するにはどうすればよいでしょうか?

        実際には、メモリはメモリ ユニットに分割されており、各メモリ ユニットのサイズは 1 バイトです。

         バイトの大きさはどのくらいですか?

共通メモリユニット

1バイト = 8ビット

1KB = 1024バイト

1MB = 1024KB

1GB = 1024MB

1TB = 1024GB

1PB = 1024TB

 (メモリ単位のサイズを 1 バイトに設定するのがより適切です。大きすぎると、コンピュータのメモリが大幅に小さくなります。大きすぎると、メモリを効率的に使用できません。)

 メモリとアドレスは密接な関係がある

        また、各メモリユニットには番号が付けられており(この番号は教育棟の番地に相当します)、このメモリユニットの番号により、CPU はすぐにメモリ空間を見つけることができます。

       日常生活では、家の番号を住所とも呼びますが、コンピューターでは、記憶装置の番号をアドレスとも呼びます。

        C 言語のアドレスに新しい名前が与えられる — ポインター

したがって、次のように理解できます。

        メモリユニット番号 == アドレス == ポインタ


(2) ポインタ変数

ポインタ変数とアドレス取得演算子

        メモリとアドレスについては予備的に理解しました。C 言語に戻りましょう。C 言語で変数を作成すると、実際にはメモリ内のスペースが適用されます。

#include <stdio.h>
int main()
{
 int a = 10;
 return 0;
}

 

たとえば、上記のコードは整数変数 a を作成し、整数 10 を格納するためにメモリに 4 バイトを割り当てます。各バイトにはアドレスがあります。上の図の 4 バイトのアドレスは次のとおりです。

0x007DF784
0x007DF785
0x007DF786
0x007DF787

        上の図では、奇妙な演算子 - & (アドレス演算子) が表示されます。

        機能: 変数のアドレスを取得します。

 たとえば、a のアドレスを pa に入力し、%p で出力できます。

しかし、印刷された住所は1枚だけで、初めて見たaさんの住所とは違うことが分かりました!

その理由は:

        1. コードが実行されるたびに、コンパイラは変数にメモリ領域を再割り当てします。これが、2 回目に出力されるアドレスが 1 回目と異なる理由の説明になります。

        2. 同時に、整数変数は 4 バイトを占めますが、最初のバイトアドレスがわかっていれば、手がかりをたどって 4 バイトのデータにアクセスすることが可能です。

         上の写真では、別の見慣れないオペレーターが表示されています

ポインター変数と逆参照演算子 

        アドレス演算子 (&) を通じて取得するアドレスは、0x006FFD70 などの数値です。場合によっては、この値も後で使用するために保存する必要があります。では、そのようなアドレス値はどこに保存すればよいでしょうか? ?

        答えは、ポインタ変数にあります。

        ポインター変数を作成することで、変数のアドレスを保存できます。

#include<stdio.h>
int main()
{
    int a = 100;
    int* pa = &a;
    *pa = 0;
}

        *pa は、pa に格納されているアドレスが指す空間を見つけることを意味します。*pa は実際には a です。

        したがって、逆参照演算子の機能は、アドレスを通じて対応する変数を見つけることです。


ポインタのサイズ 

        32 ビット マシンは 32 のアドレス バスを想定していることがわかっています。各アドレス ラインからの電気信号はデジタル信号に変換され、1 または 0 になります。そして、32 のアドレス ラインによって生成されるバイナリ シーケンスをアドレスとみなします。アドレスは 32 ビットで、格納するのに 4 バイトが必要です。

        32 ビット マシンの場合、ポインターのサイズは 4 バイトです。

        64 ビット マシンの場合、ポインターのサイズは 8 バイトです。

int main()
{
 printf("%zd\n", sizeof(char *));
 printf("%zd\n", sizeof(short *));
 printf("%zd\n", sizeof(int *));
 printf("%zd\n", sizeof(double *));
 return 0;
}

 図に示すように、デモンストレーション:

         ポインター変数のサイズと型は、同じプラットフォーム上でポインター型の変数が同じサイズである限り、相互に関係がないことに注意してください。


ポインタのサイズは型とは関係がないので、ポインタ型の存在にはどのような意味があるのでしょうか?

この問題について説明する前に、まずポインタ演算について説明します。

ポインタ演算

 ポインタには次の 3 つの基本的な操作があります。

• ポインタ +- 整数

• ポインタ-ポインタ

• ポインタの関係演算

ポインタ +- 整数

#include <stdio.h>
//指针+- 整数
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 for(i=0; i<sz; i++)
 {
 printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
 }
 return 0;
}

 1. 配列はメモリに継続的に保存されるため、最初の要素のアドレスがわかっていれば、手がかりに従って後続のすべての要素を見つけることができます。

2. ポインタの整数の加算および減算は、現在のポインタ型の要素数をスキップすることを意味します。

 ポインタ-ポインタ

//指针-指针
#include <stdio.h>
int my_strlen(char *s)
{
 char *p = s;
 while(*p != '\0' )
 p++;
 return p-s;
}
int main()
{
 printf("%d\n", my_strlen("abc"));
 return 0;
}

 1. 図で実装されているのは strlen 関数のシミュレーション実装です

2. ポインタ-ポインタは、2 つのポインタ変数間の要素の数を表します。

 ポインタの関係演算

//指针的关系运算
#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 while(p<arr+sz) //指针的⼤⼩⽐较
 {
 printf("%d ", *p);
 p++;
 }
 return 0;
}

1. ポインタ関係の比較は、実際にはアドレス サイズの比較です。


ポインタ型の意味

         1. ポインターの型によって、ポインターを逆参照するときにどの程度の権限があるか (一度に操作できるバイト数) が決まります。

//代码2
#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;
	*pc = 0;
	return 0;

 n のアドレスの場合:

 *pc = 0; 前

 後:

         2. ポインタの種類によって、ポインタが前後に移動できる距離(距離)が決まります。

#include <stdio.h>
int main()
{
 int n = 10;
 char *pc = (char*)&n;
 int *pi = &n;
 
 printf("%p\n", &n);
 printf("%p\n", pc);
 printf("%p\n", pc+1);
 printf("%p\n", pi);
 printf("%p\n", pi+1);
 return 0;
}

 

char* 型のポインタ変数 +1 は 1 バイトをスキップし、int* 型のポインタ変数 +1 は 4 バイトをスキップすることがわかります。これがポインタ変数の型の違いによる変化です。​ 

 void* ポインタ

        ポインタ型の中には、特定の型を持たないポインタ (または汎用ポインタ) として理解できる void* 型という特別な型があり、この型のポインタを使用すると、あらゆる種類のアドレスを受け入れることができます。ただし、制限もあり、void* 型のポインターは、ポインター +- 整数および逆参照演算を直接実行できません。

 const 変更ポインタ

詳しい説明を見る

const 変更ポインタicon-default.png?t=N7T8https://mp.csdn.net/mp_blog/creation/editor/134341320

ワイルドポインター

        概念: ワイルド ポインターとは、ポインターが指す位置が認識できない (ランダム、不正確、明確な制限がない) ことを意味します。

ワイルドポインタの原因

1. ポインタが初期化されていない

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

3. ポインタが指すスペースを解放します。

3つの例

#include <stdio.h>
int* test()
{
 int n = 100;
 return &n;
}
int main()
{
 int*p = test();
 printf("%d\n", *p);
 return 0;
}

 ワイルドポインタを回避する方法

1. ポインタの初期化

2. ポインタが境界を越える場合は注意してください

3. ポインタ変数が使用されなくなった場合は、直ちに NULL に設定され、使用前にポインタの有効性がチェックされます。

4. ローカル変数のアドレスを返さないようにする

5.assert断言


終わった〜

著者の許可なく転載することを禁止します

おすすめ

転載: blog.csdn.net/2301_79465388/article/details/134520467