指针&指针变量

指针

计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如 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—取地址和解引用

  1. #include <stdio.h>
  2. void main()
  3. {
  4. int x = 0, y = 1;
  5. int* ptr = &y;
  6. *ptr = x;
  7. printf_s("x = %d, y = %d\n", x, y);
  8. // 结果x = 0, y = 0
  9. }

反汇编中重要部分分析

  1. int* ptr = &y;
  2. lea eax,[y] //lea eax,[y],把&y写入eax中
  3. mov dword ptr [ptr],eax //把eax值放在ptr指向空间,即写入&y
  4. *ptr = x;
  5. mov eax,dword ptr [ptr] //把ptr指向空间的数据写入eax,即写入&y
  6. mov ecx,dword ptr [x] //把&x指向空间的数据写入ecx,即写入x
  7. mov dword ptr [eax],ecx //把ecx中数据写入对eax取址指向的地址空间,即y=[%y]=ecx=x

程序分析2—指针类型转换

  1. #include <stdio.h>
  2. void main()
  3. {
  4. int x = 0x8041;
  5. char* ptr = (char*)&x;
  6. printf_s("%c", *ptr);
  7. // 结果A(A的ASCII编码65即41h)
  8. }

char的大小为1byte但char依旧为指针,大小4byte,用ptr保存&x
对ptr解引用,即改变&x指向空间的值,但经过强转后**不再是对int
解引用而是对char*,所以结果只会看到&x指向空间的第一个字节,即8041中的41
image.png
程序分析3—指针运算**

  1. #include <stdio.h>
  2. void main()
  3. {
  4. int x = 0x8041;
  5. char* ptr = (char*)&x;
  6. int* intptr = &x;
  7. }

调试
image.png

| DFA (1).png |
- char类型ptr+1,ptr中地址增加了1byte(char元素长度)
- int
类型intptr+1,intptr中地址增加4bytes(int元素长度)


变化sizeof(指向类型)k个bytes,
即**留出空间给指向类型的k个元素*
| | —- | —- |

函数指针(变量)

  1. #include <stdio.h>
  2. int add(int a, int b) {
  3. return a + b;
  4. }
  5. void main()
  6. {
  7. int x = 0, y = 1;
  8. int (*func)(int,int) = &add;
  9. //或者int (*func)(int,int) = add;
  10. printf_s("%d", func(x, y));
  11. //或者printf_s("%d", (*func)(x, y));
  12. }
声明 返回类型函数指针参数: int (*func)(int,int):
意义 指针func保存了add()的起始地址
要点
- 函数指针”“需要和指针名称用()包围
- 调用时可直接func()也可显式说明(
fun)()

数组

