[C指针进阶]二级指针做输入的3种内存模型

对于指针,我们只需要从两个角度进行分析即可。

第一角度,站在C/C++编译器的角度看待指针,对形参,如指针类型,c编译器只会把他当做一个指针变量来看。(分配四个字节的内存)

第二角度,我们只有在是用指针所指向的内存空间的时候我们才去关心内存是一维的还是二维的。

今天分析的二维指针三种内存模型都是指针做输入的情况,在主调函数中分配内存。

在分析指针之前,先回顾一下内存四区的知识。首先我们知道内存分为5大区,我们暂把BSS段与数据段合称为全局区或者常量区,构成内存四区。以下是内存5大区具体概况。

内存5大区

  • BSS段( bss segment )

    通常是指用来存放程序中未初始化的全局变量和静态变量 (这里注意一个问题:一般的书上都会说全局变量和静态变量是会自动初始化的,那么哪来的未初始化的变量呢?变量的初始化可以分为显示初始化和隐式初始化,全局变量和静态变量如果程序员自己不初始化的话的确也会被初始化,那就是不管什么类型都初始化为0,这种没有显示初始化的就 是我们这里所说的未初始化。既然都是0那么就没必要把每个0都存储起来,从而节省磁盘空间,这是BSS的主要作用)的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。 BSS节不包含任何数据,只是简单的维护开始和结束的地址,即总大小。以便内存区能在运行时分配并被有效地清零。BSS节在应用程序的二进制映象文件中并不存在,即不占用 磁盘空间 而只在运行的时候占用内存空间 ,所以如果全局变量和静态变量未初始化那么其可执行文件要小很多。


  • 数据段(data segment)

    通常是指用来存放程序中已经初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配,可以分为只读数据段和读写数据段。字符串常量等,但一般都是放在只读数据段中。


  • 代码段(code segment/text segment)

    通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等,但一般都是放在只读数据段中 。


  • 堆(heap)

    堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或 缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张); 当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)


  • 栈 (stack)

    栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}” 中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变 量)。除此以外, 在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值 也会被存放回栈中。由于栈的先进先出特点,所以 栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

内存分区图

二阶指针做输入的3种内存模型

接下来我们来讨论二阶指针做输入的3种内存模型。

第1种

首先第一种就是指针数组,以字符型指针数组为例定义方式为:

1
char*p1[10];

指针数组的实质是表示存放指针的数组。

第2种

第二种为二维数组,以字符型二维数组为例定义方式为:

1
char p2[3][4];

第3种

第三种为二维指针,以字符型二维指针为例定义方式为:

1
char **p3;

二维指针是一个存放指针的指针,因此使用前需先开辟一段空间。

区别

指针数组与二维指针的区别:

由于二维指针储存的是指针,因此二维指针首先需要开辟空间,然后再在子元素上继续开辟空间,因此需要两步完成。

而指针数组由于本质上是数组,因此省略了第1步,只需在子元素上继续开辟空间。因此只需要1步完成。

以动态分配内存为例:

第1步

1
ptr =malloc(3 *sizeof(char *));//等价于 *ptr [3];

第2步

1
2
3
ptr =malloc(3 *sizeof (char *));等价于 *ptr [3];
for(i =0;i<3;i++)
ptr[i] =malloc(sizeof(char)*10);//表示可以存放长度为10 的字符串。

指针数组只需第2步,二维指针则需要1,2步。

而除此之外,基本再没有什么区别。他们的步长都是4,即一个指针的内存大小。

指针数组与数组指针的区别:

首先,我们知道,二维数组ptr[2].[2],ptr的实质是一个数组指针。我们知道,对于一维指针数组a[2],a本质是指针,只不过a是一个常量指针,即a的值不能被修改。那么对于二维数组ptr[2].[2],ptr是一个常量数组指针,即ptr的值不可修改。

那么,他们本质的区别是什么呢。首先,对于char p[3],他的步长是4。即一个指针的内存大小。 (p+1)=p[1],(int)(p+1)-(int)p=4。对于char( p )[3],他的步长是3,即一个数组内存大小。 (p+1) = p[1].[3],(int)(p+1)-(int)p=3。所以这就是为什么指针数组和数组指针不能相互赋值的原因。

