欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > C++内存管理

C++内存管理

2025/6/10 13:50:46 来源:https://blog.csdn.net/benjiangliu/article/details/148460990  浏览:    关键词:C++内存管理

C++内存管理

  • (一)C++中内存分布
    • 一、C++中内存划分图
    • 二、栈区
      • 情形一:当变量是函数内部的局部变量
      • 情形二:当变量是指针且指向堆内存
    • 三、堆区
    • 四、静态区
    • 五、常量区
      • 1、常量区存储哪些东西?
        • 1、字符串字面量
        • 2、全局\静态的const变量
        • 3、注意:局部非静态 const 变量
      • 2、常量区的特性
        • 1、只读性
        • 2、共享机制
    • 六、一道经典例题
  • (二)C++中内存管理
    • 一、回顾C语言中开辟动态存储内存
    • 二、new和delete
      • 1、基本语法
        • 1、基本内置类型的动态内存管理
        • 2、类类型的动态内存管理
          • 1、例1
          • 2、例2
          • 3、例3
      • 2、C++中的异常
      • 3、new和delete 的工作流程
      • 4、malloc与new,free和delete的区别
      • 5、定位new
        • 1、什么是定位new
        • 2、定位new的使用

(一)C++中内存分布

一、C++中内存划分图

在这里插入图片描述
这个是C++内存分布图,目前只需要了解栈区、堆区、静态区(数据段)、常量区(代码段)

二、栈区

栈区:栈内存由系统自动进行分配和释放。当进入函数时,系统会自动为函数内的局部变量分配内存;函数执行完毕后,这些内存会被自动回收。
栈区主要存储以下两类数据:
函数的局部变量,包括基本数据类型(如 int、float 等)和对象。函数调用的上下文信息,例如返回地址、调用参数等。

情形一:当变量是函数内部的局部变量

#include <iostream>int func()  // a b 是函数func的局部变量
{int a = 10;         // 在栈上分配内存double b = 3.14;    // 在栈上分配内存std::cout << a << " " << b << std::endl;return a; //  注意此时返回的是a的值拷贝,是传值返回// 在函数调用结束后,a和b都会被自动释放
} // 函数结束时,a和b的内存自动释放int main() // x y是main函数的局部变量
{int y = func();             // 调用func函数int x = 5;          // 在栈上分配内存{int y = 10;     // 在栈上分配内存std::cout << x << " " << y << std::endl;} // 代码块结束,y的内存自动释放std::cout << x << std::endl;return 0;
} // 函数结束,x的内存自动释放

情形二:当变量是指针且指向堆内存

int* func() 
{int* a = new int(10); // 开辟一个int型的内存空间,并赋值为10// 注意a是func函数的局部指针变量// 所以a本身在栈区,但指向的内存(10)在堆区return a;              // 返回指针a(值拷贝),指向的堆内存不会被释放// 因为指针变量a本身是func函数的局部变量,所以它存在栈上// 所以func函数运行结束后,指针变量a被销毁释放// 但是指针变量a中存储的地址是存储在堆区上的// 所以开辟的内存空间依然存在,未被销毁,这是内存泄漏// 需要手动调用delete来释放。
}

三、堆区

堆区:堆区(Heap)是程序运行时内存的重要组成部分,用于动态分配内存。由程序员通过new/new[]分配内存,delete/delete[]释放内存。若忘记释放,会导致内存泄漏(Memory Leak),之前例子里的func函数就存在内存泄漏的问题。

int main()
{int* ptr = (int*)malloc(sizeof(int)*10);// 还是这个例子// ptr是main函数的局部变量在栈区上// ptr内的地址即*(ptr)在堆区上
}

四、静态区

静态区:静态区(Static Storage Duration)是内存中的一个特殊区域,用于存储具有静态存储期的变量。这些变量的生命周期贯穿整个程序运行期间,它们在程序启动时被分配内存,在程序结束时才释放。

