C 言語はポインタを深く理解しています (非常に詳細) (1)

メモリとアドレス

メモリ

記憶とアドレスを比較するとき、まず人生の例を考えてみましょう:
寮の建物があり、あなたはその建物に入れられたとします。2 階には 100 の部屋がありますが、部屋には番号が付いていません。友達の 1 人があなたと遊びに来ます。 . 、
あなたを見つけたい場合は、家から家へと移動する必要があり、非常に非効率ですが、次のように、各部屋をフロアとそのフロアにある部屋に応じて番号付けすると、次のようになります。

⼀楼:101102103...
⼆楼:201202203...

部屋番号があれば、友人が部屋番号を取得すれば、すぐに部屋を見つけてあなたを見つけることができます。

名前も同様で、名前がないと、知らない人をすぐに見つけるのは難しいです。

したがって、場合によってはナンバリングによって作業の効率が向上することがわかります。

メモリにはデータが保存されていることがわかっています。このデータを計算する必要がある場合は、CPU (中央処理装置) を使用します。CPU はデータを使用する必要がある場合、メモリからデータを読み取り、処理されたデータが生成されます。メモリに戻す
. コンピュータを購入するとき、8GB/16GB/32GB という表示がよくありますが、これらのメモリ領域を効率的に管理するにはどうすればよいでしょうか?
実際には、メモリはメモリユニットに分割されており、各メモリユニットのサイズは 1 バイトです。
コンピュータの一般的なユニットをいくつか加えてみましょう。

bit - ⽐特位     1byte = 8bit
byte - 字节      1KB = 1024byte
KB               1MB = 1024KB
MB               1GB = 1024MB
GB               1TB = 1024GB
TB               1PB = 1024TB
PB

このうち、各メモリユニットは学生寮に相当します。クラスメイトが住む8人部屋と同じように、1バイトの空間に8ビットが入ります。各人が1ビット、各メモリユニットにも1つずつあります。番号(この番号
はこのメモリユニットの番号により、CPU はすぐにメモリスペースを見つけることができます。
日常生活では、家の番号を住所とも呼びますが、コンピューターでは、記憶装置の番号をアドレスとも呼びます。C言語ではアドレスにポインタという名前を付けていますので、メモリユニットの番号はアドレスでありポインタでもあると
理解でき、メモリの格納方法は図のようになります。


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

アドレス指定の理解

CPU がメモリ内の特定のバイト空間にアクセスするとき、CPU はそのバイト空間がメモリ内のどこにあるかを知る必要があります。メモリ内には多くのバイトがあるため、メモリをアドレス指定する必要があります (多数の寮があり、寮が必要とするのと同じです)番号が付けられます) .)。

コンピュータのアドレス指定は各バイトのアドレスを記録するのではなく、ハードウェア設計によって行われます。

コンピューターには多数のハードウェア ユニットがあり、ハードウェア ユニットが連携して動作する必要があります。いわゆるコラボレーションでは、少なくとも相互にデータを送信できなければなりません。しかし、ハードウェアとハ​​ードウェアは互いに独立しているため、どのように通信するのでしょうか? 答えは簡単、「線」で結ぶだけです。
また、CPU とメモリの間では大量のデータのやり取りが行われるため、両者を線で接続する必要があります。ただし、今日はアドレスバスと呼ばれる一連のラインに注目します。

32 ビット マシンには 32 のアドレス バスがあり、各ラインには 0 と 1 (電気パルスの有無) を示す 2 つの状態しかなく、1 つのラインで 2 つの意味を表現でき、2 つのラインで表現できることを簡単に理解できます
4つの意味など。32 のアドレス行は 2^32 の意味を表すことができ、それぞれの意味がアドレスを表します。アドレス情報はメモリに送られ、メモリ内でアドレスに対応するデータが求められ、データバスを介してCPU内部レジスタにデータが転送されます。したがって、x86 環境では、char* ポインタ変数と int* ポインタ変数は両方とも 4バイトであり、32 ビット マシンには 32 のアドレス ラインがあるため、アドレス ラインに伝送される電気信号はデジタル信号に変換されます
ここに画像の説明を挿入します

