博主水平有限,不足之处如能斧正,感激不尽!
本期内容概览:
先来分析一下这些基本的内存分布
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = {1, 2, 3, 4};
char char2[] = "abcd";
char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof (int)*4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int)*4);
free (ptr1);
free (ptr3);
}
栈 | 堆 | 数据段(静态区) | 代码段(常量区)
globalVar在哪里?
静态区,全局变量存在静态区
staticGlobalVar在哪里?
静态区,全局static变量存在静态区
staticVar在哪里?
静态区,局部staic变量存在静态区
localVar在哪里?
栈,局部变量存在栈上
num1 在哪里?
栈,局部变量存在栈上
char2在哪里?
栈,char2是局部数组,常量字符串拷贝到栈上
*char2在哪里?
栈,*char2是局部数组的第一个元素,也在栈上
pChar3在哪里
栈,pChar3是局部指针变量
*pChar3在哪里?
常量区,pChar3是指针,指向常量区的常量字符串
ptr1在哪里?
栈,ptr1是局部指针变量
*ptr1在哪里?
堆,ptr指向的空间是动态开辟在堆上的
sizeof(num1) = 40
sizeof(数组名)计算整个数组的大小
sizeof(char2) = 5
sizeof(数组名)计算整个数组的大小,""初始化的字符数组自带’\0’
strlen(char2) = 4
strlen到’\0’停下,计算有效字符个数
sizeof(pChar3) = 4/8
指针变量的大小在32位下是4bytes,64位下是8bytes
strlen(pChar3) = 4
strlen到’\0’停下,计算有效字符个数
sizeof(ptr1) = 4
指针变量的大小在32位下是4bytes,64位下是8bytes
这波夺命连环问,让我回想起那时被C语言指针折磨的恐惧…
realloc和calloc在这里不赘述,就讲讲朴实的malloc和free。
C语言中用来动态开辟内存的 函数。
int main()
{
int* pi = (int*)malloc(sizeof(int) * 4);
return 0;
}
是什么
C语言中用来释放 动态开辟的空间 的函数。
int main()
{
int* pi = (int*)malloc(sizeof(int) * 4);
free(pi);
return 0;
}
C++和C语言的重要区别:对象!那么C++中的内存管理如果有改变,跟对象脱不开关系。
C++中用来动态开辟空间的 操作符。
对内置类型的开辟,和就相当于malloc(语法形式不一样)
对自定义类型的开辟,开辟后会自动调用其默认构造函数
开辟失败抛异常(异常后面讲)
C++中用来释放动态开辟的空间的 操作符
对内置类型的释放, 相当于free(语法形式不一样)
对自定义类型的释放,释放前自动调用其析构函数
看了new和delete的特性,就知道他们是“针对对象升级的malloc和free”。
int main()
{
//开辟内置类型
int* p1 = new int;
cout << *p1 << endl;
//释放内置类型
delete p1;
//开辟内置类型并初始化
int* p2 = new int(10);
cout << *p2 << endl;
//释放内置类型
delete p2;
//开辟内置类型数组
int* p3 = new int[4];
for(int i = 0; i<4; i++)
cout << p3[i];
cout << endl;
//释放内置类型数组
delete[] p3;
//开辟内置类型数组并初始化
int* p4 = new int[4]{1, 2, 3, 4};
for(int i = 0; i<4; i++)
cout << p4[i];
cout << endl;
//释放内置类型数组
delete[] p4;
return 0;
}
:0
10
0000
1234
class Date
{
public:
Date(int y = 2022, int m = 10, int d = 1):_y(y), _m(m), _d(d)
{
cout << "Date(int y, int m, int d):_y(y), _m(m), _d(d)" << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _y = 1;
int _m = 1;
int _d = 1;
};
int main()
{
Date* pd1 = new Date;
delete pd1;
Date* pd2 = new Date[4];
delete[] pd2;
return 0;
}
:Date(int y, int m, int d):_y(y), _m(m), _d(d)
~Date()
Date(int y, int m, int d):_y(y), _m(m), _d(d)
Date(int y, int m, int d):_y(y), _m(m), _d(d)
Date(int y, int m, int d):_y(y), _m(m), _d(d)
Date(int y, int m, int d):_y(y), _m(m), _d(d)
~Date()
~Date()
~Date()
~Date()
如果混着用,
operator new的实现:
/*
operator new:通过malloc申请空间
当malloc成功,直接返回
当malloc失败,尝试执行“空间不足的应对措施”,如果这个应对措施用户自己设置了,则继续申请,否则抛异常
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem; _RAISE(nomem);
}
return (p);
}
只需要抓住这两点:
operator delete的实现:
/* operator delete: 该函数最终是通过free来释放空间的 */
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
/* free的实现 */
#define free(p)
_free_dbg(p, _NORMAL_BLOCK)
只需要抓住这一点:
C++中最好不用C的内存函数了,主要原因是自定义类型,如自定义类型容易漏掉初始化等。
对比一下malloc和new,free和delete就能知道。
malloc只开辟空间 | new开辟完空间对自定义类型会调用其构造函数
malloc是函数,用起来麻烦 | new是操作符,用起来方便
free只释放空间 | delete释放空间前,对自定义类型会调用其析构函数,而后再释放
*free(nullptr)什么都不做
定位到已开辟的空间,再次“new”,只不过这个new不真的开辟空间,而是可以对自定义类型调用构造
按这说法,好像没啥用啊,我开辟的时候直接调构造函数来实例化不就好了?
定位new表达式多用于内存池。什么是内存池?
你住在一个村里,日常用水需要走二里路去水井里打水。久而久之觉得来回跑又麻烦又累,于是接了很长的管道,从家里的院子直通水井,水泵只要看见院子里的蓄水池不满,就自动抽水。如此一来,就实现“用水自由”,再也不用那么麻烦了
频繁的开辟,就要频繁的去申请、找、分配给你。不如自己搞一个内存池,它里边随时有现成的内存可以用,不用老跑去申请。
但这有个问题,构造函数不能在创建对象后显式调用,也就是说,对于已经开辟好的空间,我们无法用它来实例对象了。
定位new就起到作用啦。现在再来回答为什么有定位new表达式
对已经开辟好的空间,无法显式调用构造函数来实例对象——只能通过定位new调用
class A
{
public:
A(int a):_aa(a)
{
cout << "A(int a):_aa(a)" << endl;
}
~A()
{
cout << "~A()" << endl;
_aa = 0;
}
private:
int _aa = 0;
};
int main()
{
A* p = (A*)malloc(sizeof(A) * 4);
//相当于拿已经开辟的空间再“new”一次
A* paa = new(p)A(10);
// paa->~A();
return 0;
}
:A(int a):_aa(a)
可以看到,并没有自动调用析构
由于疏忽或错误,未能及时释放 不再使用的内存空间
程序会逐渐崩溃,只不过是快与慢的差别
今天的分享就到这里啦,感谢浏览。
这里是培根的blog,期待与你共同进步!