C中数组名和指向数组首元素指针是同义的,当ptr指向数组后:

  • 元素地址: ptr == a ptr + k == &a[k]
  • 元素值: (ptr+k) ==(a+k) == a[k]

    1. #include <stdio.h>
    2. void main()
    3. {
    4. int a[5] = {0,1,2,3,4};
    5. int* ptr = a;
    6. printf_s("ptr = %p\n", ptr);
    7. printf_s("a = %p\n", a);
    8. printf_s("ptr + 2 = %p\n", ptr + 2);
    9. printf_s("&a[2] = %p\n", &a[2]);
    10. printf_s("*(ptr+2) = %d\n", *(ptr + 2));
    11. printf_s("*(a+2) = %d\n", *(a + 2));
    12. printf_s("*(ptr+2) = %d\n", *(ptr+2));
    13. printf_s("*(a+2) = %d\n", *(a+2));
    14. printf_s("a[2] = %d\n", a[2]);
    15. }

    结果:
    image.png

    二维数组

    参考链接: C语言二维数组指针
    示例代码 ```c

    include

    void main() { //为了方便统一使用&p打印,使用16进制数 int a[2][3] = {0x00,0x01,0x02,

    1. 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));

  1. printf_s("a[1] = %p\n", a[1]);
  2. printf_s("*a[1] = %p\n", *a[1]);
  3. printf_s("a[1][1] = %p\n", a[1][1]);
  4. printf_s("*(*(a+1)+1) = %p\n", *(*(a + 1) + 1));
  5. printf_s("*(a[1] + 1) = %p\n", *(a[1] + 1));

}

  1. 结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2019/png/670787/1576915132286-86f278ca-0ac3-4e5d-9fb4-c04e157d80da.png#align=left&display=inline&height=226&name=image.png&originHeight=341&originWidth=577&size=41371&status=done&style=none&width=383)
  2. - 观察内存,得到结论:二维数组在概念上是二维的,但在**内存中所有元素都是连续排列**的
  3. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/670787/1578225272834-9a7585cc-ea2f-4a94-8d1b-370a99409302.png#align=left&display=inline&height=62&name=image.png&originHeight=124&originWidth=819&size=9034&status=done&style=none&width=409.5)
  4. - 数组名aint[2][3]起始地址
  5. - 一次解引用***a:依旧为地址**,概念上第一个int[3]起始地址
  6. - 二次解引用****a:取到第一个int[3]起始元素值**,可监视验证
  7. ![image.png](https://cdn.nlark.com/yuque/0/2019/png/670787/1576915563128-0a495c50-7e5b-4139-8bbe-cb89fc958846.png#align=left&display=inline&height=75&name=image.png&originHeight=110&originWidth=575&size=8694&status=done&style=none&width=394)
  8. - ⭐二维数组a[2][3]获取元素值的方法
  9. - **①两次解引用****
  10. - **②int[j][k]**
  11. - **③*(a[j]+k)**
  12. - **④*((int*)a+4*j+k):如果没有int*强转,直接运算,变化以int[3]大小为单位**
  13. **<br />**个人理解**
  14. - 数组b[3]={0,1,2},*b可得到0,但二维数组a[2][3]必须两次解引用,可以**看作二维数组对一维数组额外做了一层封装从而*a=b,**a=*b,**可通过监视验证,
  15. ![image.png](https://cdn.nlark.com/yuque/0/2019/png/670787/1576916153033-ad2be860-0736-4059-9941-d9b0fe5fc7f5.png#align=left&display=inline&height=77&name=image.png&originHeight=132&originWidth=650&size=11545&status=done&style=none&width=379)
  16. - 结合👆指针运算,a+1使地址+sizeof(int[3])即12(0xA)字节,而*a+1使地址+sizeof(int)即4(0x04)字节
  17. ![image.png](https://cdn.nlark.com/yuque/0/2019/png/670787/1576918608003-2b36c0a8-258f-422b-9112-aaa86fb27579.png#align=left&display=inline&height=72&name=image.png&originHeight=112&originWidth=584&size=12035&status=done&style=none&width=374)
  18. <a name="xAUzd"></a>
  19. #### 数组指针&指针数组
  20. 参考链接: [C语言数组指针和指针数组](http://c.biancheng.net/view/335.html)
  21. ```c
  22. #include <stdio.h>
  23. void main()
  24. {
  25. int a[2][3] = { 0x00,0x01,0x02,0x10,0x11,0x12 };
  26. int(*aptr)[3] = a;
  27. int b[3] = { 0,1,2 };
  28. int* bptr = b;
  29. }

如上,可以创建指向数组的指针对数组访问,对于二维数组同样适用,但极易和指针数组搞混

指针数组 声明: int ptr[3]
含义: 运算优先级[]>
,因此声明构成一个数组定义
- 数组名ptr
- 大小为3
- 元素类型为int*
数组指针 声明: int(aptr)[3]
含义: 运算优先级()>[],因此声明构成一个指针定义
- 指针名称aptr
- *指向一个包含3个int元素的数组


字符串

C中没有string这一基本类型,实际上是一个以“\0”结尾的char[],并且char[]每个元素声明后会初始化为’\0’(0x00)
image.png


结构体&联合体

Structure

参考链接: C 结构体
定义:C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型
声明:结构体声明有三种形式,其中使用typedef可以创建自定义的新类型,并通过该类型声明新的结构体变量,见👇
存储空间:结构的大小取决于其对齐的结果

