操作系统将我们的内存空间分成了很多的区段,每个区段都存放不同的数据,这是为了存数据和取数据方便,而划分的,
从下到上依次是,受保护的头,代码段,未初始化全局变量,已初始化全局变量,以及堆,文件映射区,栈,内核操作系统。
动态申请空间的方式:malloc,calloc,realloc
相同点:都是从堆上开辟空间,返回值类型相同都是void*
申请空间失败,返回值都是NULL申请空概念都需要手动释放
在接受返回值的时候都需要类型强制转换
不同点:
malloc:参数字节数,不对申请的空间进行初始化,在接受返回值的时候都需要类型强制转换,申请空间失败返回空,所以使用前要判空
calloc:
1.参数列表不同--(元素个数,单个元素所占的大小)
2.函数功能差异:calloc会将申请的空间初始化为0
realloc:
1.参数列表(void* ptr,size_t size),假如ptr==0那么就是mallo函数
2.函数的功能,将ptr指向的空间调整为size个字节
三者详细区别
memset:
void * memset ( void * ptr, int value, size_t num );
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "almost every programmer should know memset!";
memset(str, '-', 6);
puts(str);
return 0;
}
void * memcpy ( void * destination, const void * source, size_t num );
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "Sample string";
char str2[40];
char str3[40];
memcpy(str2, str1, strlen(str1)+ 1);//不会自动拷贝最后的\0需要加1
//strcpy会自动拷贝最后的\0
memcpy(str3, "copy successful", 16);
printf("str1: %s\nstr2: %s\nstr3: %s\n", str1, str2, str3);
return 0;
}
动态释放方式free();
系统必须要对动态申请的内存空间进行管理,这样的话在释放的时候,就知道该去释放多少空间了,假如用户申请了40
个字节int* p = (int*)malloc(sizeof(int)*10);但是因为系统要对这段空间进行管理,所以就会为这段空间
添加上32字节的头部信息(结构体),以及4个字节的尾部,里面填充的是FD,FD,FD,FD,为的就是防止越界,头部的
32个字节中重要的几个信息是prev,next,size(记录用户实际申请的空间有多大),系统使用的是双向链表来将这段
空间管理起来的。
默认情况下new出来的空间在堆上,那意思就是也有可能不在堆上
new出来的空间不需要判空
数组的类型就是把 变量名拿掉之后的东西,就是数组的类型,int a[10],的类型就是int [10]
int * p = new int[10]{1,2,3,4,5,6,7,8,9,10};这样初始化申请的空间
假如你申请了一段空间,int* p = new int[10];此时没有初始化,这个时候数组中的元素就是随机值
然而int* p = new int[10]{1},除了第一个空间是1,之外其余的空间都是0,
C语言中使用malloc还要包含头文件stdlib.h在C++不用包含任何头文件,所以在C++中是操作符,
new方法申请空间不需要强转的原因就是,因为new后面跟的就是这段空间的类型,因此不需要手动强转
new,new[]可以初始化
new的空间一般不需要判空
对于new/delete,new[]/delete[],malloc/free一定要匹配使用,
在我们申请内置类型的空间的话,就算不匹配使用的话编译和运行可以通过,然后使用内存泄露检测函数(_CtrDumpMemoryLeaks())进行检测发现,貌似也不存在内存泄露。
接着我们试了自定义类型的变量,发现编译可以通过,但是对于自定义类型的类型,运行起来代码就直接崩溃了
new会调用构造函数,free不会调用析构函数
malloc申请自定义类型的空间的时候,只能够将申请的空间看作和类对象大小相等的一块空间,但是不能够被看作是
一个对象,因为没有调用构造函数,里面都是随机值
operator new,和operator delete 函数都是可以重载的,但是一般都没有必要
因为new的底层也是通过malloc来实现的,也就是说不可避免地会造成额外空间的浪费,比如每次申请空间,要多申请
36个字节,并且在链表中我们会频繁的,构造结点,这样就需要我们不断地去向内存空间索要一些小的内存块,很容易造成
内存的碎片化。优化的办法就是,我们可以采用内存池(一段大的空间,两个指针,一个指向头,一个指向尾)的方式,进
行优化,就是重载operator new(),每次用户需要申请空间的时候就让指向头的空间向后移动,然后将切割下来的空间交
给用户使用,这样既可以避免额外空间的浪费又可以避免内存上的碎片化,并且申请空间的效率还比较高
现在有这样一个问题
T* pt = (T*)malloc(sizeof(T));
需求就是让pt指向的空间被当作对象,但是我们之前已经说过了,malloc()出来的空间,只是申请了一段空间,并没
有对对象进行构造,那么怎样可以在pt指向的空间上执行构造函数呢?需要将pt只想的空间当成对象,此时就有了一个定
位new表达式(placement-new):在已经存在的空间上进行构造函数.
语法: new(地址) + T(参数)括号里面的参数是看,你所要调用的构造函数是无参还是有参的
举例:
date* pt = (date*)malloc(sizeof(date));
new(pt) date();//定位new表达式
在内存池中一般是new和定位new表达式一起来使用构造对象的
malloc+定位new就是new操作符的作用
析构函数+free()就是delete操作符的作用
只要在代码中出现new,在底层调用的都是operator new,申请空间的new操作符和定位new底层调用的函数是相同的,但是内部的是想是不一样的,为的就是统一,只要new就调用operator new()
new操作符申请空间
1.调用 void* operator new(size_t size,void* where)
{
调用malloc() + 空间不足的话的一些应对措施(比如将某些不用的空间释放)
}
2.调用构造函数
重载operator new();void operator new(size_t size,void* where),
一般不重载重复定制某些功能
定位new表达式
1.void* operator new(size_t size,void* where)
{
return where//为的就是统一于new操作符,才有这步操作
}
2.调用构造函数
关于new申请内存的时候会不会循环申请,要看用户有没有提供应对空间不足的措施,虽然ne申请空间不用判空,但是会
抛异常,需要用户手动去捕获异常
malloc申请空间只能在堆上,但是new不一定,因为可以重载new操作符,重载之后,想在哪里申请就在
哪里申请,磁盘上都可以
#include<iostream>
#include<string>
using namespace std;
class test
{
public:
~test()
{
delete this; //无限递归,死循环
//调用析构,只要delete就调用析构,递归到一定程度就会栈溢出
}
};
//不是说不能通过delete释放空间,只是不能释放本类的空间,否则就是无限递归
int main()
{
test* p = new test;
delete p; //调用析构,只要delete就调用析构
return 0;
}
怎样只能在堆上创建对象:
class date
{
public:
static date* get() //对外提供接口,但是要想调用普通函数的话就得先有对象,所以把函数给成静态的
{
return new date;
}
//但是又有一个问题就是,创建对象,除了构造,还有拷贝构造
//C++11给的方法是
date(const date& p) = delete; //这种办法就避免了利用拷贝构造函数在堆上面创建对象
//也就是说C++11把delete的作用扩大了,除了释放空间之外,还可以删除默认的拷贝构造函数,当然也可以删除其他的构造函数
private:
date()
{}
};