【C++】语言深处的“精灵”:探索内存的奥妙
这里我们要知道,我们编写一个程序,这个程序中的变量是存储在哪个区域的
栈一般是用于静态的分配内存的,但也可以动态的分配内存,
堆是用于动态的分配内存的,不能静态的分配内存
栈:
通常是向低地址方向生长的,也就是所谓的“向下生长”
-
静态分配:栈主要用于存储局部变量、函数调用的参数和返回地址等。栈的分配和释放是由系统自动管理的,程序员通常不需要(也无法)显式地进行干预。
-
内存连续性:栈内存是连续的,遵循后进先出(LIFO)的原则。由于栈的这种特性,它不会产生内存碎片。每次函数调用时,会分配一块连续的内存区域用于存储该函数的局部变量和参数等;当函数返回时,这块内存区域会被自动释放并返回到栈中供后续使用。
-
高效性:栈内存的分配和释放非常高效,因为它们是由编译器和操作系统底层支持的。栈的大小通常在程序启动时确定,并在整个程序运行期间保持不变(尽管某些系统允许在运行时调整栈的大小)。
堆(Heap):
的生长方向通常是向上的,即向着内存地址增大的方向增长
-
动态分配:堆是用于动态内存分配的区域。程序员可以在程序运行时通过
new
(在C++中)或malloc
(在C中)等函数显式地请求分配内存块。 -
内存碎片:由于堆内存是不连续的,每次分配和释放都可能导致内存块之间的间隙(即碎片)。特别是当频繁地进行小内存块的分配和释放时,这些间隙可能会累积成大量的内存碎片,降低内存的使用效率,甚至可能导致内存分配失败。
-
管理复杂性:堆内存的分配和释放需要程序员手动管理,这增加了出错的风险,如内存泄漏、野指针等。
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";
const 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);
}
**********************************
1. 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?c____
staticGlobalVar在哪里?__c__
staticVar在哪里?__c__
localVar在哪里?__a__
num1 在哪里?__a__char2在哪里?__a__
*char2在哪里?_a__//数组是拷贝,指针才是指向
pChar3在哪里?__a__//const修饰的是指针指向的内容
*pChar3在哪里?_d___
ptr1在哪里?__a__
*ptr1在哪里?_b___
c++管理内存:
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
new是一个新的关键字,也是一个操作符
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);//10表示初始化的值
// 动态申请10个int类型的空间
int* ptr6 = new int[3];//[]可以理解为数组
delete ptr4;
delete ptr5;
delete[] ptr6;//创建的是数组,delete时就要加[]
}
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用
new[]和delete[],注意:匹配起来使用。new会调用构造函数,而malloc不会调用构造函数
delete会调用析构函数,而free不会调用析构函数
class A
{
public:A(int a=1 ): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};int main()
{A* p1 = (A*)malloc(sizeof(A));A aa1(3);A aa2(3);A* p2 = new A[10]{2,2};//A* p2 = new A[10]{A(2),A(2)};//A* p2 = new A[10]{aa1,aa2};return 0;
}
class A
{
public:A(int a=1 ): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};int main()
{A* p1 = (A*)malloc(sizeof(A));A aa1(3);A aa2(3);A* p2 = new A[10]{2,2};//A* p2 = new A[10]{A(2),A(2)};//A* p2 = new A[10]{aa1,aa2};return 0;
}
有了new后,创建链表的节点就不需要写ByNewnode等函数。
链表:
struct ListNode
{int _val;ListNode* _next;ListNode(int val)//构造函数:_val(val),_next(nullptr){}
};int main()
{ListNode* n1 = new ListNode(1);ListNode* n2 = new ListNode(2);ListNode* n3 = new ListNode(3);ListNode* n4 = new ListNode(4);n1->_next = n2;n2->_next = n3;n3->_next = n4;return 0;
}
operator new与operator delete函数(重要点进行讲解):
operator new可以理解为是对malloc的封装,operator delete是与new进行配对的,delete可以理解为是对freedbg这一函数的封装的封装
free在库里面实际上是一个宏函数
/*
operator new:该函数实际通过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: 该函数最终是通过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)
当new申请空间失败时,它会抛出异常,而malloc申请空间失败,则是返回一个null。
void fun()
{int* p1 = new int[1024*1024*100];cout << p1 << endl;int* p2 = new int[1024*1024*100];cout << p2 << endl;int* p3 = new int[1024*1024*100];cout << p3 << endl;int* p4 = new int[1024 * 1024 * 100];//申请空间失败,抛出异常cout << p4 << endl;
}
int main()
{try{fun();}catch (const exception& e)//异常抛出,跳转到这{cout << e.what() << endl;}return 0;
}
//在练习的时候,记得调成32位,此时堆的空间是4G,进程地址空间是2的32次方,如果是64位,那么进程地址空间就是2的64次方,堆的空间也会变的很大,需要申请空间很多次才会抛异常。
定位new表达式(placement-new) (了解):
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
new (placeaddress) type或者new (placeaddress) type(initializer-list) place_address必须是一个指针,initializer-list是类型的初始化列表(初始化的值)
class A
{
public:A(int a = 1):_a(a){}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{A* p1 = (A*)malloc(sizeof(A));new(p1)A(10);//不支持显示调用,p1->A(1);//析构函数支持显示调用p1->~A();return 0;
}