, 結果は 32 0/1 で構成される追加のシーケンスがアドレスです。x64
環境では、64 のアドレス行があるため、アドレスは 64 の 0/1 で構成されるバイナリ シーケンスです。このようなアドレスを格納するには、8 バイトが必要です。

ポインタ変数とアドレス

アドレス取得演算子 (&)

メモリとアドレスの関係を理解し​​たら、C 言語に戻りましょう。C言語で変数を作成すると、実際には次のようなメモリ内の領域が適用されます

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

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

0x006FFD70
0x006FFD71
0x006FFD72
0x006FFD73

では、どうすれば a のアドレスを取得できるのでしょうか?

ここでは、演算子 (&)-アドレス演算子 # const 変更されたポインターを学習する必要があります。

#include <stdio.h>
int main()
{
    
    
int a = 10;
&a;//取出a的地址
printf("%p\n", &a);
return 0;
}

上の図の例によれば、出力および処理されます: 006FFD70&a は、a が占有する 4 バイトのうち小さい方のバイトのアドレスを取り出します。整数変数は 4 バイトを占有しますが、最初のバイトだけを知る必要があります
ここに画像の説明を挿入します
。アドレス、手がかりをたどって 4 バイトのデータにアクセスすることも可能です。

ポインタ変数逆参照演算子 (*)

ポインタ変数

次に、アドレス演算子 (&) を介して取得するアドレスは、0x006FFD70 のような値です。場合によっては、この値を後で使用するために保存する必要があります。では、そのようなアドレス値はどこに保存すればよいでしょうか? 答えは「ポインタ変数」です。

#include <stdio.h>
int main()
{
    
    
int a = 10;
int* pa = &a;//取出a的地址并存储到指针变量pa中
return 0;
}

ポインタ変数も変数の一種で、アドレスを格納するために使用され、ポインタ変数に格納された値がアドレスとして認識されます。

ポインタ型を逆アセンブルする方法

pa の型は int* であることがわかりましたが、ポインタの型をどのように理解すればよいでしょうか?

int a = 10;
int * pa = &a;

ここで、p a の左側に書かれているのはint* です * は pa がポインタ変数であることを示し、その前の int は pa が整数型のオブジェクトを指していることを示します 同様にchar 型の変数がある場合
, ch, ch アドレスはどのタイプのポインタ変数に入れる必要がありますか?

char ch = 'w';
char *pc = &ch;

逆参照演算子

ポインタ変数にアドレスを保存した後、それをどのように使用するのでしょうか? アドレスのみが保存されているため、アドレスに基づいて該当するデータを見つける必要があります。対応するデータを検索するには、逆参照演算子 (*) を使用する必要があります。

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

* pa は、pa に格納されているアドレスが指す空間を見つけることを意味します。Pa は実際には変数です。つまり、 pa=0 で、この演算子は a を 0 に変更します
。しかし、ここで疑問があります: なぜポインターを使用するのでしょうか? 変更はどうすればよいですか?変数?なぜ単に a=0 ではないのでしょうか?
実際、場合によっては、ポインタを使用した方が便利です。例:
ここに画像の説明を挿入します
出力された a 値と b 値が異なることがわかります。その理由は、異なる方法で渡しているためです。1 つはアドレスが渡され、関数内でアドレスの b を変更しても、関数終了時に変更後の値は保持されませんが、渡されたアドレスの a は変更後も保持されます。
例 (例はあまり良くないかもしれませんが、ほぼ同じ意味です): あなたは改装中の家を持っています。改装後、家の住所は変わりませんが、家は変わりました。

ポインタ変数のサイズ

前回の内容で、32 ビットマシンは 32 個のアドレスバスを想定していることを学びました。各アドレス線からの電気信号はデジタル信号に変換され、1 または 0 になります。そして、32 個のアドレス線によって生成されるバイナリシーケンスをアドレスの場合、アドレスは 32 ビットであり、格納するのに 4 バイトが必要です。
ポインタ変数をアドレスの格納に使用する場合、ポインタ変数のサイズは 4 バイトのスペースである必要があります。
同様に、64 ビット マシンの場合、64 のアドレス ラインがあると仮定すると、アドレスは 64 のバイナリ ビットで構成されるバイナリ シーケンスであり、格納するには 8 バイトのスペースが必要で、ポインタのサイズは 8 バイトです。