#include <iostream>int globalVar = 10; //全局变量
static int Static_GlobalVar = 10; // 静态全局变量void func() 
{static int staticLocalVar = 0;  // 静态局部变量仅仅在第一次被调用func函数时进行初始化,后续调用会跳过初始化语句staticLocalVar++; // 因为staticLocalVar是静态变量,存储在静态区中// 不像其他的局部变量一样,存储在栈区中,函数结束后,函数栈帧被销毁,局部变量一样也被销毁了// 但是 staticLocalVar静态变量 在func()函数结束后,依然存在未被销毁,再次调用func()函数staticLocalVar的值依然保留上次的值// 换句话说,静态变量的声明周期是整个程序运行期间,不受某一个具体的函数生命周期限制cout << staticLocalVar << endl;  // 每次调用递增
}int main()
{func();// 第一次进入时定义并初始化staticLocalVar = 0// 经过++ 后,staticLocalVar = 1;// 然后打印 staticLocalVar 输出为 1func();// 第二次进入时,staticLocalVar不再进行初始化,由于静态属性staticLocalVar的值出函数后仍然为1// 经过++ 后,staticLocalVar = 2;// 然后打印 staticLocalVar 输出为 2func();// 第三次进入时,staticLocalVar不再进行初始化,由于静态属性staticLocalVar的值出函数后仍然为2// 经过++ 后,staticLocalVar = 3;// 然后打印 staticLocalVar 输出为 3
}

注意:
1、在func函数之前定义的 int globalVar = 10; //全局变量
这个全局变量,也属于静态变量,因为他与static修饰的静态变量一样,无论在哪个函数中去调用修改,再另外的函数中其值一样被改变了。
2、静态局部变量的关键特性是 “一次初始化,多次调用共享值”。

五、常量区

常量区:常量区(Constant Area)是静态区(Static Area)的一个子区域,用于存储编译时可确定值的字面常量和全局 / 静态的 const 变量。它的特点是只读,程序运行期间无法修改其中的数据。

1、常量区存储哪些东西?

1、字符串字面量

1、字符串字面量:例如 “Hello, World!”、“abc” 等,存储为连续的字符数组(末尾自动添加 \0)。
2、整型 / 浮点型字面量:例如 10、3.14、‘a’ 等(这类常量可能直接嵌入代码或存储在常量区,不同编译器处理方式不同)。
3、布尔字面量:true 和 false。

2、全局\静态的const变量

被 const 修饰且具有静态存储期的变量(如全局 const 变量、静态全局 const 变量、静态局部 const 变量),会存储在常量区。

const int globalConst = 10;       // 全局 const 变量 → 常量区
static const int sGlobalConst = 20; // 静态全局 const 变量 → 常量区
void func() 
{static const int sLocalConst = 30; // 静态局部 const 变量 → 常量区
}
3、注意:局部非静态 const 变量

普通的局部 const 变量(非 static 修饰)存储在栈区,因为它们的生命周期与函数栈帧绑定,不属于静态存储期变量。

void func() 
{const int localVar = 40; // 局部 const 变量 → 栈区static const int localVar_1 = 10; // 局部静态变量存储在静态区中,因为他被static关键字修饰
}

2、常量区的特性

1、只读性

常量区的内容在程序运行期间被标记为只读,试图修改会导致运行时错误(如段错误)。

const char* str = "Hello";
str[0] = 'h'; // 编译时不报错,但运行时会崩溃(尝试修改常量区的字符串)
2、共享机制

编译器可能会对相同的字面常量进行合并优化,避免重复存储。例如:

const char* str1 = "abc";
const char* str2 = "abc";
// 编译器可能让 str1 和 str2 指向常量区中同一个 "abc" 地址
printf("%p\n",str1);
printf("%p\n",str2); // vs2022下两次打印结果一样,原因:编译器会合并相同的字符串字面量

六、一道经典例题

在这里插入图片描述

#include <stdlib.h>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";         // char2局部数组在栈区// 注意这里的意思是开辟一个字符数组// 字符数组内存放“a b c d \0”const char* pChar3 = "abcd";// pchar3 栈区 它只是一个局部指针变量,pchar3中存放的是地址// *pchar3 常量区, *(局部变量中存的是地址) 是一个常量区的地址// 这个地址指向常量区int* ptr1 = (int*)malloc(sizeof(int) * 4);int* ptr2 = (int*)calloc(4, sizeof(int));int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);// ptr1~3都 在是局部指针变量,存储在栈区// *ptr1~3 都指向堆区free(ptr1);free(ptr3);
}

在这里插入图片描述

(二)C++中内存管理

一、回顾C语言中开辟动态存储内存

#include <stdio.h>int main()
{int* ptr = (int*)malloc(sizeof(int)*10);// 使用malloc开辟4 *10 = 40个字节的内存free(ptr); // 释放ptr指向内存空间ptr = NULL; // 将ptr置为空// C语言中malloc和free是成对使用的
}

二、new和delete

new 和 delete 是用于动态内存分配和释放的运算符,它们是 C++ 管理堆内存的核心机制。
在C++ 中使用 new + 类型 去向内存申请内存

