文章目录
- 📝C/C++内存分布
- 🌠 C语言中动态内存管理方式
- 🌉C++内存管理方式
- 🌠new/delete操作内置类型
- 🌉C与C++链表构建对比
- 🚩总结
📝C/C++内存分布
这是C/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);
}
- 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?C staticGlobalVar在哪里?C
staticVar在哪里?C localVar在哪里?A
num1 在哪里?Achar2在哪里?A *char2在哪里?_A
pChar3在哪里?A *pChar3在哪里?D
ptr1在哪里?A *ptr1在哪里?B
-
全局变量
globalVar
和staticGlobalVar
都存储在数据段(静态区)中。全局变量globalVar
的生命周期贯穿整个程序的执行过程,直到程序结束,静态全局变量staticGlobalVar
的作用域仅限于当前源文件,其生命周期也贯穿整个程序的执行过程。 -
staticVar
是静态局部变量,也存储在数据段(静态区)中。 -
localVar
是普通的局部变量,存储在栈中,栈是一种后进先出(LIFO)
的数据结构,用于存储函数调用时的局部变量和返回地址等信息,当函数调用结束时,栈中分配给该函数的内存空间会被自动释放。 -
局部数组
num1
存储在栈中,数组在内存中是连续分布的,因此num1
占用了一块连续的栈空间。 -
*char2
和char2
在栈中,
*char2
:char2[]
是一个局部字符数组,存储在栈上。当你使用字符串字面量初始化它时,编译器会在栈上分配足够的内存空间,并将字符串字面量的内容(包括结尾的\0
)复制到这块内存中,所以 *char2 指向的是存储在栈上的可修改的字符数组。
*pChar3
:const char* pChar3 = "abcd"
; 中的字符串字面量"abcd"
存储在只读的数据段(常量区)中。而pChar3
本身是一个指针变量,存储在栈上,它指向常量区中的字符串。由于字符串字面量是只读的,所以通过 *pChar3 我们只能读取字符串的内容,而不能修改它。
-
*pChar3
在栈中,pChar3
在代码段(常量区),指针变量pChar3
存储在栈中,*pChar3
指向一个字符串常量,该字符串常量存储在代码段(常量区)中,代码段(常量区)用于存储程序中的常量数据,如字符串常量、枚举常量等。这些常量在程序执行期间不会被修改。 -
ptr1
是局部指针变量,存储在栈上 -
*ptr1
指向的内容,就是malloc
分配的内存,该内存在堆上
总结:
- 栈(Stack): 用于存储函数调用时的上下文信息,如返回地址、函数参数和局部变量,遵循先进后出(LIFO)的原则,大小有限,如果使用不当可能导致栈溢出
- 堆(Heap): 用于动态分配内存,存储动态分配的对象和数据结构,开发者需要手动管理堆上的内存,分配和释放,大小一般比栈要大得多,但访问速度相对较慢
- 数据段(Data Segment): 分为初始化的数据段(.data)和未初始化的数据段(.bss)用于存储全局变量和静态变量,这些变量的生命周期贯穿整个程序执行期
- 代码段(Code Segment): 存储可执行的机器指令,通常是只读的,以保护程序代码不被意外修改,存着可执行的代码/只读常量。
填空题:
sizeof(num1) = ____;
sizeof(char2) = ____; strlen(char2) = ____;
sizeof(pChar3) = ____; strlen(pChar3) = ____;
sizeof(ptr1) = ____;
sizeof(num1) = 40;
num1
是一个包含 10 个int
类型元素的数组,每个int
类型占 4 个字节,所以数组大小为 10 * 4 = 40 字节。sizeof(char2) = 5; strlen(char2) = 4;
char2
是一个包含 5 个字符(包括结尾的'\0'
)的字符数组,所以sizeof(char2)
为 5 字节。strlen(char2)
返回字符串的长度,不包括结尾的'\0'
,所以为 4。
sizeof(pChar3) = 8; strlen(pChar3) = 4;
pChar3
是一个指向字符串常量"abcd"
的指针,在 32 位系统上,指针大小为 4 字节。在 64 位系统上,指针大小为 8 字节。strlen(pChar3)
返回字符串的长度,不包括结尾的'\0'
,所以为 4。
sizeof(ptr1) = 8;
ptr1
是一个指向动态分配的int
类型数组的指针,在 32 位系统上,指针大小为 4 字节。在 64 位系统上,指针大小为 8 字节。
sizeof 和 strlen 区别?
sizeof
和strlen
是两个不同的操作符/函数,sizeof
是一个编译时操作,返回变量或数据类型的大小;而strlen
是一个运行时函数,返回字符串的长度。
- sizeof:
sizeof
是一个操作符,用于获取变量或数据类型的大小(以字节为单位),它在编译时就确定了返回值,不需要在运行时计算,对于数组,sizeof
返回整个数组的大小,而不是单个元素的大小,对于指针,sizeof
返回指针本身的大小,而不是它所指向的对象的大小。
示例:
char str[] = "hello";
printf("Size of str: %zu\n", sizeof(str)); // 输出: 6 (包括'\0')
printf("Size of char: %zu\n", sizeof(char)); // 输出: 1
- strlen:
strlen
是一个函数,用于计算字符串的长度(不包括结尾的'\0'
字符),它在运行时计算字符串的长度,需要遍历整个字符串,对于数组,strlen
只能用于字符数组(字符串),不能用于其他类型的数组,对于指针,strlen
可以计算指针所指向的字符串的长度。
示例:
char str[] = "hello";
printf("Length of str: %zu\n", strlen(str)); // 输出: 5
🌠 C语言中动态内存管理方式
- malloc:
语法:void* malloc (size_t size);
功能:动态分配指定大小的内存块,并返回指向该内存块的指针, 分配的内存块内容是未初始化的。
使用方法:
int* ptr = (int*)malloc(sizeof(int) * 4);
if (ptr == NULL)
{
// 内存分配失败,处理错误
return;
}
// 使用分配的内存
// ...
free(ptr); // 释放内存
- calloc:
语法:void* calloc (size_t num, size_t size);
功能:动态分配指定数量和大小的内存块,并返回指向该内存块的指针,分配的内存块内容会被初始化为0
。
使用方法:
// 分配 4 个 int 型元素的内存,并初始化为 0
int *ptr = (int *)calloc(4, sizeof(int));
if (ptr == NULL) {
// 内存分配失败,处理错误
return;
}
// 使用分配的内存,所有元素都被初始化为 0
// ...
free(ptr); // 释放内存
- realloc:
语法:void* realloc (void* ptr, size_t size);
功能:调整已分配内存块的大小,并返回指向新内存块的指针。- 如果新大小小于原大小,则保留原有数据;如果新大小大于原大小,则原有数据会被保留,新增部分为未初始化。
- 如果
ptr
为NULL
,则等同于malloc(size)
。
使用方法:
// 先分配 4 个 int 型元素的内存
int *ptr = (int *)malloc(4 * sizeof(int));
if (ptr == NULL)
{
// 内存分配失败,处理错误
return;
}
// 使用分配的内存
// ...
// 重新分配为 8 个 int 型元素的内存
int *new_ptr = (int *)realloc(ptr, 8 * sizeof(int));
if (new_ptr == NULL)
{
// 内存重新分配失败,处理错误
free(ptr); // 释放原有内存
return;
}
ptr = new_ptr; // 更新指针
// 使用新分配的内存
// ...
free(ptr); // 释放内存
- free:
语法:void free (void* ptr);
功能:释放动态分配的内存块,将其返回给操作系统。注意:必须确保释放的内存块是之前使用malloc
/calloc
/realloc
动态分配的。- 如果
ptr
为NULL
,则该函数不执行任何操作。
使用方法:
- 如果
int *ptr = (int *)malloc(4 * sizeof(int));
if (ptr == NULL)
{
// 内存分配失败,处理错误
return;
}
// 使用分配的内存
// ...
free(ptr); // 释放内存
// 不能再访问已释放的内存
常见注意要点:
- 动态分配的内存必须在使用完毕后及时释放,否则会导致内存泄漏。
- 不能访问已经释放的内存块,否则会出现未定义行为。
- 如果分配失败,这些函数会返回
NULL
指针,需要进行错误处理。
🌉C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new
和delete
操作符进行动态内存管理。
#include<stdlib.h>
int main()
{
int* ptr = (int*)malloc(4 * sizeof(int));
free(ptr);
int* ptr2 = (int*)calloc(4, sizeof(int));
//判断是否成功开辟空间,每个还需要检查
int* ptr3 = (int*)realloc(ptr, 8 * sizeof(int));
free(ptr3);
return 0;
}
🌠new/delete操作内置类型
在 C++ 中,new
和 delete
操作符用于动态内存分配和释放。当使用这些操作符时,需要注意以下几点:
内置类型:
- 对于内置类型(如
int
、double
、char
等),使用new
和delete
操作符与使用malloc
和free
函数的效果是相同的。 - 例如:
int* ptr = new int; // 分配一个 int 类型的内存空间
delete ptr; // 释放 ptr 指向的内存空间
分配内存,但没有初始化
- 动态申请一个int类型的空间并初始化为10
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
delete ptr2;
动态申请10个int类型的空间,并释放
int* arr = new int[10]; // 动态申请10个int类型的空间
delete[] arr; // 释放 arr 指向的数组内存空间
当然,我们也可以开辟空间的时候,又进行初始化
#include<iostream>
using namespace std;
int main()
{
// 动态申请一个int类型的空间并初始化为10
int* ptr3 = new int[10]{ 2,3,4,5,5 };
delete[] ptr3;
return 0;
}
这样一部分初始化想要的值,后面默认初始化为0
- 使用
new
和delete
操作符时,编译器会自动调用构造函数和析构函数,但对于内置类型来说,这些函数是空操作。
注意:申请和释放单个元素的空间,使用new
和delete
操作符,申请和释放连续的空间,使用new[]
和delete[]
,注意:匹配起来使用。
🌉C与C++链表构建对比
C语言构造链表节点的方式:
struct ListNode
{
ListNode* _next;
int _data;
};
struct ListNode* LTCreateNode(int x)
{
struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->_data = x;
newnode->_next = NULL;
return newnode;
}
这是C++的实现:
struct ListNode
{
ListNode* _next;
int _data;
ListNode(int data)
:_next(nullptr)
, _data(data)
{}
};
前面我们知道new不仅会开空间,还会调用构造函数,析构函数的目的是初始化,delete会调用析构函数,因此即使是自定义类型,也可以使用new开空间并初始化。
因此,只要我们写好构造函数,new的使用是真香啊