C++内存如何管理?

C/C++内存分布

在程序运行时的五大内存区域

1,栈区

内容:1,函数的形参;2,函数内的局部变量;3,函数调用的返回地址

特点:1,自动分配、自动释放(无需手动管理);2,空间小(默认几 MB),速度极快;3, 遵循后进先出(LIFO);4,函数执行完,栈内存自动回收

2,堆区

内容:手动申请的内存

申请方式:

C:malloc / calloc / realloc

C++:new

释放方式:

C:free()

C++:delete

特点:1,空间大;2,手动管理,不释放会造成内存泄漏;3,分配速度慢

生命周期:手动申请 → 手动释放(或程序结束)

3,数据段(全局,静态)

内容:1,全局变量;2, static 修饰的静态变量(全局 / 局部静态)

特点:1,自动初始化为 0 / 空值;2,程序结束由系统自动释放;3,全局共享,整个程序都能访问

例如:

java 复制代码
int g_a = 10;        // 全局变量 → 全局区(已初始化)
static int s_b = 20; // 静态变量 → 全局区

void func() {
    static int s_c = 30; // 局部静态 → 仍在全局区
}
4,常量区

存放内容:字符串常量、const 修饰的全局常量

特点:1,只读,修改会直接程序崩溃;2,相同常量只存一份(节约内存)

生命周期:整个程序运行期间

例如:

java 复制代码
const char* str = "Hello World"; 
// "Hello World" 存放在常量区
// str 是指针变量,存在栈区
5,代码区

内容:编译后的二进制机器指令(函数体代码)

特点:1,只读(防止程序被意外修改);2, 共享(多次运行同一个程序,共用一份代码)

生命周期:整个程序运行期间都存在

指针的大小如何计算?

C/C++ 中,指针变量的大小,只和操作系统位数有关,和它指向什么类型无关。原因是指针存放的是内存地址,不管指向声明类型,地址本身的长度是固定的。

系统 所有指针(int*,char*,void*...)大小
32位系统 4字节
64位系统 8字节

注意:

1,数组名≠指针:只有当数组名作为函数参数时,才会退化为指针。

2,strlen 必须保证指针指向的字符串有 \0 结束,否则会越界访问,结果不可控

相关小知识:

java 复制代码
int** heap_ptr = (int**)malloc(sizeof(int*));
分析:
·int *:整形指针,int**:二级指针
·sizeof(int*):表示一个一级指针的大小,32位系统是4,64位系统为8
·(int**) :强制类型转换,malloc 返回 void*强转为 int**,
意思是:把这个堆地址,当成存放一级指针 (int*)的地址来使用
·heap_ptr 是二级指针变量                    
全局普通变量【全局:定义在所有函数外面】与全局静态变量有什么区别?

全局普通变量:在所有文件中可见,其他文件可以用extern+变量类型+变量名;就能直接引用

全局静态变量:只在当前源文件可见

注意:在同一个工程项目中,如果两个源文件都定义全局普通变量:int a;会直接报错,如下图所示,在两个完全不同的工程项目中就不会影响;全局静态变量就没问题

变量的创建与析构顺序?
java 复制代码
using namespace std;
//创建四个类,用于查看变量创建顺序与析构顺序
class A {
public:
	A() {
		cout << "A" << endl;
	}
	~A() {
		cout << "~A" << endl;
	}
private:
	int _a;
};
class B {
public:
	B() {
		cout << "B" << endl;
	}
	~B() {
		cout << "~B" << endl;
	}
private:
	int _b;
};
class C {
public:
	C() {
		cout << "C" << endl;
	}
	~C() {
		cout << "~C" << endl;
	}
private:
	int _c;
};
class D {
public:
	D() {
		cout << "D" << endl;
	}
	~D() {
		cout << "~D" << endl;
	}
private:
	int _d;
};
//全局变量
C c;
int main() {
	A a;
	B b;
	static D d;
	return 0;
}
//查看变量创建顺序与析构顺序。
注意:局部静态变量在函数结束时最后析构。
计算拷贝构造的次数?
java 复制代码
class Widget { /* ... */ }; // 假设已实现拷贝构造函数

