C++动态内存管理涉及使用new
和delete
操作符来动态分配和释放堆内存。new
用于在堆上分配内存并初始化对象,delete
用于释放先前分配的内存。此外,C++还提供了智能指针如std::unique_ptr
和std::shared_ptr
来自动管理内存,以避免内存泄漏和悬挂指针。这些智能指针在超出作用域时会自动删除其所指向的对象。
C/C++内存分布不同的人会有不同的分布,这里列举两条常见的,本文主要基于分类2
在C/C++中,内存可以被分为几个不同的部分:
malloc()
或calloc()
函数分配内存,在C++中,使用new
关键字分配内存。当不再需要分配的内存时,必须手动使用free()
(C)或delete
(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在哪里?____ staticGlobalVar在哪里?____
staticVar在哪里?____ localVar在哪里?____
num1 在哪里? ____ char2在哪里____
*char2在哪里?___
pChar3在哪里?____ *pChar3在哪里?____
ptr1在哪里?____ *ptr1在哪里?____
2. 填空题:
sizeof(num1) = ____;
sizeof(char2) = ____; strlen(char2) = ____;
sizeof(pChar3) = ____; strlen(pChar3) = ____;
sizeof(ptr1) = ____;
3. sizeof 和 strlen 区别?
C语言从入门到实战——数组和指针的强化练习题可看这篇文章强化一下
sizeof
是一个运算符,用于获取一个变量或类型的字节大小。它可以用于任何类型的变量,包括基本数据类型和自定义数据类型。sizeof
可以在编译时计算,因此不需要实际运行程序。
strlen
是一个函数,用于获取一个字符数组的长度,即字符的个数。它只能用于以null
字符('\0'
)结尾的字符串。strlen
在运行时计算字符个数,所以需要遍历整个字符数组来计算长度。
所以,sizeof
用于获取变量或类型的字节大小,而strlen
用于获取以null
字符结尾的字符串的字符个数。
int a[] = { 1,2,3,4 };//a数组有4个元素,每个元素是int类型的数据
printf("%zd\n", sizeof(a));//16 - sizeof(数组名)的情况,计算的是整个数组的大小,单位是字节 - 16
printf("%zd\n", sizeof(a + 0));//a表示的就是数组首元素的地址,a+0还是首元素的地址 - 4/8
//int*
printf("%zd\n", sizeof(*a));//a表示的就是数组首元素的地址,*a 就是首元素,大小就是4个字节
printf("%zd\n", sizeof(a + 1));//a表示的就是数组首元素的地址,a+1就是第二个元素的地址,这里的计算的是第二个元素的地址的大小-4/8
printf("%zd\n", sizeof(a[1]));//a[1]是数组的第二个元素,大小是4个字节
printf("%zd\n", sizeof(&a));//&a - 取出的是数组的地址,但是数组的地址也是地址,是地址,大小就是4/8个字节
char arr[] = "abcdef";
printf("%zd\n", sizeof(arr));
printf("%zd\n", sizeof(arr + 0));//arr+0是数组首元素的地址,地址的大小是4/8个字节
printf("%zd\n", sizeof(*arr));//*arr是数组的首元素,这里计算的是首元素的大小 1
printf("%zd\n", sizeof(arr[1]));//1
printf("%zd\n", sizeof(&arr));//&arr - 是数组的地址,数组的地址也是地址,是地址就是4/8个字节
printf("%zd\n", sizeof(&arr + 1));//&arr+1,跳过整个数组,指向了数组的后边,4/8
printf("%zd\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址 4/8
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));//随机值
printf("%d\n", strlen(arr + 0));//随机值
//'a'-97
//printf("%d\n", strlen(*arr));//err
// //'b'-98
//printf("%d\n", strlen(arr[1]));//err
printf("%d\n", strlen(&arr));//随机值
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//随机值
C语言从入门到实战——动态内存管理,可以看这篇文章,详细了解一下,本文不做过多介绍。
void Test()
{
int* p1 = (int*)malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int) * 10);
// 这里需要free(p2)吗?11 free(p3 );12 }
free(p3);
}
malloc
、calloc
、realloc
都是用于动态分配内存空间的函数。
malloc
函数用于分配指定大小的内存空间,它只分配内存,并不对内存进行初始化。示例:void* malloc(size_t size)
。
calloc
函数用于分配指定数量和大小的连续内存空间,并将分配的内存空间初始化为0。示例:void* calloc(size_t num, size_t size)
。
realloc
函数用于重新分配已经分配的内存空间的大小,可以扩大或缩小已经分配的内存空间。示例:void* realloc(void* ptr, size_t size)
。
总结:
malloc
只分配内存,不进行初始化。calloc
分配内存并初始化为0。realloc
重新分配内存的大小,可以扩大或缩小原来的内存空间。glibc中malloc实现原理 malloc是一个动态内存分配函数,用于在运行时分配指定大小的内存空间。malloc的实现原理如下:
以上就是malloc的简单实现原理。实际上,malloc的实现可能会更加复杂,考虑到内存对齐、线程安全、内存池等因素。不同的操作系统和编译器也会对malloc进行调优和优化。
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new
和delete
操作符进行动态内存管理。
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[10];
delete ptr4;
delete ptr5;
delete[] ptr6;
}
注意:申请和释放单个元素的空间,使用new
和delete
操作符,申请和释放连续的空间,使用new[]
和delete[]
,注意:匹配起来使用。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
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)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A) * 10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
注意:在申请自定义类型的空间时,new
会调用构造函数,delete
会调用析构函数,而malloc
与free
不会。
new
和delete
是用户进行动态内存申请和释放的操作符,operator new
和operator delete
是系统提供的全局函数,new
在底层调用operator new
全局函数来申请空间,delete
在底层通过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;
}
通过上述两个全局函数的实现知道,operator new
实际也是通过malloc
来申请空间,如果malloc
申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete
最终是通过free
来释放空间的。
在C++中,new
操作符和operator new
是两个不同的概念。
new
是一个表达式,用于在堆上动态分配对象,并返回对象的指针。它会执行以下操作:
operator new
分配所需的内存空间。operator new
是一个函数,用于在堆上分配内存空间,但是它不会调用对象的构造函数。它只执行以下操作:
所以,可以说new
是一个包含了operator new
操作的更高级别的操作符。在使用new
时,它会自动调用operator new
来分配内存,并调用构造函数进行对象的初始化。而直接使用operator new
则只分配内存,不会调用构造函数。
在C++中,我们通常使用new
来动态分配对象,而不直接使用operator new
,因为它提供了更高的抽象级别,并能确保对象的正确初始化。另外,使用new
时,还可以使用delete
来释放分配的内存,并调用对象的析构函数进行清理。而直接使用operator new
分配的内存,则需要使用operator delete
来释放内存,没有自动调用析构函数的功能。
operator delete
和delete
是在释放动态分配的内存时使用的两个不同的概念。
delete
是一个表达式,用于释放通过new
操作符动态分配的对象的内存。它会执行以下操作:
operator delete
释放分配的内存。operator delete
是一个函数,用于释放通过operator new
分配的内存,它只执行以下操作:
总结一下,delete
是一个包含了调用析构函数和operator delete
操作的高级别操作符。它不仅释放内存,还能确保对象的析构函数被正确调用。
在C++中,我们通常使用delete
来释放通过new
分配的内存,因为它提供了更高的抽象级别,并能确保对象的正确清理和释放。而直接使用operator delete
来释放内存,则需要自己手动调用对象的析构函数进行清理,没有自动调用析构函数的功能。
在C++中,使用关键字new
动态分配内存时,如果分配失败,会抛出std::bad_alloc
异常。因此,当我们使用new
开辟空间时,不需要显式检查接受的指针是否为空。
如果new
分配内存失败,它会抛出异常,程序会捕获该异常并做相应的处理。因此,如果new
调用返回了一个非空指针,我们可以确定内存分配成功,不必再额外检查指针是否为空。
然而,当我们使用new
分配内存时,还是有一些需要注意的地方:
std::nothrow
,它将在分配失败时返回nullptr
,而不是抛出异常。delete
来释放动态分配的内存。总的来说,虽然使用new
动态分配内存时不需要显式检查接受的指针是否为空,但在使用动态分配内存的过程中,我们仍需要注意其他相关的问题。
new
是可以和free
配对的,当然malloc
也是可以和delete
配对的,主要的问题是,他们进行配对会在某些特定情况下进行报错,所以我不建议交错使用
存在析构函数会直接导致报错,具体原因是释放空间不对
free
不行,delete
也不行,只有delete[]
可以
在C++中,使用new
关键字来动态分配内存时,分配的内存大小取决于所创建的对象的类型。对于内置类型(如int
、float
等),分配的内存大小与其字节大小相同。但对于自定义类型,分配的内存大小可能会比其成员变量的总大小大出几个字节。
这是因为C++编译器在内部为自定义类型的对象维护了一些附加的信息,以便进行对象的构造和析构等操作。这些附加信息可能包括虚函数表指针(如果类具有虚函数)、访问控制信息、数据成员的偏移量等。
总结:自定义类型并不一定比内置类型多开几个字节,主要看存不存在析构函数,如果存在析构函数,使用free
和delete
是会报错,要使用delete[]
,因为delete[]
底层会自动向前取几个字节
在C语言中,使用malloc
函数来动态分配内存时,分配的内存大小取决于所请求的字节数,与类型无关。因此,无论是自定义类型还是内置类型,使用malloc
函数分配的内存大小都是一样的。
malloc
函数分配的内存空间是以字节为单位进行分配的。无论是内置类型还是自定义类型,都需要根据其大小来确定所需的字节数,并将其作为参数传递给malloc
函数。因此,相同大小的内置类型和自定义类型,在使用malloc
分配内存时,分配的空间大小是相同的。
需要注意的是,与C++不同,C语言中的malloc
不会为自定义类型分配额外的字节来存储附加信息,如虚函数表指针。在C中,我们需要自己管理内存,确保为自定义类型分配的空间大小足够存储其成员变量的值,并正确地进行内存访问和释放操作。