1、基本语法

1、基本内置类型的动态内存管理
int main()
{// 动态分配单个对象int* ptr1 = new int;// 向内存申请一个int类型空间大小的内存,即4个字节int* ptr2 = new int(10);// 向内存申请一个int类型空间大小的内存,即4个字节// 并将其初始化为 10// 动态分配数组int* ptr3 = new int[10];// 向内存申请10个int类型大小的内存,即4*10=40个字节int* ptr4 = new int[10]{1,2,3,4,5};// 向内存申请10个int类型大小的内存,并将前5个元素赋值为1,2,3,4,5,delete ptr1; // 释放ptr1指向的内存delete ptr2; // 释放ptr2指向的内存// 注意释放指向数组的地址,必须使用delete[]delete[] ptr3;delete[] ptr4;return 0;
}
2、类类型的动态内存管理

1、类类型在执行 new + 类类型 时,会先调用构造函数,如果有默认构造函数可以不传参,如果没有默认构造函数数则必须传参,进行初始化。
2、程序运行结束前,执行 delete + 指针变量 时,会先调用类类型的析构函数,先释放类的空间

1、例1
#include <iostream>
#include <stdlib.h>using namespace std;class A // 定义一个类
{
public:A(int a): _a(a){cout << "A():" << this << endl;}A(const A& a){_a = a._a;cout << "A(const A& a)" << endl;}~A(){cout << "~A():" << this << endl;}void print(){cout << _a << endl;}
private:int _a;
};int main()
{// C语言方式:仅分配内存,不调用构造函数A* p1 = (A*) malloc(sizeof(A))// 开辟一个A类大小的内存空间,并赋值给p1// 只开辟了空间// C++方式:分配内存并调用构造函数A* p2 = new A(99);// 开辟一个A类大小的空间,并初始化成员变量_a = 99 // 既开辟了空间,又初始化了成员变量free(p1);// 错误:free不会调用析构函数,可能导致资源泄漏// 正确做法:先显式调用析构函数,再释放内存// p1->~A();  // 显式调用析构函数(不推荐这种方式)p2->print();// 99delete p2;// 输出结果:// A():000002231F418630 ,这条语句:A* p2 = new A(99);会调用A类的构造函数// ~A() : 000002231F418630 ,这条语句:delete p2; 会调用A类的调用析构函数
}
2、例2
class A
{
public:A(int a): _a(a){cout << "A():" << this << endl;}A(const A& a){_a = a._a;cout << "A(const A& a)" << endl;}~A(){cout << "~A():" << this << endl;}void print(){cout << _a << endl;}
private:int _a;
};void test3()
{// 开辟多个空间,并初始化值// 第一中方法:利用拷贝构造A a1(1), a2(2), a3(3); // 先创建对象A* p1 = new A[3]{ a1,a2,a3 }; // 利用拷贝构造将a1,a2,a3的值传入new出的新空间内// 输出结果://A() : 000000CBC90FF2E4//A() : 000000CBC90FF304//A() : 000000CBC90FF324// 第二种方法:利用匿名对象A* p2 = new A[3]{ A(1),A(2),A(3) };// 编译器直接将构造+拷贝构造优化为了 直接构造// 实际输出结果://A() : 00000241F8CA5AB8//A() : 00000241F8CA5ABC//A() : 00000241F8CA5AC0// 第三中方法: 直接隐式转换A* p3 = new A[3]{ 1,2,3 };// 最方便!因为编译器直接将构造+拷贝构造 优化成了 直接构造// 实际输出结果://A() : 00000241F8CA5E28//A() : 00000241F8CA5E2C//A() : 00000241F8CA5E30delete[] p1;delete[] p2;delete[] p3;// 最后输出三个地址对应对象的析构://~A() : 00000241F8CA5CA0//~A() : 00000241F8CA5C9C//~A() : 00000241F8CA5C98//~A() : 00000241F8CA5AC0//~A() : 00000241F8CA5ABC//~A() : 00000241F8CA5AB8//~A() : 00000241F8CA5E30//~A() : 00000241F8CA5E2C//~A() : 00000241F8CA5E28
}
3、例3
struct ListNode
{int _val;ListNode* _next;ListNode(int val = 0):_val(val), _next(nullptr){}
};
// 复习:C++中的结构体具有类的所有特性,只是所有的成员函数和变量默认都是public类型的
// 所有结构体也有初始化列表void test4()
{// 使用C++的方法创建一个单链表// 不用写SLTBuyNodeListNode* p1 = new ListNode(1);ListNode* p2 = new ListNode(2);ListNode* p3 = new ListNode(3);ListNode* p4 = new ListNode(4);p1->_next = p2;p2->_next = p3;p3->_next = p4;ListNode* pcur = p1;while (pcur != nullptr) // 对链表进行遍历{cout << pcur->_val << endl;pcur = pcur->_next;}}

2、C++中的异常

1、在C语言中,如果内存开辟失败会返回空指针NULL

#include <stdio.h>
int main()
{int* ptr = (int*)malloc(sizeof(int)* 10000);if(ptr == NULL) // 如果内存返回失败,会返回空指针{perror("maolloc fail~"); // 打印错误原因exit(-3); //  退出程序,返回-3}
}

2、在C++ 中因为使用new和delete进行内存管理,而之前我们已经学过了new的底层是使用operator new吗,但是本质上实际也是通过malloc来申请空间,异常机制统一处理所有错误(包括构造函数失败、深层嵌套调用错误),无需层层传递错误码,而且强制开发者显式处理错误,减少因疏忽导致的未检查错误。

#include <iostream>
#include <stdlib.h>
using namespace std;#define _DEBUGint main()
{try{for (size_t i = 0; i < 100000; i++){
#ifdef _DEBUG // 调试开关if (i == 494){int x = 0; // 使用手动断点的方式,让程序运行到第494次}
#endifint* pa = new int[1024 * 1024]; // 4M 这里会异常,直接跳到catch语句,输出异常原因if (pa){cout << i << "->" << pa << endl;}else{break;}}}catch (const exception& e){cout << e.what() << endl; // bad allocation}// 异常退出结束,因为new失败了以后,不返回空指针// 当使用 new 分配内存时,如果系统无法提供足够的内存(如物理内存不足、虚拟地址空间耗尽),// 会抛出 std::bad_alloc。// 这里和malloc失败以后返回空指针是本质的区别// 换句话说,我们在在C++中是不去检查返回值的,直接捕获异常就可以了return 0;
}

在 C++ 中,try-catch 块必须成对出现,用于捕获和处理特定类型的异常。当异常被抛出时,程序会跳转到最近的匹配 catch 块执行,本例子中try-catch 与 std::exception 配合使用。
其基本语法与工作原理

try 
{// 可能抛出异常的代码
} 
catch (const std::exception& e) 
{// 捕获所有派生自 std::exception 的异常std::cerr << "捕获异常: " << e.what() << std::endl;// e.what(),中存放的是异常代码
}

3、new和delete 的工作流程

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是
系统提供的全局函数,new在底层调用operator new全局函数来申请空间
,delete在底层通过
operator delete全局函数来释放空间。

/*
operator new:该函数实际通过malloc来申请空间,
当malloc申请空间成功时直接返回;申请空间失败,尝试执行空 间不足应对措施。
如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid *p;while ((p = malloc(size)) == 0)if (_callnewh(size) == 0){// report no memory// 如果申请内存失败了,这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}
return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{_CrtMemBlockHeader * pHead;RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));if (pUserData == NULL)return;_mlock(_HEAP_LOCK); /* block other threads */__TRY/* get a pointer to memory block header */pHead = pHdr(pUserData);/* verify block type */_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg( pUserData, pHead->nBlockUse );__FINALLY_munlock(_HEAP_LOCK); /* release other threads */__END_TRY_FINALLY
return;
}/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果
malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

  1. 操作符 new 的基本流程
    内存分配:new操作符会调用operator new函数,该函数的任务是分配一块足够大的内存空间。
    对象构造:内存分配完成后,会调用相应的构造函数,在这块已分配的内存上构造对象。
    返回指针:对象构造好之后,new操作符会返回一个指向该对象的指针。

  2. operator new 函数
    标准实现:在 C++ 标准库中,operator new有其标准的实现,它是基于malloc函数来完成内存分配工作的。
    重载可能性:operator new支持被重载,借助重载这个函数,我们能够自定义内存分配的具体行为。

#include <iostream>
using namespace std;class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}A(const A& a){_a = a._a;cout << "A(const A& a)" << endl;}~A(){cout << "~A():" << this << endl;}void print(){cout << _a << endl;}
private:int _a;
};int main()
{A* p1 = new A[10];// 在new A[10]这个语句执行过程中,会向内存申请40个字节的内存空间,因为A类占用一个int类成员变量 4*10 = 40// 但是new在像内存申请的时候,除去40个字节的内存外,另外还多申请4个字节或8个字节(看32/64位系统)的内存,存放开辟内存的大小// 本例子中为0a即10个单元// new A[10] 会申请 10 * sizeof(A) = 40 字节的内存用于存储对象// 此外,系统会在对象内存之前额外存储数组长度(通常4/8字节)// 本例中实际分配的内存可能为 4(长度) + 40(对象) = 44 字节// 并按系统要求对齐(如8字节对齐,最终分配48字节//0x00B3B8C8  0a 00 00 00  ....  这里的0a代表//0x00B3B8CC  00 00 00 00  ....  p1 =  0x00B3B8CC 内存开辟从这里开始//0x00B3B8D0  00 00 00 00  ....//0x00B3B8D4  00 00 00 00  ....//0x00B3B8D8  00 00 00 00  ....//0x00B3B8DC  00 00 00 00  ....//0x00B3B8E0  00 00 00 00  ....//0x00B3B8E4  00 00 00 00  ....//0x00B3B8E8  00 00 00 00  ....//0x00B3B8EC  00 00 00 00  ....//0x00B3B8F0  00 00 00 00  ....delete[] p1;// 在执行delete[] p1的过程中// 程序首先会找到 0x00B3B8C8,检查内存存储的值,这个值代表后续需要释放多少个内存单元// 并依次调用 10 次析构函数// 然后从p1中存储的 0x00B3B8CC 地址开始释放10 * int 字节的内存空间// delete p1// 错误写法(未定义行为):// 1. 仅对第一个对象(p1[0])调用析构函数// 2. 但释放整个内存块(包括所有10个对象的内存)// 结果:其余9个对象的析构函数未被调用,若对象持有资源(如文件句柄、堆内存),会导致资源泄漏// 注意:内存管理器仍会释放整个块,不会造成"部分内存未释放"的情况// 导致系统报错A* p2 = new A[10];delete[] p2;// 如果此时将A类的析构函数注释掉// 编译器会自动优化// 不再生成p2前面那个存储数组大小的内存单元// 因为编译器认为没有内存单元需要被析构了// 例如:日期类这种return 0;
}

