指针&指针变量
指针
计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如 int 占用 4 个字节,char 占用 1 个字节。为了正确地访问这些数据,必须为每个字节都编上号码,就像门牌号、身份证号一样,每个字节的编号是唯一的,根据编号可以准确地找到某个字节
我们将内存中字节的编号称为地址(Address) / **指针(Pointer)
地址从 0 开始依次增加,对于 32 位环境,程序能够使用的内存(可寻址空间**)为 232Bytes=4GB,最小的地址为 0,最大的地址为 0XFFFFFFFF
指针变量
| 定义 | 用于存放地址的变量 |
|---|---|
| 声明 | int* intptr(空格位置无影响) 含义:intptr可以保存一个int变量的地址 |
| 大小 | 1word (32bits机器即为4bytes,64bits机器即为8bytes) |
| 操作 | - &:取地址(address-of)操作符,获取操作数地址 - :解引用(dereference)操作符,对指针解引用,*获取当前指针指向地址区域中的值 |
| 运算 | 对指针的+,-,++,—,+=等运算都是针对内存中地址的运算,ptr+n即表示地址+n*元素长度(取决于ptr指向类型)** |
程序分析1—取地址和解引用
#include <stdio.h>void main(){int x = 0, y = 1;int* ptr = &y;*ptr = x;printf_s("x = %d, y = %d\n", x, y);// 结果x = 0, y = 0}
反汇编中重要部分分析
int* ptr = &y;lea eax,[y] //lea eax,[y],把&y写入eax中mov dword ptr [ptr],eax //把eax值放在ptr指向空间,即写入&y*ptr = x;mov eax,dword ptr [ptr] //把ptr指向空间的数据写入eax,即写入&ymov ecx,dword ptr [x] //把&x指向空间的数据写入ecx,即写入xmov dword ptr [eax],ecx //把ecx中数据写入对eax取址指向的地址空间,即y=[%y]=ecx=x
程序分析2—指针类型转换
#include <stdio.h>void main(){int x = 0x8041;char* ptr = (char*)&x;printf_s("%c", *ptr);// 结果A(A的ASCII编码65即41h)}
char的大小为1byte但char依旧为指针,大小4byte,用ptr保存&x
对ptr解引用,即改变&x指向空间的值,但经过强转后**不再是对int解引用而是对char*,所以结果只会看到&x指向空间的第一个字节,即8041中的41
程序分析3—指针运算**
#include <stdio.h>void main(){int x = 0x8041;char* ptr = (char*)&x;int* intptr = &x;}
调试
|
|
- char类型ptr+1,ptr中地址增加了1byte(char元素长度)
- int类型intptr+1,intptr中地址增加4bytes(int元素长度)
变化sizeof(指向类型)k个bytes,
即**留出空间给指向类型的k个元素* |
| —- | —- |
函数指针(变量)
#include <stdio.h>int add(int a, int b) {return a + b;}void main(){int x = 0, y = 1;int (*func)(int,int) = &add;//或者int (*func)(int,int) = add;printf_s("%d", func(x, y));//或者printf_s("%d", (*func)(x, y));}
| 声明 | 返回类型函数指针参数: int (*func)(int,int): |
|---|---|
| 意义 | 指针func保存了add()的起始地址 |
| 要点 | - 函数指针”“需要和指针名称用()包围 - 调用时可直接func()也可显式说明(fun)() |
数组
C中数组名和指向数组首元素指针是同义的,当ptr指向数组后:
- 元素地址: ptr == a ptr + k == &a[k]
元素值: (ptr+k) ==(a+k) == a[k]
#include <stdio.h>void main(){int a[5] = {0,1,2,3,4};int* ptr = a;printf_s("ptr = %p\n", ptr);printf_s("a = %p\n", a);printf_s("ptr + 2 = %p\n", ptr + 2);printf_s("&a[2] = %p\n", &a[2]);printf_s("*(ptr+2) = %d\n", *(ptr + 2));printf_s("*(a+2) = %d\n", *(a + 2));printf_s("*(ptr+2) = %d\n", *(ptr+2));printf_s("*(a+2) = %d\n", *(a+2));printf_s("a[2] = %d\n", a[2]);}
二维数组
参考链接: C语言二维数组指针
示例代码 ```cinclude
void main() { //为了方便统一使用&p打印,使用16进制数 int a[2][3] = {0x00,0x01,0x02,
0x10,0x11,0x12};
int(ptr)[3] = a; printf_s(“a = %p\n”, a); printf_s(“a = %p\n”, a); printf_s(“*a = %p\n\n”, a);
printf_s(“a+1 = %p\n”, a + 1); printf_s(“(a+1) = %p\n”, (a + 1)); printf_s(“(a+1) = %p\n”, (a + 1)); printf_s(“(a+1) = %p\n\n”, (a + 1));
printf_s("a[1] = %p\n", a[1]);printf_s("*a[1] = %p\n", *a[1]);printf_s("a[1][1] = %p\n", a[1][1]);printf_s("*(*(a+1)+1) = %p\n", *(*(a + 1) + 1));printf_s("*(a[1] + 1) = %p\n", *(a[1] + 1));
}
结果:<br />- 观察内存,得到结论:二维数组在概念上是二维的,但在**内存中所有元素都是连续排列**的- 数组名a是int[2][3]起始地址- 一次解引用***a:依旧为地址**,概念上第一个int[3]起始地址- 二次解引用****a:取到第一个int[3]起始元素值**,可监视验证- ⭐二维数组a[2][3]获取元素值的方法- **①两次解引用****- **②int[j][k]**- **③*(a[j]+k)**- **④*((int*)a+4*j+k):如果没有int*强转,直接运算,变化以int[3]大小为单位****<br />**个人理解**- 数组b[3]={0,1,2},*b可得到0,但二维数组a[2][3]必须两次解引用,可以**看作二维数组对一维数组额外做了一层封装从而*a=b,**a=*b,**可通过监视验证,- 结合👆指针运算,a+1使地址+sizeof(int[3])即12(0xA)字节,而*a+1使地址+sizeof(int)即4(0x04)字节<a name="xAUzd"></a>#### 数组指针&指针数组参考链接: [C语言数组指针和指针数组](http://c.biancheng.net/view/335.html)```c#include <stdio.h>void main(){int a[2][3] = { 0x00,0x01,0x02,0x10,0x11,0x12 };int(*aptr)[3] = a;int b[3] = { 0,1,2 };int* bptr = b;}
如上,可以创建指向数组的指针对数组访问,对于二维数组同样适用,但极易和指针数组搞混
| 指针数组 | 声明: int ptr[3] 含义: 运算优先级[]>,因此声明构成一个数组定义 - 数组名ptr - 大小为3 - 元素类型为int* |
|---|---|
| 数组指针 | 声明: int(aptr)[3] 含义: 运算优先级()>[],因此声明构成一个指针定义 - 指针名称aptr - *指向一个包含3个int元素的数组 |
字符串
C中没有string这一基本类型,实际上是一个以“\0”结尾的char[],并且char[]每个元素声明后会初始化为’\0’(0x00)
结构体&联合体
Structure
参考链接: C 结构体
定义:C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型
声明:结构体声明有三种形式,其中使用typedef可以创建自定义的新类型,并通过该类型声明新的结构体变量,见👇
存储空间:结构的大小取决于其对齐的结果
Union
参考链接: C 共用体
定义:union是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型,但是任何时候只能有一个成员带有值
声明:见示例
存储空间:Union的大小只需满足最大成员即可
示例(vs默认8字节对齐,开头主动声明为4字节)
#include <stdio.h>#pragma pack(4)typedef struct{int a[7];char b;double c;} Stru;union Data{int i;float f;char str[20];} data;void main(){Stru s;Data u;printf_s("%d\n", sizeof(s));printf_s("%d\n", sizeof(u));}
结果为40,20,structure分析见👇,union的最大成员为char[20],故为20字节
结构成员对齐
参考链接: C语言内存对齐和结构补齐
为了优化CPU从内存中取数据的效率,compiler会对结构体对齐
对齐原则
- 第一个成员的**首地址为0**
- 每个成员的**首地址是size的整数倍**,当size大于基准时,按照基准对齐
最后以结构总体对齐:结构体**结束地址要是基准的整数倍**
#pragma pack(4) //以4字节对齐typedef struct{int a[7];char b;double c;} Stru;
int[]: 从0开始,占据7*4=28bytes
- b: sizeof(char)=1,任何位置都是1的倍数,则直接放入
- sizeof(double)=8>4,按4字节对齐,当前地址29不是4的整数倍,则29+3=32,补3字节
- 结束地址28+1+3+8=40,满足4的整数倍
- 最终结构的size为40bytes

