IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 嘉明的C学习之Day11--指针与一维数组以及malloc动态空间申请、进程地址空间的了解、栈空间与堆空间的区别 -> 正文阅读

[C++知识库]嘉明的C学习之Day11--指针与一维数组以及malloc动态空间申请、进程地址空间的了解、栈空间与堆空间的区别

指针的自增自减与指针与一维数组

指针的自增自减

接下来说一下指针的自增自减操作。
首先来看下面这段程序,你们猜下面的值使多少?2 2 3?

#include<stdio.h>
int main() {
	int a[3] = { 2,6,9 };
	int* p;
	int j;
	p = a;
	j = *p++;
	printf("a[0]=%d,j=%d,*p=%d", a[0], j, *p);
}

在这里插入图片描述
其实结果是 2 2 6,a[0]的值和j的值我们都可以知道,但是*p的值我们就有点迷惑了,到底是j=?p之后是p+1还是?p+1呢?
j = ?p++这个语句其实是j=?p之后p=p+1。
因为? 和 ++都是优先级一样的运算符,且顺序是从右到左的(所以先执行?,再执行++),因此p+1之后指向下一个数组的下一个元素?p就等于6了。

然后我们再看下面这段程序,改动就是把?p用括号括起来。

#include<stdio.h>
int main() {
	int a[3] = { 2,6,9 };
	int* p;
	int j;
	p = a;
	j = (*p)++;
	printf("a[0]=%d,j=%d,*p=%d", a[0], j, *p);
}

让么这次的输出结果是多少呢?
在这里插入图片描述
可以看到加了个括号结果却是 3 2 3,这是为什么呢?
j = ?p++这个语句其实是j=?p之后?p=?p+1。因为p存的是a[0]的地址,因此改变?p的值就是改变a[0]的值。所以?p=?p+1时,a[0]的值也需要+1,因此a[0]也变成了3
为什么这个程序跟上面的不一样呢?因为()的优先级大于++所以,运算时p都要带着?

小技巧:以上这两种情况我们可以获得一个小技巧,怎么判断p++还是?p++呢?我们只需要判断符号跟++比谁的优先级比较高。高过++的话就需要带上?(其实通常我们用得到的能比自增自减优先级高的有两种 () 和 [] ,如下表)
在这里插入图片描述
知道了上面的小技巧。接下来我们再加入一条语句作为练习,再次输出。大家猜猜结果如何?

#include<stdio.h>
int main() {
	int a[3] = { 2,6,9 };
	int* p;
	int j;
	p = a;
	j = (*p)++;
	printf("a[0]=%d,j=%d,*p=%d", a[0], j, *p);
	j=p[0]++;
	printf("a[0]=%d,j=%d,*p=%d", a[0], j, *p);
}

