3. 内存管理
3.1 malloc, realloc, calloc
malloc、calloc 和 realloc 是在C语言中用于动态内存分配的三个相关函数,它们有一些区别:
-
malloc:
- 功能: 分配指定大小的内存块。
- 用法:
void* malloc(size_t size); - 注意: 返回的内存块中的内容是未初始化的,可能包含垃圾值。
-
calloc:
- 功能: 分配指定数量和大小的连续内存块,并将内存块中的所有位初始化为零。
- 用法:
void* calloc(size_t num, size_t size); - 注意: 返回的内存块中的所有位都被初始化为零。
-
realloc:
-
功能: 重新调整之前分配的内存块的大小。
-
用法:
void* realloc(void* ptr, size_t size); -
注意
:
- 如果成功,返回指向重新调整大小后的内存块的指针。
- 如果失败,返回
NULL,并且原内存块保持不变。 - 可以用于扩大或缩小内存块。
- 可能会移动已分配的内存块到一个新的位置。
-
以下是使用 malloc、calloc 和 realloc 的简单示例:
-
malloc 示例:
#include <stdio.h> #include <stdlib.h> int main() { int *arr; int size = 5; // 使用 malloc 分配内存 arr = (int*)malloc(size * sizeof(int)); if (arr == NULL) { fprintf(stderr, "内存分配失败\n"); return 1; } // 使用分配的内存 for (int i = 0; i < size; i++) { arr[i] = i * 2; } // 释放内存 free(arr); return 0; } -
calloc 示例:
#include <stdio.h> #include <stdlib.h> int main() { int *arr; int size = 5; // 使用 calloc 分配内存,并初始化为零 arr = (int*)calloc(size, sizeof(int)); if (arr == NULL) { fprintf(stderr, "内存分配失败\n"); return 1; } // 使用分配的内存 for (int i = 0; i < size; i++) { printf("%d ", arr[i]); // 输出应该是全零 } // 释放内存 free(arr); return 0; } -
realloc 示例:
#include <stdio.h> #include <stdlib.h> int main() { int *arr; int size = 5; // 使用 malloc 分配内存 arr = (int*)malloc(size * sizeof(int)); if (arr == NULL) { fprintf(stderr, "内存分配失败\n"); return 1; } // 使用分配的内存 for (int i = 0; i < size; i++) { arr[i] = i * 2; } // 重新调整内存块的大小 size = 10; arr = (int*)realloc(arr, size * sizeof(int)); if (arr == NULL) { fprintf(stderr, "内存重新调整失败\n"); return 1; } // 使用重新调整大小后的内存 for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } // 释放内存 free(arr); return 0; }
有关扩容问题,realloc是确定可以原地扩容和异地扩容的
realloc:
- 原地扩容:
realloc可以支持原地扩容。如果在内存管理系统中存在足够的相邻空间,realloc将会在原有的内存块上进行扩容,而不需要移动数据。- 在原地扩容的情况下,
realloc返回的指针值可能与之前的指针值相同,表示在原有的内存块上进行了扩容。
- 异地扩容:
- 如果在原地扩容不可行(例如,相邻空间不足),
realloc可能会在其他地方分配新的内存块,将原有数据复制到新的内存块中,然后释放旧的内存块。 - 在异地扩容的情况下,
realloc返回的指针值可能与之前的指针值不同,表示发生了内存的重新分配和数据的复制。
- 如果在原地扩容不可行(例如,相邻空间不足),
3.2 C++内存分布
在哪里,sizeof, strlen
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);
}
- 选择题:
选项: A.栈 B.堆 C.数据段 D.代码段
globalVar在哪里?____ staticGlobalVar在哪里?____
staticVar在哪里?____ localVar在哪里?____
num1 在哪里?____
答案:因为全局变量,静态变量都是在数据段,数据段AKA全局/静态区(Global/Static Memory) ,所以前三个都是在数据段,localVar局部变量存在栈上,num1是一个指针,也存在栈上,所以答案是(左到右)CCCAA
char2在哪里?____ *char2在哪里?___
pChar3在哪里?____ *pChar3在哪里?____
ptr1在哪里?____ *ptr1在哪里?____
答案:char2是一个指针,存在栈上,这里的char2是一个字符数组,在栈上分配,是可修改的
- 在
*char2中,char2是一个数组,*char2表示数组的第一个元素。 - 在
*pChar3中,pChar3是一个指针,*pChar3表示指针指向的内存位置的内容。
最终答案是AAADAB
- 填空题:
sizeof(num1) = ____; sizeof(char2) = ____; strlen(char2) = ____;
sizeof(pChar3) = ____; strlen(pChar3) = ____;
sizeof(ptr1) = ____;
数组开了10个空间,10个int,那么就是40 ;char2是个字符数组,大小是5;strlen长度的话是4,不算最后的'\0';pchar3是一个指针,所以是4/8,看是32位还是64位;pchar3的strlen长度的话是4;ptr1是一个指针,大小也是4/8
40 ; 5; 4;4/8;4;4/8
sizeof 和 strlen 是C/C++中两个不同的操作符,用于获取信息,但它们有不同的用途和行为
sizeof:
-
用途:
sizeof用于获取某个类型或变量所占用的字节数。- 对于数组,
sizeof返回整个数组占用的字节数。 - 对于指针,
sizeof返回指针本身的字节数。
-
示例:
int arr[5]; size_t sizeOfArray = sizeof(arr); // 返回整个数组占用的字节数 int* ptr; size_t sizeOfPointer = sizeof(ptr); // 返回指针本身占用的字节数
strlen:
-
用途:
strlen用于获取以 null 结尾的字符串的长度,即字符串中的字符个数,不包括 null 终止符。- 适用于字符数组或以指针指向的字符串。
-
示例:
const char str[] = "Hello"; size_t length = strlen(str); // 返回字符串的长度,不包括 null 终止符 -
注意:
strlen需要在 null 终止的字符串上进行操作,如果字符串没有 null 终止符,可能导致未定义的行为。strlen返回的是字符串的实际长度,而不是占用的内存空间。
区别总结:
sizeof是一个运算符,用于获取类型或变量的大小(以字节为单位)。strlen是一个函数,用于获取以 null 结尾的字符串的长度(字符数)。sizeof适用于任何类型或变量,包括数组、结构、指针等。strlen适用于以 null 结尾的字符串。
总体而言,sizeof 用于获取内存占用的大小,而 strlen 用于获取字符串的实际长度。在使用时需要根据具体的需求选择合适的操作符。
3.3 new 和 delete
new/malloc 有底层实现机制关联交叉,不匹配使用,可能有问题,可能没问题,建议匹配使用
**malloc/free ** 和 new/delete的区别 malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
-
malloc和free是函数,new和delete是操作符
-
malloc申请的空间不会初始化,new可以初始化
-
malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
-
malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
-
malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
-
申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
往 用法 + 底层答即可,6是重点
3.4 定位new
定位new是 在已分配的原始内存空间中调用构造函数初始化一个对象
格式 new (place_address) type或者**new (place_address) type(initializer-list)*place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化
class Test
{
public:
Test()
: _data(0)
{
cout<<"Test():"<<this<<endl;
}
~Test()
{
cout<<"~Test():"<<this<<endl;
}
private:
int _data;
};
void Test()
{
// pt现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
Test* pt = (Test*)malloc(sizeof(Test));
new(pt) Test; // 注意:如果Test类的构造函数有参数时,此处需要传参
}
定位 new 主要用于以下情况:
- 在预分配的内存区域上构造对象: 定位
new允许程序员在预分配的内存空间上构造对象,而不是使用默认的堆分配。这对于特殊的内存管理需求很有用,例如在嵌入式系统或硬实时系统中。 - 对象的放置 new 运算符: 定位
new可以用于实现自定义的内存管理策略。程序员可以控制对象被放置的内存位置,以满足特殊的性能或硬件要求。 - 实现自定义的内存池: 定位
new对于实现自定义的内存池非常有用。程序员可以通过定位new将对象分配到预分配的内存块中,而不是使用默认的堆分配。这有助于提高内存分配的效率和灵活性。 - 处理特殊硬件要求: 在某些嵌入式系统或特殊硬件环境中,可能需要更精确地控制对象的内存分配和位置。定位
new允许程序员直接管理对象的内存,以适应特定的硬件要求。 - 实现自定义的内存管理策略: 如果程序需要实现自定义的内存管理策略,例如内存池、自定义分配器等,定位
new可以提供更灵活的方式来管理对象的内存。