WRY

Where Are You?
You are on the brave land,
To experience, to remember...

0%

C语言内存管理和指针交流研讨

随手记了笔记,等老师可以公开PPT之后对应补全,荣老师讲的太好了,十分敬佩

2021.04.01来填坑啦,但仍有一些细节的问题,没有搞清楚

老师的PPT文件

基本概念

一个变量的声明有如下两个关键部分组成

  • 变量类型:约定了申请多大的内存
  • 变量名称:用于识别访问到这块内存

变量类型又可以分为两大类

  • 非数组类型,其中包含指针类型
  • 数组类型

非数组类型

  • int
  • double
  • float
  • 指针类型,下面详细介绍
  • ...

sizeof(type)求的都是变量约定占用空间的大小,因此该函数在很多时候编译器就可以计算

数组类型

  • int a[2] 变量类型为int [2],变量名称为a
  • float b[2][3] 变量类型float [2][3],变量名称为b
  • double c[2][3][4] 变量类型double[2][3][4],变量名称为c
  • ...

sizeof(int[2]) 是符合语法的

所有的数组类型,无论在表达上是二维、三维或者更高维度,但本质上均是特殊的一维构造类型,包含两个要素:

老师重点强调了C语言数组变量类型是一维的,没有多维之说

  • 元素个数
  • 元素类型

例如float b[2][3]包含两个元素,元素类型是float[3]

指针类型

指针类型就是一个普通的非数组变量类型,任何一个变量类型,都有指向其的一个指针类型,反之依然。例如

intfloatdouble <==> int*float*double*

int[2]float[2][3]double[2][3][4] <==> int(*)[2]float(*)[2][3]double(*)[2][3][4]

指针变量类型本身也是一个非数组变量类型,也有其对应的指针类型

int*float*double* <==> int(**)[2]float(**)[2][3]double(**)[2][3][4]

在之前的资料中,指针数组是指一个数组中,每个元素都是指针;数组指针是指一个指向数组的指针

1
2
char* strs[4]; // 指针数组,一个每个元素都是char*的数组
char (*pstr) [4]; // 数组指针,一个指向char[4]的指针

老师强调了关于数组指针和指针数组的说法其实是不太有道理的,建议从数组类型的两个要素理解,上述的说法不值一提。

数据类型总结

关于Element Type和Corresponding Pointer Type下文将会介绍。

内存六元组模型

M = {Address, Variable_Type, Name, Size, Value, Value_Type}

M: 申明变量系统给分配的一段内存,粗略可以分为两部分:物理属性和逻辑属性,其中只有Address和Size是必不可少的,见malloc的例子。

  • 物理属性,机器知道的内容
    • Address: 在内存中的第一个字节的地址,由系统分配,一旦确定无法修改
    • Variable_Type:变量类型
    • Name:变量名称
    • Size:内存大小,必须要,且不可修改
  • 逻辑属性,程序员知道的内容
    • Value:内存表示的值
    • Value_Type:表示值的类型
      • 非数组类型,Value_Type=Variable_Type, Value是通过Value_Type去观察这段内存获得的值
      • 数组类型,指向数组里元素变量类型的指针类型,Value是数组第一个元素所处内存的第一个字节编号(等于Address)

内存模型举例

  • int a=10

    类别
    Address 0x0028FF11
    Variable Type int
    Name a
    Size 4
    Value 10
    Value Type int
  • int* p=NULL

    类别
    Address 0x0046A521
    Variable Type int*
    Name p
    Size 4
    Value NULL
    Value Type int*
  • int e[2]

    类别
    Address 0x0076AB11
    Variable Type int[2]
    Name p
    Size 8
    Value 0x0076AB11
    和Address相等
    Value Type int*
  • int g[2][3]

    类别
    Address 0x0098B11D
    Variable Type int[2][3]
    Name g
    Size 24
    Value 0x0098B11D
    和Address相等
    Value Type int(*)[3]
  • m_struct h

    1
    2
    3
    4
    struct m_struct{
    int a;
    float b;
    }
    类别
    Address 0x0085D611
    Variable Type struct m_struct
    Name h
    Size 8
    Value Invisible
    Value Type struct m_struct
  • malloc(16)

    类别
    Address 0x00351728
    Variable Type N/A
    Name N/A
    Size 16
    Value N/A
    Value Type N/A

