见下图:

想自测的小伙伴可以看下述的题:

代码语言:javascript

AI代码解释

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);
}

A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

globalVar在哪里?____ staticGlobalVar在哪里?____ staticVar在哪里?____

localVar在哪里?____ num1 在哪里?____ char2在哪里?____

pChar3在哪里?____ ptr1在哪里?____ *char2在哪里?___

*pChar3在哪里?____ *ptr1在哪里?____

答案如下:

代码语言:javascript

AI代码解释

int globalVar = 1;//全局变量定义在数据段
static int staticGlobalVar = 1;//静态全局变量也定义在数据段
//上面这两个全局变量是在main函数之前就已经初始化,在哪都能用,作用域是全局的
//但是这两个是有区别的,区别在于链接属性是不一样的,globalVar是所有文件可见,staticGlobalVar只在当前文件可见
void Test()
{
	static int staticVar = 1;//静态局部变量也定义在数据段,这一个是运行到这里再初始化,它的作用域在Test函数中,只能在Test函数中使用
	int localVar = 1;//局部变量定义在栈区
	int num1[10] = { 1, 2, 3, 4 };//数组定义在栈区
	char char2[] = "abcd";//数组定义在栈区
	const char* pChar3 = "abcd";//指针定义在栈区
	int* ptr1 = (int*)malloc(sizeof(int) * 4);//ptr1是指针存放在栈区,但是它所指向的内容却是存放在堆区
	int* ptr2 = (int*)calloc(4, sizeof(int));//ptr2是指针存放在栈区,但是它所指向的内容却是存放在堆区
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);//ptr3是指针存放在栈区,但是它所指向的内容却是存放在堆区
	free(ptr1);
	free(ptr3);
}

需要注意:上面讲述了全局的全局变量和静态变量的区别,以及局部的静态变量与全局变量的区别。

趁势我们把这些内容也回顾下:

sizeof(num1) = ____; sizeof(char2) = ____; sizeof(pChar3) = ____;

sizeof(ptr1) = ____; strlen(char2) = ____; strlen(pChar3) = ____;

答案如下:

sizeof(num1) = 40; sizeof(char2) = 5; sizeof(pChar3) = 4/8;

sizeof(ptr1) = 4/8; strlen(char2) = 4; strlen(pChar3) = 4;

sizeof 和 strlen 区别

本质不同

  • sizeof
    • 编译时运算符(不是函数),在编译期间确定结果。
    • 功能:计算变量或数据类型所占内存的字节数(包括字符串末尾的 \0)。
  • strlen
    • 运行时函数(定义在 <string.h> 中)。
    • 功能:计算字符串的实际长度(从起始地址到第一个 \0 前的字符数,不包括 \0)。

代码语言:javascript

AI代码解释

//静态字符数组
char str[] = "Hello";
//sizeof(str) → 结果为 6(5个字符 + 结尾的 \0)。
//strlen(str) → 结果为 5(只计算有效字符 'H','e','l','l','o')。

//指针指向的字符串
const char* ptr = "Hello";
//sizeof(ptr) → 结果为 指针大小(4 或 8 字节,取决于系统)。
//strlen(ptr) → 结果为 5(字符串内容长度)。

//固定长度数组
char arr[100] = "abc";
//sizeof(arr) → 结果为 100(整个数组的内存大小)。
//strlen(arr) → 结果为 3(字符串 "abc" 的长度)。

总结如下:

特性

sizeof

strlen

类型

运算符(编译时求值)

函数(运行时求值)

参数

可接受变量、数据类型或表达式

只接受字符串地址(const char*)

计算内容

内存占用大小(包括 \0)

字符串有效长度(不包括 \0)

安全性

无副作用(不访问内存)

需遍历字符串直到 \0(可能越界)

数组退化

对数组返回实际大小

数组退化为指针后失效

指针处理

返回指针本身的大小(4/8 字节)

返回指针指向字符串的长度

📕二、C++内存管理方式

✨2.1 C语言内存管理方式(回顾)

C语言当中我们知道malloc/calloc/realloc/free,在此进行回顾下:

特性

malloc

calloc

realloc

初始化

不初始化(垃圾值)

初始化为零

保留原数据,新增部分不初始化

参数

1 个(总字节数)

2 个(元素数量 × 元素大小)

2 个(原指针 + 新字节数)

主要用途

通用内存分配

需要初始化的数组

调整已分配内存的大小

内存计算

直接指定字节数

自动计算 num * size

修改现有内存块大小

性能

较快(不初始化)

较慢(需初始化)

可能涉及内存复制(较慢)

malloc (Memory Allocation)

  • 功能:分配指定字节数的未初始化内存
  • 特点
    • 只接受一个参数:需要分配的字节数size
    • 分配的内存内容是未初始化的(包含随机垃圾值)
    • 适用于需要精确控制内存大小的场景

代码语言:javascript

AI代码解释

void* malloc(size_t size);
int* arr = (int*)malloc(5 * sizeof(int)); // 分配 20 字节(假设 int 占 4 字节)