32位机以双字(dword)进行传输,一次读8bytes(2*32bits),经过对齐,40bytes内容需要CPU从内存中读取5次
选择题知识点
⭐给定代码
#include <stdio.h>void main(){char a[100] = { "Hello World!" };a[99] = *((char*)(((int)&a[0]) + 4));int b[100] = { 1,2,3,4,5,6,7 };b[99] = *((int*)(((int)&b[0]) + 4));}
①a[99]的值等于?a[4]
(int)&a[0]获取&a[0],转化为数字,直接+4后地址前进4字节,即为&a[4],对其解引用,获取a[4]
②b[99]等于?b[1]
(int)&b[0]获取&b[0],转化为数字,直接+4后地址前进4字节,即为&b[1],解引用,获取b[1]
**结论:地址化为int类型后+1表示地址前进1字节不是所有现代处理器都需要对齐
⭐计算机中地址A,A+1,A+2,A+3保存int 256,令int aptr=A在另一台设备上B,B+1,B+2,B+3保存int 256,令int bptr = B,不同设备int都是4字节长度,则 |
①A+1中数据和B+1相同🚫
②A+1中数据和B+2相同🚫
③aptr == bptr ✅ | 关于①②,设计到数据格式中大小端问题256=0x0100,同时为有符号数,所以实际表示为4字节:0x00000100,将其入栈保存
- 大端下栈帧向高地址生长,A:00,A+1:01,A+2:00,A+3:00
- (X86默认)小端下栈帧向低地址生长,A:00,A+1:00,A+2:01,A+3:00
此时①②错误,③:对int* ptr解引用,无论地址格式,都获取其中int值,即256 | | —- | —- |VS中的内存窗口:以多种方式显示内存情况,但是不出现变量名称(见👆结构对齐配图)
- 给定代码
则语句①(ary+3)=10②pary[4]=10③pary++④ary++中,错误的是ary++,数组名(起始地址)不可直接运算int a[10];int *ptr = a;
6.给定代码,结果为10 5*,sizeof计算分配空间的长度,strlen只计算字符串有效长度(无\0) ```cinclude
include
void main() { char s[10] = “hello”; printf(“%d %d”, sizeof(s), strlen(s)); }
7. 给定代码```c#include <stdio.h>#include <string.h>void main(){char str1[] = "abc";const char* str2 = "abc";const char* str3 = "abc";int i = (str2 == str3);}
则调试结果如下
- 对比6,当没有指定char[]大小时,sizeof计算”字符+\0”空间
- 由于char在C中存放在专门的段中,对于相同字符串,不同指针实际指向一个地址

- C语言在数组越界访问时不会报错