对内存赋值

1
2
int a;
a = 15;

上述表达式会产生如下的过程

对内存取值

对内存取值的步骤为:定位内存 => 识别操作 => 获得返回值

  • 定位内存

    • 变量名定位
    • 指针间接定位
  • 识别操作

    识别操作按照如下顺序进行识别,也就是说下面的操作是有优先级的

    • 获得内存的首地址,&,返回二元组,值和类型<0xxxxxxxxx, int*>
    • 获得内存的大小,sizeof,返回二元组,值和类型<4, size_t>
    • 获得内存的表示值,除1,2 两种操作符以外,返回二元组,值和类型<10, int>
  • 返回值两元素

    • 返回值类型
    • 返回值的值

取值赋值举例

  • int a=10; int* p=&a,中第二条语句的执行过程

  • size_t c=sizeof(a),执行流程如下

  • int b=a,执行流程如下

  • *p=20,执行流程如下

  • int* q=&(*p),执行流程如下

  • size_t c=sizeof(*p),执行流程如下

  • int b=*p,执行流程如下

指针变量加减操作

指针类型p,执行p+n的含义

例如*(p+1)=30,执行过程如下

再探讨

使用指针进行定位

对指针p指向的内存空间的定位有两种方式,并且是完全等价的

  • *p*(p+n)
  • p[0]p[n]

Tips: 书上一般些int p; 我们可以写成int p,把int*写成一个整体,看成一个类型

再看int e[2]

类别
Address 0x0076AB11
Variable Type int[2]
Name p
Size 8
Value 0x0076AB11
和Address相等
Value Type int*
  • Q:Why Size=8?

    A:sizeof(int) * 2

  • Q:Why Variable_Type=int[2]?

    A:Variable Type需要能体现出变量的大小

  • Q:Why Value和Address一样?

    A:Value指向第一个元素的下标,正好与整块变量的起始位置相同

  • Q:Why Type是int*

    A:Value Type是数组中元素的指针类型,如此才可以*(p+n)或者p[n]来定位数组中的元素

  • Q:&e的返回值

    A:<0x0076AB11, int(*)[2]>,被赋值的变量需要是Variable Type的指针类型,如下int (*x)[2] = &e

  • Q:sizeof(e)的返回值

    A:<8, size_t>,返回的是变量e的Size

  • Q:e的返回值

    A:<0x0076AB11, int*>,被赋值的变量需要是Value Type的类型,如下int* x = e

  • Q: 那么e[0]到底是个啥?&、sizeof、e[0]

    A:

    根据定位之后找到的六元组

    • &:<0x0076AB11, int*>
    • sizeof:<4, size_t>
    • e[0]:<?, int>
  • Q:e=e+1e++为啥会出错?

    A:数组的Value一定是指向数组第一个元素的地址编号,对于数组e来说,V的值必须和A相等。因此,e并不是一个常量,它的值不能更改是语法限制的,对常量修改的错误应该是:assignment of read-only variable 'e',而对e的修改会出现如下错误:

    但是int *p是可以执行p++

再看int g[2][3]

int g[2][3]

类别
Address 0x0098B11D
Variable Type int[2][3]
Name g
Size 24
Value 0x0098B11D
和Address相等
Value Type int(*)[3]
  • &,<0x0098B11D, int(*)[2][3]>
  • sizeof,<24, size_t>
  • g,<0x0098B11D, int(*)[3]>

再看g[1]

  • &,<0x0098B12F, int(*)[3]>
  • sizeof,<12, size_t>
  • g[1]<0x0098B12F, int*>

字符串

1
2
3
char str1[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
char str2[6] = "hello";
char* ptr = "hello"

上述三种表达的区别?

我忘了,尴尬,在内存上没啥区别,在6元组上,第三个会和前两个有区别

数组变量作为函数参数

老师重点强调:c语言参数传递的机制是 Pass By Value

形参中:int*=int[]int(*)[3]=int[][3],数组中第一维信息的丢失是C语言所有数组类型变量内存的取值机制造成的

malloc问题

int* p = (int *)malloc(sizeof(int)*4);,发生的流程