内存模型示意图

代码

以下是针对二维指针的3种内存模型进行排序的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
/*排序并输出第1种内存模型:指针数组*/
void sort_myArray1(char **myArray, int len) {
printf("排序前:\n");
for (int i = 0; i < len; i++)
printf("%s\n", myArray[i]);
printf("******************\n");
for (int i = 0; i < len; i++) /*改变指针的指向*/
for (int j = len - 1; j > i; j--) {
if (strcmp(myArray[j], myArray[j - 1])>0) {
char *tmp = myArray[j];
myArray[j] = myArray[j - 1];
myArray[j - 1] = tmp;
}
}
printf("排序后:\n");
for (int i = 0; i < len; i++)
printf("%s\n", myArray[i]);
printf("\n");
}
/*排序并输出第2种内存模型:二维数组(数组指针)*/
void sort_myArray2(char(*myArray)[4], int len) {
printf("排序前:\n");
for (int i = 0; i < len; i++)
printf("%s\n", myArray[i]);
printf("******************\n");
for (int i = 0; i < len; i++) /*改变指针所指向内存的值*/
for (int j = len - 1; j > i; j--) {
if (strcmp(myArray[j], myArray[j - 1])>0) {
char tmp[4];
strcpy(tmp, myArray[j]);
strcpy(myArray[j], myArray[j - 1]);
strcpy(myArray[j - 1], tmp);
}
}
printf("排序后:\n");
for (int i = 0; i < len; i++)
printf("%s\n", myArray[i]);
printf("\n");
}
/*排序并输出第3种内存模型:二维指针*/
void sort_myArray3(char **myArray, int len) {
printf("排序前:\n");
for (int i = 0; i < len; i++)
printf("%s\n", myArray[i]);
printf("******************\n");
for (int i = 0; i < len; i++) /*改变指针所指向内存的值,也可以改写成改变指针的指向*/
for (int j = len - 1; j > i; j--) {
if (strcmp(myArray[j], myArray[j - 1])>0) {
char tmp[4];
strcpy(tmp, myArray[j]);
strcpy(myArray[j], myArray[j - 1]);
strcpy(myArray[j - 1], tmp);
}
}
printf("排序后:\n");
for (int i = 0; i < len; i++)
printf("%s\n", myArray[i]);
printf("\n");
}
/*为二级指针分配动态内存并赋值*/
char**get_myArray3(int len) {
char**myArray = NULL;
while ((myArray = (char**)malloc(3 * sizeof(char*))) == NULL) {
printf("Error.\n");
exit(1);
}
for (int i = 0; i < 3; i++) {
while ((myArray[i] = (char*)calloc(100, sizeof(char))) == NULL) {
printf("Error.\n");
exit(1);
}
sprintf(myArray[i], "%d%d%d", i + 1, i + 2, i + 3);
}
return myArray;
}
/*将动态分配的内存空间释放掉*/
void init_myArray3(char**myArray, int len) {
for (int i = 0; i < len; i++)
if (myArray[i] != NULL) {
free(myArray[i]);
myArray[i] = NULL;
}
}
int main() {
char*p1[] = { "123","456","789" }; /*第1种内存模型:指针数组*/
char p2[3][4] = { "abc","def","ghi" }; /*第2种内存模型:多维数组*/
char**p3 = get_myArray3(3); /*第3种内存模型:二维指针*/
sort_myArray1(p1, sizeof(p1) / sizeof(p1[0])); /*数组的指针个数*/
sort_myArray2(p2, sizeof(p2) / sizeof(p2[0])); /*数组的行数*/
sort_myArray3(p3, 3); /*由于sizeof(p3)为4,即一个指针的大小,故不能用sizeof(p3) / sizeof(p2[3])*/
init_myArray3(p3, 3);
if (p3 != NULL)
free(p3); /*释放掉二维指针分配的内存*/
p3 = NULL;
return 0;
}

文章结束了,但我们的故事还在继续
坚持原创技术分享,您的支持将鼓励我继续创作!