在这里插入图片描述
执行j=p[0]++后结果是4 3 4
我们来分析一下过程,因为上面的输出结果是3 2 3。我们添加了j=p[0]++,这个语句可以拆分为j=p[0];p[0]=p[0]+1(因为[]比++的优先级高,所以要带上p[0],而不是p++
p[0]实际上等同于?(p+0)即?p
既然它指向a[0]因此p[0]的值变a[0]的值也要变跟上面一样 在3 2 3 全部都+1 就是 4 3 4了。

指针与一维数组

其实指针与一维数组我们再讲数组的时候就有提到过,不过为了更深入的理解,我们还需要更细致的讲解。

我们看这个程序,我们定义了一个字符数组a,通过子函数change改变了a[0]的值。

#include<stdio.h>
void change(char a[]) {
	a[0] = 'H';

}
int main() {
	char c[10] = "hello";
	change(c);
	printf("%s", c);
	return 0;
}

在这里插入图片描述
这个程序相信大家都可以知道结果,但是我想深扒一下过程。所以我们看看监视和内存
在这里插入图片描述
因为在数组传递的时候我们讲过的知识点
主方法里面的参数叫做实参,方法里的参数叫做形参
其次也说过传递的本质就是传递值!!!!(这句话很重要)
因此主方法中字符数组c传给子函数change中形参只可能是传递初始地址的值,不可能把整个完整的数组传过去。 所以a存的是c的首地址的值,所以指向的第一个元素是h。

在这里还有一个值得关注的点,我们在change定义的形参不像是c一样是字符数组类型,而是指针类型
那么我们是不是可以这样,直接把形参变成char ?a,如下

#include<stdio.h>
void change(char *a) {
	*(a+0) = 'H';
}
int main() {
	char c[10] = "hello";
	change(c);
	printf("%s", c);
	return 0;

}

在这里插入图片描述
可以看到完全没问题,所以我们可以得出一个结论:
!!!!!!!!数组在作为实参传递到子函数时会弱化成为指针类型!!!!!!!!(说这一个标题的中重点就是为了讲这个)

总结完指针和一维数组,我们是不是就可以理解
为什么数组的传递传的时首地址值而不是整个字符了呢
为什么形参时指针可以接收主函数传递的数组名实参了呢

拓展知识点
sizeof(a)为什么是4?不是1
有的人可能会问char不是占一个字节吗,为什么sizeof(a)为什么是4?不是1呢?
因为指针的长度永远等于机器地址寄存器字长。 你的机器是32位机,sizeof(s)就是4字节长.你可以换个64位cpu, 装个64位的操作系统和64位编译器,然后你会一直得8.(答案来自百度)

关于指针和数组的区别
因为我的了解不是很深入,我跟很多初学者可以把数组名先理解为指针常量(因为数组名不可以自增、自减)大家可以取看看别的大佬博客学习,不要被我误人子弟了哈哈哈哈哈。

malloc动态空间申请

进程地址空间

在开始之前我们先了解几个概念
1.程序是磁盘里有序的指令集和
2.运作起来的程序叫做进程,进程运行时会生成一个虚拟空间叫做进程地址空间

什么是进程地址空间呢?如下图
转自知乎https://zhuanlan.zhihu.com/p/96516183
在这里插入图片描述
进程空间分为内核空间1G和用户空间3G。不同空间划分区域的功能作用如下
在这里插入图片描述

栈空间与堆空间的了解

很多读者在学习C语言的数组后都会觉得数组长度固定很不方便,其实C语言的数组长度固定是因为其定义的整型、浮点型、字符型变量、数组变量都在栈空间中,而栈空间的大小在编译时是确定的(因为数组一开始定义好就确定下来了,数组是放在栈空间)。如果使用的空间大小不确定,那么就要使用堆空间

栈空间和堆空是什么?有什么不同?
1.栈是计算机系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈操作、出栈操作都有专门的指令执行,这就决定了栈的效率比较高;
2.堆则是C/C++函数库提供的数据结构,它的机制很复杂,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能由于内存碎片太多),那么就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后返回。
3.显然,堆的效率要比栈低得多。
就好比如你被别人叫去组织一场烧烤,主办方把食材和场地布置的事项都告诉你,你肯定一下就搞完了。如果什么都没告诉你,在开始的时候才说要吃什么怎么布置,就会慢很多

下面我们画一张图来形象的理解。

比如我们定义了一个i变量

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

因为i的大小是确定的,所以它就会被保存到栈空间里。
在这里插入图片描述

malloc的认识与使用

而存在堆中都是不确定空间,也就是我们今天要说的动态申请空间,它有一个专门的函数叫做malloc
接下来我们看这一段程序来理解malloc

#include<stdio.h>
int main() {
	int i;
	scanf("%d", &i);
	char* p;
	//因为malloc返回值是void*类型的指针即是对应空间的首地址,所以需要用指针变量接收
	p = (char *)malloc(i);//因为void*不能进行偏移,所以我们需要强制转换。对应的是char类型,所以要char*
	strcpy(p, "malloc success");
	puts(p);
	return 0;
}

在这里插入图片描述
其实这样的操作就可以实现动态数组了

接下来我们介绍一下malloc和注意的点,在上述程序中我们在执行

void ?malloc(size_t size);


1.需要给 malloc 传递的参数是一个整型变量,因为这里的 size_t 即为 int
2.返回值为 void?类型的指针,void?类型的指针只能用来存储一个地址而不能进行偏移(注意void和void?是不一样的,void不需要返回值而void?返回的是无类型指针)。
3.因为malloc并不知道我们申请的空间用来存放什么类型的数据,所以确定要用来存储什么类型的数据后,都会将 void?强制转换为对应的类型。 就好比如你在向学校中申请一间教室作为活动场地,学校给了你间课室但是不会说这间教室就是用来唱歌的或者指定干什么干什么的,这间教室的用途是由申请人决定的。
4.malloc申请空间单位是字节,比如malloc(20)证明申请了20个字节大小的空间

然后我们再看一下此时的进程地址空间情况
在这里插入图片描述
以及内存的情况
在这里插入图片描述
点击下一步
在这里插入图片描述

malloc中的free操作

既然有可以申请动态空间,肯定有释放动态空间,总不能占着茅坑不拉屎吧。
释放动态空间是free()函数,接下来我们将介绍一下它
free 函数的格式为 void free(void *ptr)接收的是空指针类型的参数
如下程序

