1. 动态内存分配
1.1 内存布局
在C++中,内存区域通常指的是程序运行时,操作系统为程序分配的内存空间。这些内存区域用于存储程序的不可部分,包括代码、数据、堆和栈等。
1. 文本区域(Text Segment)
也称为代码区域。 存储程序的二进制代码,包括机器指令。 该区域是只读的,以防止程序意外地修改自己的指令。
2. 全局区域(Initialized Data Segment)
通常分为数据段(Data Segment)、BSS段(BSS Segment,Block Started by Symbol)、常量区。 数据段包含程序中已经初始化的全局变量和静态变量。 BSS段包含程序中未初始化的全局变量和静态变量,操作系统会在程序加载时将其初始化为零或空指针。 常量区里面存放的是常量,如const修饰的全局变量、字符串常量等。
3. 堆区域(Heap Segment)
堆是用于动态内存分配的区域,通过new/ma11oc操作符来分配内存。 堆区域由程序员负责管理,需要显式地调用delete/free来释放分配的内存,否则会导致内存泄漏。 堆区域在程序执行期间持续增长,直到达到操作系统分配的上限。
4. 栈区域(Stack Segment)
栈用于存储局部变量、函数调用的上下文(如返回地址和参数)以及程序执行过程中的临时数据。 每当函数被调用时,都会在栈上为其分配一个新的栈帧,包含该函数的局部变量和参数。函数返回时,其栈帧会被销毁,释放内存。
1.2 malloc
(C语言申请内存,C++也可使用,但是不建议) malloc函数可以动态分配一组连续字节,这个函数需要一个整数类型的参数表示希望分配的字节个数,它的返回值就是分配好的第一个字节的地址。如果分配失败则返回值是NULL,函数把返回值记录的在无类型指针的存储区里,需要强制转换成有类型指针然后再使用
#include <stdlib>
void *malloc(size_t size);功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。 分配的内存空间内容不确定,一般使用memset初始化。
size: 需要分配内存大小(单位:字节)
返回值:
成功:分配空间的起始地址
失败:NULL
使用 malloc 函数时,需要注意以下几点:
- 类型转换:由于
malloc返回的是void *类型的指针,因此通常需要将其转换为实际所需数据类型的指针。例如,如果你想要分配一个整数数组,你需要将malloc的返回值转换为int *。
int *array = (int *)malloc(sizeof(int) * num_elements);- 内存初始化:
malloc分配的内存区域不会自动初始化,其内容是不确定的。如果需要,你必须手动初始化分配的内存。为了防止脏数据的出现,一般在申请完空间后,将申请到的空间内容清零
#include <cstdlib>
void * memset(void * buffer, int c, size_t num);功能:将buffer后面的num个字节用 c 替换
参数:
buffer: 需要替换的内存块
c : 要被设置的值。
num: 被设置为该值的字节数。
返回值:
成功:该值返回一个指向存储区 buffer 的指针。
失败:NULL
int *array = (int *)malloc(sizeof(int) * num_elements);
//将申请到空间前
memset(array, 0, sizeof(int) * num_elements);
//emset(array, 0, sizeof(array)); //error_array只是一个指针内存释放:使用
malloc分配的内存必须使用free函数来释放,否则会造成内存泄漏。错误处理:在调用
malloc后,应检查返回值是否为NULL,以处理内存分配失败的情况。
int *array = (int *)malloc(sizeof(int) * num_elements);
if (array == NULL) {
// 内存分配失败,处理错误
printf("Memory allocation failed.\n");
return -1;
}1.3 free
(C语言释放内存,C++也可使用,但是不建议)
- 释放申请的区域
#include <cstdlib>
void free(void *ptr);功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域的首地址。对同一内存空间多次释放发出错。
参数:
ptr: 需要释放空间的首地址,被释放区域是由malloc函数所分配的区域。
返回值:无
实例
#include<cstdlib>
int main()
{
int* age = NULL;
int n = 0;
std::cout << "请输入人数:" ;
std::cin >> n;
age = (int*)malloc(n * sizeof(int));
if (age == NULL)
{
std::cout << "申请空间失败" << std::endl;
return -1;
}
//将申请到空间值
memset(age, 0, sizeof(int) * n);
for (int i = 0; i < n; i++) /*给数组赋值*/
{
age[i] = i + 1;
}
for (int i = 0; i < n; i++) /*打印数组元素*/
{
std::cout << age[i] << " ";
}
free(age);
age = NULL;
std::cout << std::endl;
return 0;
}练习:随机生成8个数,存放在数组中
#include<iostream>
#include<cstdlib>
#include<ctime>
int* create(int size)
{
int num = 0;
int* p_num = (int*)malloc(size * sizeof(int));
if (p_num) {
for (num = 0; num < size; num++)
{
*(p_num + num) = rand() % 36 + 1;
}
}
return p_num;
}
int main()
{
int n = 8;
int* p_num = NULL, num = 0;
srand(time(NULL));
p_num = create(n);
if (p_num)
{
for (num = 0; num < n; num++)
{
std::cout << *(p_num + num) << " ";
}
free(p_num);
p_num = NULL;
}
std::cout << std::endl;
return 0;
}1.4 calloc
(C语言申请内存,C++也可使用,但是不建议)
calloc 函数是 C 语言标准库 <stdlib.h> 中的另一个用于动态内存分配的函数。与 malloc 不同,calloc 会同时分配内存并初始化这块内存为零。其原型如下:
void *calloc(size_t num, size_t size);参数说明:
num:要分配的元素数量。
size:每个元素的大小(以字节为单位)。
返回值:
如果内存成功分配,calloc 返回一个指向分配的内存区域的指针,且该区域的内容被初始化为零。
如果内存分配失败(例如,由于内存不足),calloc 返回 NULL。
使用 calloc 函数时,你通常不需要再手动将分配的内存初始化为零,因为 calloc 已经帮你完成了这个工作。这是 calloc 和 malloc 之间的一个主要区别。
#include<iostream>
#include<stdlib>
int main()
{
int* age = NULL;
int n = 0;
std::cout << "请输入人数:";
std::cin >> n;
age = (int*)calloc(n , sizeof(int));
if (age == NULL)
{
std::cout << "申请空间失败" << std::endl;
return -1;
}
//将申请到空间中
memset(age, 0, sizeof(int) * n);
for (int i = 0; i < n; i++) /*给数组赋值*/
{
age[i] = i + 1;
}
for (int i = 0; i < n; i++) /*打印数组元素*/
{
std::cout << age[i] << " ";
}
free(age);
age = NULL;
std::cout << std::endl;
return 0;
}1.5 realloc
(C语言申请内存,C++也可使用,但是不建议)
realloc 函数是 C 语言标准库 <stdlib.h> 中的一个用于调整之前分配的内存区域的大小的函数。如果之前的内存区域足够大,realloc 可能会简单地返回原指针;否则,它会分配一个新的、适当大小的内存区域,将原内存区域的内容复制到新区域(如果可能的话),并释放原内存区域。其原型如下:
void *realloc(void *ptr, size_t size);参数说明:
ptr: 指向之前通过 malloc、calloc 或 realloc 分配的内存区域的指针。如果是 NULL,则 realloc 的行为类似于 malloc。
size: 新的内存区域大小(以字节为单位)。
返回值:
如果内存成功重新分配,realloc 返回一个指向新的内存区域的指针。如果新区域与旧区域相同,则可能返回原指针。
如果内存重新分配失败(例如,由于内存不足),realloc 返回 NULL。此时,原内存区域不会被释放,仍然需要通过 free 函数来释放。
使用 realloc 函数时,需要注意以下几点:
原指针有效性: 在调用 realloc 之后,原指针 ptr 可能不再有效,因为 realloc 可能会移动内存块。因此,你应该总是使用 realloc 返回的新指针来访问内存。
内存释放: 如果 realloc 返回 NULL,原内存区域仍然有效,并且需要手动释放。否则,如果 realloc 成功,它会自动释放原内存区域(如果移动了的话)。
内存初始化: realloc 不会初始化新分配的内存区域。如果增加了内存大小,新分配的部分的内容是不确定的。
错误处理: 在调用 realloc 后,应该检查返回值是否为 NULL,以处理内存重新分配失败的情况。
#include <iostream>
#include <stdlib>
#include <string>
int main()
{
char* str = (char*)ma11oc(10 * sizeof(char)); // 初始分配 10 个字符的空间
if (str == NULL)
{
// 内存分配失败,处理错误
std::cout << "Memory allocation failed." << std::endl;
return -1;
}
strcpy(str, "Hello"); // 写入一些内容
// 尝试扩大内存区域以容纳更多内容
char* new_str = (char*)real1oc(str, 20 * sizeof(char));
if (new_str == NULL)
{
// 内存重新分配失败,处理错误
std::cout << "Memory allocation failed." << std::endl;
free(str);// 不要忘记释放原内存
return -1;
}
str = new_str; // 更新指针
strcat(str, "World!");
// 追加更多内容到新的内存区域
std::cout << str << std::endl;
// 释放内存
free(str);
return 0;
}在上面的示例中,我们首先分配了一个足够存放"Hello"的内存区域,然后使用 real1oc 来扩大这个区域以存放"Hello World"。注意,在调用 real1oc 后,我们更新了 str 指针以指向新的内存区域,并检查了 real1oc 是否返回 NULL。最后,我们不要忘记在不再需要内存时调用 free 函数来释放它。
1.6 new
(C++申请内存,C++特性)
C++动态内存分配使用 new,原理和malloc完全相同,表示申请一块相应类型相应长度的内存。
使用方法(分配1个某种类型的大小)
类型* 指针变量名 = new 类型/*[1]*/
int *p = new int/*[1]*/使用方法(分配一块某种类型的大小)
类型* 指针变量名 = new 类型[个数];
int* p1 = new int[10]; //分配1.7 delete
(C++释放内存,C++特性)
C++释放动态内存申请delete,原理和free完全相同,表示释放一块申请的区域。
使用方法(释放1个申请的空间)
delete 指针变量:
int *p = new int/*[1]*/;
delete /*[]*/p;使用方法(释放1块申请的空间)
delete [] 指针变量:
int* pi = new int[10]; //分配
delete []pi;练习:动态分配一个int类型大小区域
#include<iostream>
int main()
{
int* pi = new int/*[1]*/; //分配
*pi = 1234; //使用
delete/*[]*/pi; //释放
pi = NULL;
return 0;
}练习:动态分配一块内存,保存10个整数
#include<iostream>
using namespace std;
int main()
{
int* pi = new int;
*pi = 1234;
cout << *pi << endl;
delete pi; //防止内存泄露
pi = NULL; //防止脚指针
//new数组
int *parr = new int[10];
for(int i = 0; i < 10; i++)
{
parr[i] = i + 1;
cout << parr[i] << ' ';
}
cout << endl; //输出换行
delete[]_parr;
parr = NULL;
return 0;
}如果在使用new的时候不为这块内存初始化,则内容为随机。可以在new的时候为该块区域进行初始化。
对于单个元素
类型* 变量名 = new 类型(初值);对于多个元素
类型* 变量 = new 类型[元素个数][元素初值];实例
#include<iostream>
using namespace std;
int main()
{
int* pi = new int(1);
cout << *pi << endl;
delete pi; //防止内存泄露
pi = NULL; //防止野指针
//new数组
int* parr = new int[5] {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++)
{
cout << parr[i] << '';
}
cout << endl; //输出换行
delete[]_parr;
parr = NULL;
return 0;
}2. 二级指针
- C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
- 二级指针就是指向一个一级指针变量地址的指针。二级指针不可以代表二维数组
int arr[][]{3} = { 1, 2, 3, 4, 5, 6 };
//int** p = arr;//error指针变量 变量
地址 值
指针变量1 指针变量2 变量
地址1 → 地址2 → 值
在C++中,二级指针(double pointer)是一个指向指针的指针。换句话说,它是一个指针变量,其值是一个指针的地址。二级指针通常用于间接引用或修改指针变量的值。这在某些情况下,如动态内存分配、函数指针的使用以及指针数组的操作中非常有用。
练习
在这个例子中,我们首先创建了一个 int 型指针 ptr1,并用 new 为它分配了内存,同时初始化了一个值为42的 int。然后,我们定义了一个 int 型二级指针 ptr2,并将 ptr1 的地址赋给它。
通过 ptr2,我们可以间接地访问和修改 ptr1 所指向的值。这种间接引用在函数参数传递中尤其有用,允许函数修改调用者传递的指针变量。
需要注意的是,使用二级指针时,必须确保所有的内存分配都得到了适当的释放,以防止内存泄漏。在上述例子中,我们使用了 delete 来释放 ptr1 指向的内存。
#include <iostream>
int main() {
// 定义一个int型指针
int* ptr1 = new int(42);
// 定义一个int型二级指针
int** ptr2 = &ptr1;
// 通过二级指针访问原始指针所指向的值
std::cout << "Value of *ptr1: " << *ptr1 << std::endl;
std::cout << "Value of **ptr2: " << **ptr2 << std::endl;
// 修改原始指针所指向的值
**ptr2 = 99;
std::cout << "Value of *ptr1 after modification: " << *ptr1 << std::endl;
// 释放动态分配的内存
delete ptr1;
return 0;
}练习:使用数组指针存放数组的地址,使用二级指针来遍历指针数组,并解引用出数组元素
#include<iostream>
int main()
{
int arr[5] = { 1,3,5,7,9 };
int* p_arr[5] = { 0 };
for (int i = 0;i < 5;i++)
{
p_arr[i] = &arr[i];
}
for (int **pp_arr = p_arr; pp_arr < p_arr + 5; pp_arr++)
{
std::cout << "*pp_arr = " << * pp_arr << std::endl; //地址
std::cout << "**pp_arr = " << **pp_arr << std::endl; //数值
}
return 0;
}对于main函数参数的理解
int main(int argc, char* argv[])char* argv[]可以看作是一个二级指针,即char** argv