注意:new + 类的情况下,开辟好内存单元后,首先调用构造函数并完成初始化,然后执行程序,在**delete[]**时会对

4、malloc与new,free和delete的区别

malloc / free和new / delete的共同点是:
都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,
    //如果是多个对象,[]中指定对象个数即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常

功能:
6. 申请自定义类型对象时,malloc / free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放

5、定位new

1、什么是定位new

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new(place_address) type(initializer - list)
place_address必须是一个指针,initializer - list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
定位new的运用场景在内存池、进程池

2、定位new的使用
#include <iostream>using namespace std;class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};// 定位new/replacement new
int main()
{// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行A* p1 = (A*)malloc(sizeof(A));// 上面这个程序,我们想内存申请了一个A类大小的空间,但是没有进行构造// 核心原因:我们没有使用new,因为new是operator new + 构造的,而我们使用的是malloc// p1->A(10); // 现在我们想去构造p1所指向的内存空间,p1->A(10); 这种写法不被支持// C++只支持通过指针去调用析构函数,而不支持调用构造函数new(p1)A(1);  // 为了解决上面的那个问题,C++引入了定位new,去对指针所指向的类对象进行初始化(调用构造)// 通过 new(指针) 类名(参数) 的语法,在已分配的原始内存(如 malloc 分配的内存)上显式调用构造函数,完成对象初始化。// 注意:如果A类的构造函数有参数时,此处需要传参p1->~A();// C++支持指针调用析构,但是不支持指针调用构造free(p1);// 不通过delete p1去释放p1指针指向的类对象// 因为之前已经调用了析构函数了A* p2 = (A*)operator new(sizeof(A));// operator new(sizeof(A)) 调用全局 operator new 函数,分配一块大小为 sizeof(A) 的原始内存,并返回 void* 指针。// 之前说了new是 operator new + 构造函数// 所以operator new是只申请内存 不构造// 需手动使用 placement new 初始化对象。new(p2)A(10);// 这里使用定位new对p2指针指向的对象进行构造p2->~A();// 显示调用析构函数operator delete(p2);// 该函数接受一个指向已分配内存的 void* 指针,并释放该内存区域,与operator new成对使用// 因为operator new 不调用构造函数,所以operator delete也不调用析构函数// operator只进行内存释放,不调用析构函数(需手动调用析构函数)return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词