#include<stdio.h>
int main() {
	int i;
	scanf("%d", &i);
	char* p;
	//因为malloc返回值是void*类型的指针即是对应空间的首地址,所以需要用指针变量接收
	p = (char *)malloc(i);//因为void*不能进行偏移,所以我们需要强制转换。对应的是char类型,所以要char*
	strcpy(p, "malloc success");
	puts(p);
	free(p);//释放空间是p的地址必须和原来的一样,不可以发生偏移
	printf("free success\n");
	return 0;
}

在这里插入图片描述
接下来我们观察内存有什么变化
在这里插入图片描述
讲完了free有的人就会问
free不是很简单吗?直接free§就可以释放了
其实使用free需要保证所p的地址必须和原来的一样,不可以发生偏移
原因是申请一段堆内存空间时,内核帮我们记录的是起始地址和大小,所以释放时内核用对应的首地址进行匹配, 匹配不上时,进程就会崩溃。
比如我们在这里加上个p++

#include<stdio.h>
int main() {
	int i;
	scanf("%d", &i);
	char* p;
	//因为malloc返回值是void*类型的指针即是对应空间的首地址,所以需要用指针变量接收
	p = (char *)malloc(i);//因为void*不能进行偏移,所以我们需要强制转换。对应的是char类型,所以要char*
	strcpy(p, "malloc success");
	puts(p);
	p++;
	free(p);//释放空间是p的地址必须和原来的一样,不可以发生偏移
	printf("free success\n");
	return 0;
}

就会出现这个结果,因为p的地址值已经变了。系统想释放找不到它在哪里,就会报错。
在这里插入图片描述
就好比如一个图书馆里面有一张借阅记录表,借书的时候需要填对应的个人信息p 20。如果你换个人是p+1 20系统就识别不了,就会还书失败。
在这里插入图片描述
又有人会问
free 函数不是接收的是void?类型的参数吗??p不是char?类型的吗??怎么不用把p强制转换为void?呢??
其传入的参数为void类型指针,任何指针均可自动转为void?类型指针,所以我们把p传递给free函数时,不需要强制类型转换。

规范使用malloc

其实上述的释放空间还是不够严谨,因为我们提到过虽然free掉了p,但是p的地址还是指向那块地址,而这块地址已经不属于它的了,所以如果后续进行操作就会出错。
因此我们需要加上p = NULL;初始化它p存的地址
如下程序,这也整体下来我们对malloc的操作才算规范

#include<stdio.h>
int main() {
	int i;
	scanf("%d", &i);
	char* p;
	//因为malloc返回值是void*类型的指针即是对应空间的首地址,所以需要用指针变量接收
	p = (char *)malloc(i);//因为void*不能进行偏移,所以我们需要强制转换。对应的是char类型,所以要char*
	strcpy(p, "malloc success");
	puts(p);
	free(p);//释放空间是p的地址必须和原来的一样,不可以发生偏移
	p = NULL;	
	printf("free success\n");
	return 0;
}

栈空间与堆空间的差异

我们定义一个写入栈空间的方法,其中定义了个数组
以及一个写入堆空间的方法,定义了一个动态空间(动态数组)
并为它们写入各自的内容,最后返回地址值给主方法的字符指针a

#include<stdio.h>
char* printf_stack() {
	char c[20];
	strcpy(c, "printf_stack");
	puts(c);
	return c;
}

char* printf_malloc() {
	char* p;
	p = (char*)malloc(20);
	strcpy(p, "printf_malloc");
	puts(p);
	return p;
}
int main() {
	char* a;
	a = printf_stack();//栈空间会随函数执行完毕时释放(自动释放)
	puts(a);
	a = printf_malloc();//堆空间不会随函数执行完毕释放,需要自己手动free掉
	puts(a);
	free(a);
	a = NULL;
	return 0;
}

执行结果如下
在这里插入图片描述
可以看到调用栈空间时,puts(a)出现了错误。而调用堆空间时却没有错误。这就是我们这一个程序要讲的东西。
栈空间会随函数执行完毕时释放(自动释放)
堆空间不会随函数执行完毕释放,需要自己手动free掉

接下来我们来证明这一点
在这里插入图片描述
可以看到a是跟c的值一样的,但是就是提取不到内容的东西,即证明这个数组的空间已经被释放掉了。
因为我们之前用改变值的例子说过
文章连接https://blog.csdn.net/Zcymatics/article/details/123079868
简单的传值是不会改变主函数的值的,因为主函数和其他函数在栈中都有属于自己的空间。
在这里插入图片描述
**当printf_stack()执行完毕,系统就会主动释放空间。**所以即视你a指向那块地址都是无济于事

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-28 15:10:05  更:2022-02-28 15:12:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:31:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码