Union

参考链接: C 共用体
定义:union是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型,但是任何时候只能有一个成员带有值
声明:见示例
存储空间:Union的大小只需满足最大成员即可
示例(vs默认8字节对齐,开头主动声明为4字节)

  1. #include <stdio.h>
  2. #pragma pack(4)
  3. typedef struct
  4. {
  5. int a[7];
  6. char b;
  7. double c;
  8. } Stru;
  9. union Data
  10. {
  11. int i;
  12. float f;
  13. char str[20];
  14. } data;
  15. void main()
  16. {
  17. Stru s;
  18. Data u;
  19. printf_s("%d\n", sizeof(s));
  20. printf_s("%d\n", sizeof(u));
  21. }

结果为40,20,structure分析见👇,union的最大成员为char[20],故为20字节


结构成员对齐

参考链接: C语言内存对齐和结构补齐
为了优化CPU从内存中取数据的效率,compiler会对结构体对齐
对齐原则

  1. 第一个成员的**首地址为0**
  2. 每个成员的**首地址是size的整数倍**,当size大于基准时,按照基准对齐
  3. 最后以结构总体对齐:结构体**结束地址要是基准的整数倍**

    1. #pragma pack(4) //以4字节对齐
    2. typedef struct
    3. {
    4. int a[7];
    5. char b;
    6. double c;
    7. } Stru;
  4. int[]: 从0开始,占据7*4=28bytes

  5. b: sizeof(char)=1,任何位置都是1的倍数,则直接放入
  6. sizeof(double)=8>4,按4字节对齐,当前地址29不是4的整数倍,则29+3=32,补3字节
  7. 结束地址28+1+3+8=40,满足4的整数倍
  8. 最终结构的size为40bytes

image.png
32位机以双字(dword)进行传输,一次读8bytes(2*32bits),经过对齐,40bytes内容需要CPU从内存中读取5次


选择题知识点

  1. ⭐给定代码

    1. #include <stdio.h>
    2. void main()
    3. {
    4. char a[100] = { "Hello World!" };
    5. a[99] = *((char*)(((int)&a[0]) + 4));
    6. int b[100] = { 1,2,3,4,5,6,7 };
    7. b[99] = *((int*)(((int)&b[0]) + 4));
    8. }

    ①a[99]的值等于?a[4]
    (int)&a[0]获取&a[0],转化为数字,直接+4后地址前进4字节,即为&a[4],对其解引用,获取a[4]
    image.png
    ②b[99]等于?b[1]
    (int)&b[0]获取&b[0],转化为数字,直接+4后地址前进4字节,即为&b[1],解引用,获取b[1]
    image.png
    **结论:地址化为int类型后+1表示地址前进1字节

  2. 不是所有现代处理器都需要对齐

  3. ⭐计算机中地址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 | | —- | —- |

  4. VS中的内存窗口:以多种方式显示内存情况,但是不出现变量名称(见👆结构对齐配图)

  5. 给定代码
    1. int a[10];
    2. int *ptr = a;
    则语句①(ary+3)=10②pary[4]=10③pary++④ary++中,错误的是ary++,数组名(起始地址)不可直接运算

    6.给定代码,结果为
    10 5*
    ,sizeof计算分配空间的长度,strlen只计算字符串有效长度(无\0) ```c

    include

    include

void main() { char s[10] = “hello”; printf(“%d %d”, sizeof(s), strlen(s)); }

  1. 7. 给定代码
  2. ```c
  3. #include <stdio.h>
  4. #include <string.h>
  5. void main()
  6. {
  7. char str1[] = "abc";
  8. const char* str2 = "abc";
  9. const char* str3 = "abc";
  10. int i = (str2 == str3);
  11. }

则调试结果如下

  • 对比6,当没有指定char[]大小时,sizeof计算”字符+\0”空间
  • 由于char在C中存放在专门的段中,对于相同字符串,不同指针实际指向一个地址

image.png

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