calloc (Contiguous Allocation)

  • 功能:分配指定数量和大小的内存块并初始化为零
  • 特点
    • 接受两个参数:元素数量(num)和单个元素大小(size
    • 分配的内存内容会被自动初始化为 0(比 malloc 更安全)
    • 总分配大小 = num * size
    • 适用于需要初始化数组的场景

代码语言:javascript

AI代码解释

void* calloc(size_t num, size_t size);
int* arr = (int*)calloc(5, sizeof(int)); // 分配并初始化 5 个 int(全 0)

realloc (Re-allocation)

  • 功能调整已分配内存块的大小
  • 特点
    • 接受两个参数:原内存指针(ptr)和新字节大小new_size
    • 可能的行为:
      • 原地扩展/缩小内存(如果后续空间足够)
      • 重新分配新内存,复制旧数据,释放旧内存
    • 不会初始化新增的内存区域
    • 如果 ptrNULL,则等价于 malloc(new_size)

代码语言:javascript

AI代码解释

void* realloc(void* ptr, size_t new_size);
int* new_arr = (int*)realloc(arr, 10 * sizeof(int)); // 扩展到 40 字节
✨2.2 new/delete

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理

用法如下:

代码语言:javascript

AI代码解释

class A
{
public:
	A(int n = 0)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
};
int main()
{
	A* p1 = (A*)malloc(sizeof(A));
    free(p1);
	A* p2 = new A;//对于自定义类型是如此
	delete p2;
    int* p3 = new int;//对于内置类型这样定义
    int* p4 = new int(100);//这是定义个int类型并初始化的形式
    int* p5 = new int[100];//这是定义数组的形式
    delete[100] p5;//对于数组释放空间是这样定义
    int* p6 = new int[100]{ 1,2,3 };//这是定义数组的形式并初始化的形式
	return 0;
}

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用 new[]和delete[]

相信会有这样的疑惑,既然C++继承了C语言的用法,即默认malloc、calloc等函数是可以使用的,为什么还要new/delete这些呢?

这就需要讲述它们的差别:

new与malloc

  • 对于内置类型来说,new和malloc没有什么区别
  • 对于自定义类型来说,new会调用自定义类型的构造函数,而malloc不会
  • new可以通过构造函数初始化变量的内容

delete与free

  • 对于内置类型,delete和free没有什么区别
  • 对于自定义类型,delete会调用自定义类型的析构函数,而free不会
  • delete面对不同的情况需要加上[ ],然而free不用考虑
✨2.3 operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符

operator new 和operator delete是系统提供的全局函数

new在底层调用operator new全局函数来申请空间,delete在底层通过 operator delete全局函数来释放空间

operator new使用方式:

代码语言:javascript

AI代码解释

void* p1 = malloc(1024);
void* p2 = operator new (1024);

可以观察下面operator new库中的实现本质就是用malloc申请空间

代码语言:javascript

AI代码解释

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
void *p;
while ((p = malloc(size)) == 0)
	if (_callnewh(size) == 0)
	{
	    // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
	    static const std::bad_alloc nomem;
	    _RAISE(nomem);
	}
return (p);
}

所以 operator new域malloc正常使用的方式都是一样的,只不过处理错误的方式不一样

代码语言:javascript

AI代码解释

int main()
{
	size_t size = 2;
	void* p1 = malloc(size * 1024 * 1024 * 1024);
	cout << p1 << endl; //申请失败返回0
	try
	{
		void* p2 = operator new (size * 1024 * 1024 * 1024);
		cout << p2 << endl;//失败抛异常(面向对象处理错误的方式)
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

抛异常不是本章节的重点,等到我们学习到此的时候,再着重学习!

所以malloc、operator new、new它们的关系如下:

malloc operator new -->malloc+失败抛异常 new --> operator new + 构造函数 new比malloc不一样的地方:1.调用构造函数初始化 2. 失败抛异常 delete和free不一样的地方在于delete调用析构函数清理 operator delete 和free没有本质区别

补充知识: 相信上述代码你关注到了 size_t size = 2; size * 1024 * 1024 * 1024; 为什么不写成2*1024*1024*1024?注意此处我们是为了让它开辟不出空间,如果写成2*1024*1024*1024那这里就会报溢出错误,原因如下:

  • 整数类型范围
    • 在 C ++ 中,整数类型有不同的长度和表示范围。int 通常是一个有符号类型,在 32 - bit 系统中占 32 位。它的取值范围是 - 2^(31) 到 2^(31) - 1,即 - 2147483648 到 2147483647。当计算结果超过这个范围时,就会发生溢出
    • size_t 是一个无符号整数类型,用于表示大小和索引等非负值。在 32 - bit 系统中,size_t 通常是 32 位无符号整数,其范围是 0 到 4294967295无符号整数在计算过程中不会出现负数,当计算结果超过其最大值时,会产生溢出,但不会像有符号整数那样出现负值,而是会循环回到 0 并继续计算

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