c/c++的内存管理

一、程序内存区域划分

这张图大概的把程序的地址空间分出来了,下面是低位,上面是高位,

程序地址空间,又叫虚拟地址空间,其实学了Linux之后会知道,这真的就是一个虚拟的,因为每个进程都会开辟一个这样的,而不是一台电脑仅有一个;

程序地址空间,分用户空间和内核空间,内核空间暂时不讲,是Linux下的内容,大概就是一块由操作系统管理的空间,而且用户除了通过其提供的系统调用接口访问内核空间之外,其他没有办法访问;

用户空间又可以分为:栈,内存映射段,堆,数据段,代码段。。。;

栈中主要存储一些函数的栈帧,和在函数中开辟的临时变量还有一些寄存器数据;

内存映射段需要联系到pcb中的mm_struct,暂时不说;

堆中主要是给可变长数组等不知道容量的容器开辟空间的,它的空间较大,但是速度相比起栈来说会慢很多;

数据段主要存储全局数据和static数据;

代码段可以执行代码,还有存储只读常量(常量字符串等等);

关于栈和堆其实还有一些点:

比如:如何验证栈是向下增长的?

很简单,比如由明显先后顺序的定义三个变量,看看地址是如何变化的;

这里首先定义a,然后再b,然后再c;而且先后顺序明显,定义a,调用函数之后,定义b,再调用函数,定义c;

#define  _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
void stack2();
void stack1()
{
	int b = 5;
	cout << &b << endl;
	stack2();
}
void stack2()
{
	int c = 1;
	cout << &c << endl;
}
int main()
{
	int a = 10;
	cout << &a << endl;
	stack1();
	return 0;
}

 可以看见a,b,c的地址是依次打印的,但是a的地址反而最大,所以验证出了栈是向下增长的;

2、同理,堆是向上增长的;

二、c语言中的内存管理

malloc/realloc/calloc

这三种方法有什么相同点?又有什么不同点?

相同点:

1、头文件都是stdlib.h

2、返回值都是void*,必须强转类型

3、都是标准库提供的从堆中申请空间的方法

4、申请成功之后返回申请空间的首地址,申请失败返回NULL

5、都需要和free连用,不然内存泄漏

6、不可以越界访问

不同点:

1、malloc:

void* malloc(size_t size);//size是字节数

void* calloc(size_t num,size_t size);//num是元素个数,size是每个元素的字节数

而且全部初始化为0

void* realloc(void* p,size_t size);

将p所指向空间,扩展到size个字节大小,如果p为NULL,作用相当于malloc

p如果不是NULL,如果参数size 小于等于 p原来指向的空间大小,则还是返回原地址

                             如果参数size大于p原来所指向空间的大小,而且要扩大的空间在原先p指向空间首地址之后够用,那还是返回原地址;

如果要扩大的空概念在原先p指向空间的首地址不够用,那就重新申请一段够空间的首地址返回;而且会将原空间中的元素拷贝到新空间中,并将旧空间释放;

这里还有一个问题:

realloc是如何判断出p指向空间的大小的?好像该方法参数中并没有给出啊?

比如我 int* ptr = (int*)malloc(sizeof(int)*10);

现在要扩容到100个int类型元素,那realloc怎么知道我这个ptr指向的空间够不够100个元素呢?

那我们就要想一下,是不是malloc了之后,真的只申请sizeof(t) * num个字节空间了;

在vs环境下:

其实申请到的空间比我们想要申请的空间要大;

在vs环境下,真正申请到的空间比我们想申请的会打36个字节;其中就包含了表示堆中空间,谁被申请了,谁没被申请,以及各种维护双向链表的信息,在之后还多开辟了四个字节的空间,防止数组越界;

 

三、c++中内存管理方式

1、new、delete操作

比如要申请一个int类型变量的空间

#define  _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

int main()
{
	int* T = new int(1);
	delete(T);
	return 0;
}

 在()中可给出初始化的值,如果不给括号,则会放入随机值;

如果要申请一段连续的int类型变量空间

#define  _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

int main()
{
	int* T = new int[10];
	delete[] T;
	return 0;
}

