C++内存分布
C++内存分布通常包含以下几个部分:
-
栈(Stack):用于存储局部变量以及函数参数。当声明一个局部变量时,它会被存放在栈中。栈是自上而下的数据结构,进入作用域时分配内存,离开作用域时释放内存。栈的分配和回收速度非常快,但是大小有限。
-
堆(Heap):用于动态内存分配,由程序员分配释放。如果需要在函数调用结束后仍保留数据或者需要动态分配内存大小,就可以使用堆。使用堆空间可以创建大小动态变化的数据结构,如链表和树。但是,管理堆内存(使用
new
和delete
)需要谨慎,以避免内存泄漏和碎片化。 -
全局/静态存储区:存储全局变量、静态变量和常量(静态主导)。这部分内存在程序启动时分配,在程序结束时释放。全局变量和静态变量的生命周期贯穿整个程序运行期间,而局部静态变量的生命周期虽然也是整个程序,但其作用域局限于声明它的函数内。
-
代码区:存放程序的二进制代码,即编译后的机器指令。这部分内存是只读的,用来存放执行的代码。
-
常量区:存储常量字符串和其他常量数据。这部分数据通常也是只读的,位于程序的数据段中。
/*内存分布*/
using namespace std;
#include <iostream>
int globalvar = 1;//---全局/静态存储区
static int staticGlobalVar = 1;//---全局/静态存储区
void Test() {
static int staticVar = 1;//---全局/静态存储区
int localVar = 1;//---栈(Stack)
int num1[10] = {1, 2, 3, 4};//---栈(Stack)
char char2[] = "abcd";//---栈(Stack)
const char* pchar3 = "abcd";//栈(Stack)---常量区
int* ptr1 = (int*)malloc(sizeof(int) * 4);//栈(Stack)---堆(Heap)
int* ptr2 = (int*)calloc(4, sizeof(int));//栈(Stack)---堆(Heap)
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);//栈(Stack)---堆(Heap)
free(ptr1);//---栈(Stack)
free(ptr3);//---栈(Stack)
}
sizeof和strlen的区别
sizeof
运算符
-
sizeof
是一个编译时运算符,用于计算其操作数的大小(以字节为单位)。这个大小是在编译时确定的,不是在运行时。 -
对于数据类型(如int、float等),
sizeof
返回该类型所占的字节数。 -
对于数组,
sizeof
返回整个数组占用的内存大小,包括所有元素。 -
对于指针,
sizeof
返回指针本身的大小,而不是指针指向的内存大小。 -
sizeof
可以用于任何数据类型,包括基本类型、数组、指针、结构体等。
strlen
函数
-
strlen
是一个标准库函数,用于计算C风格字符串(即以null
终止的字符数组)的长度,不包括终止的null
字符。 -
strlen
的参数是一个指向字符数组(字符串)的指针。它通过遍历字符串直到遇到null
终止符来计算字符串的实际长度。 -
strlen
的返回值是一个size_t
类型,表示字符串的长度(不包括null
终止符)。 -
strlen
在运行时计算字符串的长度,因此其返回值依赖于字符串的实际内容。
/*sizeof与strlen区别*/
#include<iostream>
#include<string.h>
using namespace std;
int main(){
char str[] = "Hello";
char* pstr = "Hello";
cout<<"sizeof(str):"<<sizeof(str)<<endl;
cout<<"sizeof(pstr):"<<sizeof(pstr)<<endl;
cout<<"strlen(str):"<<strlen(str)<<endl;
cout<<"strlen(pstr):"<<strlen(pstr)<<endl;
}
使用sizeof
:
sizeof(str)
将返回6,因为str
数组包含5个字符加上一个null
终止符,每个字符占用1字节。
sizeof(pstr)
将返回指针的大小,这个大小依赖于平台(例如,在64位系统上通常是8字节)。
使用strlen
:
strlen(str)
将返回5,因为str
字符串的长度是5(不包括null
终止符)。
如果pstr
指向一个以null
终止的字符串,strlen(pstr)
也会返回该字符串的长度(不包括null
终止符)。
C语言中动态内存管理方式
在C语言中,动态内存管理是通过几个标准库函数实现的,主要包括malloc
、calloc
、realloc
和free
。这些函数提供了在堆(heap)上动态分配、重新分配和释放内存的能力,使得程序可以根据需要在运行时管理内存。
malloc
函数
原型:void* malloc(size_t size);
用途:分配size
字节的未初始化内存。如果分配成功,返回指向分配内存的指针;如果分配失败,返回NULL
。
特点:分配的内存区域不会被自动初始化,其内容是未定义的。
/*malloc*/
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int*)malloc(sizeof(int) * 4); // 分配4个整数的空间
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 4; i++) {
ptr[i] = i;
}
for (int i = 0; i < 4; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
free(ptr); // 释放内存
return 0;
}
calloc
函数
原型:void* calloc(size_t num, size_t size);
用途:分配足够空间以容纳num
个大小为size
的对象,并将分配的内存初始化为零。如果分配成功,返回指向分配内存的指针;如果分配失败,返回NULL
。
特点:与malloc
不同,calloc
分配的内存区域会被自动初始化为零。
/*calloc*/
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int*)calloc(4, sizeof(int)); // 分配并初始化4个整数的空间
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 4; i++) {
printf("%d ", ptr[i]); // 默认初始化为0
}
printf("\n");
free(ptr); // 释放内存
return 0;
}
realloc
函数
原型:void* realloc(void* ptr, size_t size);
用途:更改之前调用malloc
或calloc
分配的内存块的大小。ptr
是指向旧内存块的指针,size
是新内存块的大小。如果分配成功,返回指向重新分配内存的指针;如果分配失败,返回NULL
,且原指针ptr
仍然有效。
特点:如果新大小大于原大小,新增的内存不会被初始化;如果新大小小于原大小,超出的部分会被丢弃。
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int*)malloc(sizeof(int) * 2); // 最初分配2个整数的空间
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 2; i++) {
ptr[i] = i + 1;
}
ptr = (int*)realloc(ptr, sizeof(int) * 4); // 扩展到4个整数的空间
if (ptr == NULL) {
printf("Memory reallocation failed\n");
return 1;
}
for (int i = 2; i < 4; i++) {
ptr[i] = i + 1;
}
for (int i = 0; i < 4; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
free(ptr); // 释放内存
return 0;
}
free
函数
原型:void free(void* ptr);
用途:释放之前通过malloc
、calloc
或realloc
分配的内存。ptr
是指向要释放的内存块的指针。
特点:一旦内存被释放,该指针ptr
就不再是一个有效的内存引用。继续使用这样的指针是未定义行为,通常称为悬挂指针。
C++中动态内存管理方式
在C++中,动态内存管理除了可以使用C语言风格的malloc
、calloc
、realloc
和free
函数之外,还引入了更安全和更直观的操作方式,主要通过使用new
和delete
操作符来实现。
new
和delete
操作符
new
操作符
用于分配内存。它会自动计算需要分配的内存大小,并调用对象的构造函数(如果有的话)。
delete
操作符
用于释放new
操作符分配的内存,并调用对象的析构函数(如果有的话)。
new/delete操作内置类型
/*new/delete操作内置类型*/
#include <iostream>
using namespace std;
void Test() {
int* ptr1 = new int;
int* ptr2 = new int(10);
int* ptr3 = new int[3];
delete ptr1;
delete ptr2;
delete[] ptr3;
}
new/delete操作自定义类型
/*new/delete操作自定义类型*/
#include <iostream>
using namespace std;
class A {
private:
int _a;
public:
A(int a = 0)
: _a(a) {
cout << "A(int a=0)" << endl;
}
~A() {
cout << "~A()" << endl;
}
};
int main() {
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A) * 10);
A* p6 = new A[10];
free(p5);
delete[] p6;
}
operator new与operator delete底层代码
/*operator new与operator delete底层代码*/
/*
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)
通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
定位new表达式(placement-new)
定位new
表达式(Placement new
)是C++中一个非常有用的特性,它允许你在已分配的内存上构造对象。这种技术特别适合于需要直接在特定内存位置构造对象的场景,例如在内存池、共享内存或嵌入式系统中使用。定位new
的基本思想是重用已有的内存,而不是通过标准的new
表达式请求新的内存分配。
#include <iostream>
#include <new> // 对于定位new必须包含
class MyClass {
public:
MyClass(int x) {
std::cout << "MyClass constructed with value " << x << std::endl;
}
};
int main() {
char buffer[sizeof(MyClass)]; // 分配足够的内存来存储MyClass对象
MyClass* pMyClass = new(buffer) MyClass(10); // 在buffer指向的内存上构造对象
// 使用pMyClass做一些事情...
pMyClass->~MyClass(); // 显式调用析构函数来销毁对象
// 注意:不应该使用delete来释放pMyClass,因为内存不是通过new分配的
}
-
使用定位
new
时,你需要确保分配的内存足够大且适当对齐,以存放指定类型的对象。 -
由于定位
new
不会分配内存,因此你不应该使用delete
来释放对象;相反,你需要显式调用对象的析构函数来销毁它,如果需要的话,手动释放内存。
malloc/free和new/delete的区别
malloc
/free
和new
/delete
是C++中用于内存管理的两套机制,它们在功能上有一定的重叠,但也存在一些关键的区别。理解这些区别对于编写高效、可靠的C++代码非常重要。
基本区别
-
来源和兼容性:
malloc
和free
是C语言中的函数,用于动态内存分配和释放。它们在C++中仍然可用,提供了与C代码的兼容性。new
和delete
是C++引入的操作符,提供了面向对象的内存管理方式。 -
构造函数和析构函数调用:
malloc
仅分配内存,不调用构造函数;free
仅释放内存,不调用析构函数。new
在分配内存的同时调用对象的构造函数;delete
在释放内存前调用对象的析构函数。 -
错误处理:
malloc
在无法分配内存时返回nullptr
。new
在默认情况下会抛出std::bad_alloc
异常,除非使用了nothrow
版本,此时它会返回nullptr
。 -
类型安全:
malloc
返回void*
类型,需要显式转换为目标类型指针。new
直接返回正确的类型指针,提供了类型安全。 -
内存对齐:
malloc
提供的内存对齐可能不适合所有类型的对象。new
保证了分配的内存满足对象对齐要求。 -
分配大小的计算:
使用malloc
时,必须手动计算分配的内存大小。new
自动计算所需的内存大小,基于对象的类型。 -
重载:
malloc
和free
不能被重载,它们是标准库函数。new
和delete
可以被重载,允许自定义内存分配和释放行为。
什么是内存泄漏,内存泄漏的危害
什么是内存泄漏:内存泄漏是程序中的一种资源管理错误,发生于当程序分配的内存没有被正确释放回操作系统或可用内存池,即便它已经不再被使用或无法被访问。内存泄漏是动态内存管理中常见的问题,尤其在使用如C和C++这类需要手动内存管理的编程语言时。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,会导致资源浪费、性能下降、系统稳定性降低,最终可能引起程序或系统崩溃,增加维护成本。
/*内存泄漏示例*/
void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}
内存泄漏的分类
C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据需要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!