Widget f(Widget u)//拷贝次数为1	//拷贝次数为5
{
    Widget v(u); //拷贝次数为2     //拷贝次数为6				//  拷贝构造 v
    Widget w = v;  //拷贝次数为3   //拷贝次数为7					//  拷贝构造 w
    return w;  //拷贝次数为4       //拷贝次数为8					// 返回值优化相关
}

int main() {
    Widget x;                     //  默认构造 
    Widget y = f(f(x));  //拷贝次数为9         // 嵌套调用 + 拷贝构造
}

解析:考虑系统优化时,

优化时机 :仅发生在函数传参函数返回这两个环节。

拷贝次数为4和拷贝次数为5合并,拷贝次数为8和拷贝次数为9合并,所有共7次。

c和c++中内存管理方式与区别

malloc与new的区别:malloc只申请空间,new包含申请空间+构造函数初始化

释放空间时,free只释放空间,delete包含释放空间还调用了析构函数

java 复制代码
#include<iostream>
using namespace std;

//int main() {
//	int* p = new int[3](1);//不可以写成这样,编译器直接报错了
//	return 0;
//}
//C++和c中内存管理方式
//int main() {
	//	//C函数
	//	int* p1 = (int*)malloc (sizeof(int));
	//	int* p2 = (int*)malloc(sizeof(int) * 10);
	//	//释放
	//	free(p1);
	//	free(p2);
	//	//c++操作符
	////	int* p3 = new int;//开辟一个空间
	//	int* p5 = new int(10);//开辟一个空间,初始化为10
	//	int* p4 = new int[10];//开辟10个空间
//int* p = new int[3](1);//不可以写成这样,编译器直接报错了
	//	//释放
	//	delete p5;
	//	delete[] p4;

//	return 0;
//}
//malloc与new的区别:malloc只申请空间,new包含申请空间+构造函数初始化
//释放空间时,free只释放空间,delete包含释放空间还调用了析构函数
class A {
public:
	A(int a = 0)
		:_a(0){
		_a = a;
		cout << "A" << endl;
	}
	~A() {
		cout << "~A" << endl;
	}
private:
	int _a;
};
int main() {
	int* p1 = new int;
	int* p2 = (int*)malloc(sizeof(int));
	A* p3 = (A*)malloc(sizeof(A));
	A* p4 = new A;
	//释放
	free(p3);
	delete p4;
	
	return 0;
}

operator new与operator delete函数

申请资源:

1,operator new ==> 底层调用 malloc+失败抛出异常:bad allocation

2,new ==> operator new + 构造函数

3,new 比起malloc不一样的地方:1.调用构造函数初始化,2.失败了抛出异常

4,delete与free不一样的地方:delete会调用析构函数清理资源

5,operator delete和free:作用相似:都是只释放原始内存,不调用析构;但不是完全等同:operator delete 是 C++ 标准库函数,可以被重载;free 是 C 库函数,不能重载。底层默认 operator delete 内部确实调用 free,但语法、可扩展性不一样,不能说完全没区别。

举例:

java 复制代码
class A {
public:
	//构造函数
	A(const int& a = 0) 
	:_a(a){
		cout << "A()" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
private:
	int _a;
};
int main() {
	A* p1 = (A*)malloc(sizeof(A));//只分配内存,不调用构造函数,控制台 不会输出 A()
	//A* p2 = new A;
	A* p3 = (A*)operator new(sizeof(A));//底层调用 malloc 分配内存,只分配内存,不调用构造函数,失败会抛异常,这里大小正常,不会抛
	//operator new 和malloc的区别是什么?
	//结果:使用方式都是一样的,处理错误的方式不一样

	//为什么会出现下面的结果?这是一个值得思考的问题?
	void* p4 = malloc(1024 * 1024 * 1024 *1);//0166B040
	cout << p4 << endl;
	void* p5 = malloc(1024 * 1024 * 1024 *2);//00000000
	cout << p5 << endl;
	void* p6 = malloc(1024 * 1024 * 1024 *3);//00000000
	cout << p6 << endl;
	void* p7 = malloc(1024 * 1024 * 1024 *4);//012F1900
	cout << p7 << endl;



	size_t size = 2;
	void* p8 = malloc(size * 1024 * 1024 * 1024);//2G
	cout << p8 << endl;//00000000,失败返回0
	//void* p9 = operator new(size * 1024 * 1024 * 1024);
	//cout << p9 << endl;//失败抛出异常

	try {
		void* p9 = operator new(size * 1024 * 1024 * 1024);
		cout << p9 << endl;

	}
	catch (exception& e) {
		cout << e.what() << endl;//bad allocation
	}
	return 0;
}
用operator new申请空间,我们想要调用构造函数时的调用方法
java 复制代码
//使用operator new申请空间,我们想要调用构造函数时的调用方法
//typedef unsinged int size_t;
class A {
public:
	//构造函数
	A(const int& a = 0)
		:_a(a) {
		cout << "A()" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
private:
	int _a;
};
int main() {
	A* p10 = (A*)operator new (sizeof(A));
	new(p10)A(10);
	p10->~A();
	operator delete(p10);
	return 0;
}

注意:

new /operator new /malloc 最终都是去 堆 (heap) 上申请内存

malloc 和 new 的区别

1.初始化与构造:new 会调用构造函数完成对象初始化;malloc仅分配内存,不会调用构造函数。

2.失败处理:new 内存分配失败时抛出异常;malloc 失败时返回 0

3.本质不同:malloc 是 C 标准库提供的库函数;new 是 C++ 内置操作符(关键字)。

4.使用方式:malloc 需要手动指定分配的字节数,返回值是无类型的 void*,必须强转才能使用;new 后面直接跟数据类型 / 对象,无需计算字节数,返回值是对应类型的指针,无需强转。

5.配套释放:new 分配的内存必须用delete释放(会调用析构函数);malloc 分配的内存必须用free释放。

内存泄漏

定义:

程序中已经不再使用的动态分配内存,因为忘记释放、释放逻辑错误等原因,导致内存一直被占用,就是内存泄漏。

c/c++与Java的区别:

内存泄漏是所有手动管理内存的语言(C/C++) 都存在的问题,不是 C++ 独有的。

Java 等语言:拥有自动垃圾回收机制(GC),对象不再被引用时会自动回收内存,不需要手动释放,几乎不会出现内存泄漏。

内存泄漏的危害
  1. 长期运行的服务 / 程序(如服务器、后台程序),泄漏会导致可用内存越来越少,最终程序运行变慢、卡顿甚至崩溃。

  2. 内存资源有限的设备(嵌入式、单片机、移动端),少量泄漏也会快速耗尽内存,导致程序异常。

相关推荐
forEverPlume1 小时前
SQL如何统计分组内不重复值的数量_COUNT与DISTINCT结合应用
jvm·数据库·python
极创信息1 小时前
信创领域五种主流CPU架构(X86 / ARM / RISC-V / MIPS / LoongArch)
java·arm开发·数据库·spring boot·mysql·软件工程·risc-v
向阳是我1 小时前
Flutter Android 编译错误修复:JVM Target Compatibility 不一致问题记录
android·jvm·flutter
_日拱一卒1 小时前
LeetCode:146LRU缓存
java·开发语言
StockTV1 小时前
韩国股票实时数据 KOSPI(主板)和 KOSDAQ(创业板)的实时行情、K 线及指数数据
java·开发语言·算法·php
2501_901200531 小时前
PHP源码部署需要多大硬盘空间_PHP项目存储空间估算方法【方法】
jvm·数据库·python
Java成神之路-1 小时前
面试题:SpringMVC执行流程(视图版+前后端分离版)
java·springmvc
无敌秋1 小时前
C++ 单例模式
c++·单例模式