ここに画像の説明を挿入します
ここに画像の説明を挿入します
32
ビット プラットフォーム上のアドレスは 32 ビット、ポインタ変数のサイズは 4 バイト
64 ビット プラットフォーム上のアドレスは 64 ビット、ポインタ変数のサイズは 8 バイト
ポインタのサイズと型に注意してくださいポインタ型変数が同じプラットフォームで同じサイズである限り、変数は無関係です。

ポインタ変数の型の意味

ポインタ変数のサイズは型に関係なく、ポインタ変数である限り、同じプラットフォーム上ではサイズは同じですが、なぜさまざまなポインタ型があるのでしょうか?

ポインター逆参照

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

デバッグすると、コード 1 は n の 4 バイトすべてを 0 に変更しますが、コード 2 は n の最初のバイトのみを 0 に変更することがわかります。
結論: ポインターの型によって、ポインターを逆参照するときにどの程度の権限があるか (一度に操作できるバイト数) が決まります。
たとえば、char* のポインタを逆参照すると 1 バイトしかアクセスできませんが、int* のポインタを逆参照すると 4 バイトにアクセスできます。

ポインタ±整数

#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 バイトをスキップする
ことがわかります。これがポインタ変数の型の違いによる変化です。結論: ポインターの種類によって、ポインターが前後に移動する距離(距離)が決まります。

const 変更ポインタ

const 変更された変数

変数は変更できます。変数のアドレスがポインタ変数に与えられている場合、ポインタ変数を介して変数を変更することもできます
しかし、変数にいくつかの制限を設けて変更できないようにしたい場合はどうすればよいでしょうか? これがconstが行うことです

#include <stdio.h>
int main()
{
    
    
int m = 0;
m = 20;//m是可以修改的
const int n = 0;
n = 20;//n是不能被修改的
return 0;
}

上記のコードの n は変更できません。実際、n は本質的には変数です。ただし、const で変更した後は文法上の制限があります。コード内で n を変更する限り、文法規則に準拠せず、エラーが報告され、「n を直接変更する方法はありません」という結果になります。しかし、n をバイパスし、n のアドレスを使用し、n を変更すれば、それを行うことができます。

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

ここに画像の説明を挿入します
ここで 1 つが実際に変更されていることがわかりますが、なぜ n が const によって変更されるのかを考える必要があります。これは、変更できないようにするためです。p が n のアドレスを取得すると、n を変更できます。これは、const の制限を破ります。これは不合理です。したがって、p は、n のアドレスを取得しても、n を変更できないようにする必要があります。 n.

const 変更されたポインタ変数

#include <stdio.h>
//代码1
void test1()
{
    
    
int n = 10;
int m = 20;
int *p = &n;
*p = 20;
p = &m; 
}
void test2()
{
    
    
//代码2
int n = 10;
int m = 20;
const int* p = &n;
*p = 20;   x
p = &m; 
}
void test3()
{
    
    
int n = 10;
int m = 20;
int *const p = &n;
*p = 20; 
p = &m;    x
}
void test4()
{
    
    
int n = 10;
int m = 20;
int const * const p = &n;
*p = 20;   x
p = &m;    x
}
int main()
{
    
    
//测试⽆const修饰的情况
test1();
//测试const放在*的左边情况
test2();
//测试const放在*的右边情况
test3();
//测试*的左右两边都有const
test4();
return 0;
}

結論: const がポインタ変数を変更する場合
• const を * の左側に置くと、ポインタが指す内容を変更するため、ポインタが指す内容はポインタを介して変更できなくなります。ただし、ポインター変数自体の内容は変更可能です。
• const を * の右側に配置すると、ポインタ変数自体が変更され、ポインタ変数の内容は変更できなくなりますが、ポインタが指す内容はポインタを介して変更できます

おすすめ

転載: blog.csdn.net/2301_79178723/article/details/132446707