虽然这些操作很简单,但是一定要注意对应关系

如果错误操作很有可能会内存泄漏。。。

new  -> delete;

new T[]  -> delete[]

那么这两个函数的好处相比较于malloc那个系列到底好在哪里?

首先,malloc仅仅是申请空间,并不会在申请的空间中调用构造方法;(所以说malloc系列仅在c语言阶段,因为c语言根本没有构造方法),free也只是将空间释放掉,不会清理空间中的资源,

这两个是c语言库中的内容,根本没有写入什么构造方法析构方法;

但是new在申请空间之后会调用构造方法,delete在释放空间之后,会调用析构方法,清理空间中资源;

#define  _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Date
{
public:
	Date()
	{
		cout <<"Date(int, int, int) "<< endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	Date* T = new Date[5];
	delete[] T;
	return 0;
}

比如这个简单的日期类,我在构造和析构函数中什么都没干,只让他打印一下,代表调用了一次;

而结果却是:

这样的结果是malloc系列达不到的;

那么new是如何开辟空间的呢?

其底层其实调用了一个operator new的方法;void* operator new(size_t size);

大家可以注意到,其实我并没有给出空间的具体字节大小,而是给出了要申请多少个对象,所以这个size是编译器自动计算的。这也算是优点之一了~~

这个方法本质也是malloc,malloc申请成功之后返回首地址,如果失败那就会执行空间不足时的应对措施,如果不提供应对措施,那就抛出bad_alloc异常,如果提供措施,那么malloc会一直尝试申请,直到申请成功。

第二步,如果申请的空间是自定义类的,那么会调用构造方法;

delete又是如何实现释放空间的呢?

第一步、如果空间中存放的是自定义类型,那就调用析构函数先清理资源

第二步、调用operator delete方法,

底层方法是free,因为new的底层是malloc,这两个应该同时存在;

这里再提一句,如果是自定义类型,new和delete的对应关系如果不严格遵守,那其实也没太大问题,因为不会调用所谓构造方法和析构函数,都是内置类型,只会调用operator new和operator delete,底层是malloc和free,不会出现问题;

但是对于自定义类型,绝对不可以!!!

这里给大家介绍一个函数,叫

_CrtDumpMemoryLeaks();

它是用来检测系统有没有资源泄露的,

#define  _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Date
{
public:
	Date()
	{
		cout <<"Date(int, int, int) "<< endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	Date* T = new Date[5];
	delete T;
	_CrtDumpMemoryLeaks();
	return 0;
}

如果这里,我将new[]和delete连用,就不是内存泄漏不泄露的问题了,直接程序异常崩溃了。。。

那如果我把new和delete[]连用,

#define  _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Date
{
public:
	Date()
	{
		cout <<"Date(int, int, int) "<< endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	Date* T = new Date;
	delete[] T;
	_CrtDumpMemoryLeaks();
	return 0;
}

 

 这里我只截图了一部分,它会无限调用析构函数,对一块空间来回释放,那程序一定是有问题的;只是我这里还没加入具体操作而已,所以程序也是一定会异常退出的。

关于不连用的问题,还可以再补充一点:

new[]和delete连用的时候:

new[k]的时候,是开辟一个动态数组,那怎么知道它是一个数组而不是一个元素?所以会在k个元素的基础上,多开四个字节空间(在最前面),存储数组长度k,这样delete[]才知道对象数组的大小,会调用k次析构函数(自定义类),并且释放 (k个自定义类类型对象大小 + 4)个字节的空间,但是没有调用delete[],而是用delete,被认为是只有一个元素,只调用了一次析构函数而且没有把那四个字节释放掉,所以程序会崩溃;

如果new和delete[]连用:

new的时候没有多开辟四个字节空间,所以delete[]不知道要调用多少次析构函数,那就会一直析构。。。直到崩溃;

虽然说那个_CrtDumpMemoryLeaks()说是可以检查内存泄漏,但是我除了malloc开了空间不释放之外,其他情况好像都检测不出来,,,包括new和delete不配套操作(lll¬ω¬)

而且大多数内存泄漏检测工具都十分昂贵而且不一定准确,还是大家养好良好习惯比较好。

希望对大家能够有所帮助,有说的不对的地方欢迎指正!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在C/C++编程语言中,内存是一个非常重要的概念。内存是计算机用于存储和访问数据的地方,它可以被看作是一个巨大的存储器数组,每个元素都有一个独特的地址。 在C/C++中,我们可以使用指针来访问和操作内存。指针是一个特殊类型的变量,它存储了一个内存地址。通过指针,我们可以间接访问和修改内存中的数据。 当我们在程序中声明一个变量时,系统会为该变量分配一块内存空间,并将其地址存储在变量名中。我们可以通过使用变量名来访问和修改该内存空间中的值。 另外,我们可以使用动态内存分配函数来在运行时动态地分配内存。这在需要在程序中创建变量长度的数组或者临时存储空间时非常有用。动态内存分配函数包括malloc、calloc和realloc。在使用这些函数分配内存后,我们需要记得通过使用free函数来释放这些内存空间。 值得注意的是,C/C++中的内存管理是程序员的责任。这意味着我们在使用指针和动态内存分配函数时需要小心,以避免内存泄漏和悬挂指针等问题。我们需要确保我们在使用完内存后及时释放它,以避免浪费内存资源。 总结来说,C/C++中的内存是一个重要的概念,我们可以使用指针来访问和操作内存。通过动态内存分配函数,我们可以在程序运行时动态地分配内存。然而,我们也需要负责管理内存,以避免出现内存泄漏和悬挂指针等问题。 ### 回答2: C/C++中的内存填空题是指填写一段代码,完成特定的内存操作。以下是一个例子: ```c #include <stdio.h> int main() { int array[5]; // 声明一个包含5个整数的数组 int *p = array; // 声明一个指向数组首元素的指针 // 使用循环将数组中的元素赋值为0到4 for (int i = 0; i < 5; i++) { *(p + i) = i; } // 打印数组中的元素 for (int i = 0; i < 5; i++) { printf("%d ", array[i]); } return 0; } ``` 在这个例子中,我们声明了一个包含5个整数的数组`array`,然后使用指针`p`指向数组的首元素。接下来,通过循环遍历数组,利用指针`p`对数组元素进行赋值操作,赋值的值为数组下标。最后,再通过循环遍历数组,利用数组`array`打印出各个元素的值。这段代码展示了C/C++中的指针和数组的使用,以及对内存空间的操作。 ### 回答3: C/C++ 内存填空题一般涉及指针和内存管理的知识。下面给出一个例子以300字来回答: 以下是一道关于C/C++ 内存填空题的解答。 ```c #include <stdio.h> #include <stdlib.h> int main() { int* ptr = (int*)malloc(sizeof(int)); int* arr = (int*)calloc(5, sizeof(int)); *ptr = 10; for (int i = 0; i < 5; i++) { arr[i] = i; } printf("Ptr: %d\n", *ptr); printf("Arr: "); for (int i = 0; i < 5; i++) { printf("%d ", arr[i]); } printf("\n"); free(ptr); free(arr); return 0; } ``` 上述代码中包含了两个关于内存的填空处,首先是通过`malloc(sizeof(int))`来分配存储 int 类型数据的内存空间,并将其地址赋值给`ptr`指针;另一个是通过`calloc(5, sizeof(int))`来分配存储 5 个 int 类型数据的连续内存空间,并将其地址赋值给`arr`指针。 接着通过`*ptr = 10`给指针 `ptr` 所指向的内存位置赋值为 10。并用一个 for 循环给数组 `arr` 赋值为 0 到 4。 最后通过`printf`打印结果。Ptr 输出为 10, Arr 输出为 0 1 2 3 4,表示内存填空处正确。 最后需要调用`free`函数手动释放内存,以避免内存泄漏。 在实际编程中,动态内存分配是一个常见的操作,合理地申请内存并及时释放内存对于提高程序的性能和效率十分重要。因此对于这类题目要熟悉`malloc`、`calloc`、`realloc`、`free`等函数的使用规则和注意事项,以及指针的正确使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值