这是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);
}
char2在哪里?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
分配的内存,该内存在堆上
总结:
填空题: 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
返回指针本身的大小,而不是它所指向的对象的大小。示例:
char str[] = "hello";
printf("Size of str: %zu\n", sizeof(str)); // 输出: 6 (包括'\0')
printf("Size of char: %zu\n", sizeof(char)); // 输出: 1
strlen
是一个函数,用于计算字符串的长度(不包括结尾的 '\0'
字符),它在运行时计算字符串的长度,需要遍历整个字符串,对于数组,strlen
只能用于字符数组(字符串),不能用于其他类型的数组,对于指针,strlen
可以计算指针所指向的字符串的长度。
示例:char str[] = "hello";
printf("Length of str: %zu\n", strlen(str)); // 输出: 5
void* malloc (size_t size);
功能:动态分配指定大小的内存块,并返回指向该内存块的指针, 分配的内存块内容是未初始化的。
使用方法:int* ptr = (int*)malloc(sizeof(int) * 4);
if (ptr == NULL)
{
// 内存分配失败,处理错误
return;
}
// 使用分配的内存
// ...
free(ptr); // 释放内存
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); // 释放内存
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); // 释放内存
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++又提出了自己的内存管理方式:通过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;
}
在 C++ 中,new
和 delete
操作符用于动态内存分配和释放。当使用这些操作符时,需要注意以下几点:
内置类型:
int
、double
、char
等),使用 new
和 delete
操作符与使用 malloc
和 free
函数的效果是相同的。 int* ptr = new int; // 分配一个 int 类型的内存空间
delete ptr; // 释放 ptr 指向的内存空间
分配内存,但没有初始化
// 动态申请一个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语言构造链表节点的方式:
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的使用是真香啊