赞
踩
目录
new和delete 堆区空间操作(对比malloc和free)
在g++中默认初始化成员变量为0,而A *pa2 = new A(5)初始化成员变量为5
- #include <iostream>
- using namespace std;
-
- class A
- {
- public:
- int m_num;
-
- public:
- A(){};
- A(int num) : m_num(num)
- {
- cout << "construct" << endl;
- }
- ~A()
- {
- cout << "disconstruct" << endl;
- };
- };
-
- int main(int argc, char const *argv[])
- {
- A *pa = new A;
- A *pa2 = new A();
- A *pa3 = new A(5);
-
- cout << "pa->m_num = " << pa->m_num << endl;
- cout << "pa2->m_num = " << pa2->m_num << endl;
- cout << "pa3->m_num = " << pa3->m_num << endl;
-
- delete pa;
- delete pa2;
- delete pa3;
- return 0;
- }
运行结果:
调用operator new()--malloc
调用了分配对象的构造函数
调用了分配对象的析构函数
调用operator delete()--free
new和malloc delete和free 没有区别
区别:
new 不用强制类型转换
new在申请空间的时候可以 初始化空间内容
malloc不会调用构造函数 free不会调用析构函数
new 会调用构造函数 delete调用析构函数
new调用有参构造
为了减少 malloc 调用的次数,可以考虑以下方法:
在C++中,内存管理是开发者的一项重要责任,也是容易出错的地方。开发者可能会遗忘释放已分配的内存,导致内存泄漏。重载new和delete可以帮助开发者更好地追踪和管理内存分配。通过在重载的new和delete操作符中插入日志或者调试语句,开发者可以监测和记录所有内存分配和释放的情况,从而检测内存泄漏。
例如,以下的代码展示了如何重载new和delete操作符来监测和追踪内存分配:
- void* operator new(size_t size) {
- void* p = malloc(size);
- std::cout << "Allocated " << size << " bytes at address " << p << std::endl;
- return p;
- }
-
- void operator delete(void* p) {
- std::cout << "Deallocated memory at address " << p << std::endl;
- free(p);
- }
在一些硬件平台和操作系统上,为了实现最优性能,数据需要按照某种特定的边界对齐。如果没有对齐,可能会导致性能下降,甚至运行错误。通过重载new和delete,我们可以为特定的类实现定制的内存对齐方式。
下面的代码演示了如何重载new和delete操作符来实现内存对齐:
- class Aligned {
- public:
- static void* operator new(std::size_t size) {
- void* p = std::aligned_alloc(alignof(Aligned), size);
- if (!p) {
- throw std::bad_alloc();
- }
- return p;
- }
-
- static void operator delete(void* p) {
- std::free(p);
- }
- };
在某些情况下,多个进程可能需要访问同一块内存区域。在这种情况下,可以通过重载new和delete操作符,实现在共享内存区域中分配和释放对象。
例如,以下的代码展示了如何通过重载new和delete来在共享内存中分配和释放对象:
- // 假设SharedMemoryManager是一个用于管理共享内存的类
- class SharedMemoryManager {
- public:
- void* allocate(size_t size);
- void deallocate(void* p);
- };
-
- class SharedMemoryObject {
- public:
- void* operator new(size_t size) {
- return SharedMemoryManager::allocate(size);
- }
-
- void operator delete(void* p) {
- SharedMemoryManager::deallocate(p);
- }
- };
在以上的例子中,SharedMemoryObject类的对象将会被分配在共享内存中,从而可以被多个进程访问。
全局的new和delete操作符可被重载以满足特定的需求,比如定制内存管理策略,或者为内存分配和释放添加自定义行为。要注意,这些全局重载将影响到整个程序的范围,包括标准库的容器等,所以在实践中应谨慎使用。
- void* operator new(size_t size) {
- // ... 实现代码
- }
-
- void operator delete(void* p) {
- // ... 实现代码
- }
operator new需要返回一个足够大,可以容纳请求内存大小的指针。如果内存分配失败,需要抛出std::bad_alloc异常。operator delete需要释放传入的指针指向的内存。
下面的代码将在全局的new和delete操作符中添加一些定制的行为。在分配和释放内存时,我们会打印一些信息到控制台,以便于跟踪内存的使用情况:
- #include <cstdlib>
- #include <iostream>
-
- void* operator new(size_t size) {
- void* p = std::malloc(size);
- if (!p) {
- throw std::bad_alloc();
- }
-
- std::cout << "Allocated " << size << " bytes at address " << p << std::endl;
- return p;
- }
-
- void operator delete(void* p) {
- std::cout << "Deallocated memory at address " << p << std::endl;
- std::free(p);
- }
以上代码演示了如何在全局的new和delete操作符中添加自定义的行为。这种方式在实际开发中可以帮助我们更好地理解和跟踪内存的使用情况。不过请注意,添加过多的日志可能会对性能产生影响,所以在生产环境中通常不会添加过多的日志信息。
对类的new和delete操作符进行重载允许我们为该类的对象提供定制的内存管理策略。这对于需要进行特殊内存管理的类来说特别有用,例如需要在共享内存中创建的对象,或者需要进行特殊对齐的对象。
对类的new和delete操作符进行重载的基本形式如下:
- class MyClass {
- public:
- static void* operator new(std::size_t size);
- static void operator delete(void* p);
- };
operator new需要返回一个足够大,可以容纳请求内存大小的指针。如果内存分配失败,需要抛出std::bad_alloc异常。operator delete需要释放传入的指针指向的内存。
假设我们有一个需要8字节对齐的类,我们可以通过重载new和delete操作符来满足这个要求:
- #include <cstdlib>
- #include <new>
-
- class Aligned {
- public:
- static void* operator new(std::size_t size) {
- void* p = std::aligned_alloc(8, size);
- if (!p) {
- throw std::bad_alloc();
- }
- return p;
- }
-
- static void operator delete(void* p) {
- std::free(p);
- }
- };
在这个例子中,我们使用了std::aligned_alloc函数来进行对齐的内存分配。如果分配失败,我们抛出std::bad_alloc异常。在operator delete中,我们简单地调用std::free来释放内存。
假设我们有一个需要在共享内存中创建的对象,我们可以通过重载new和delete操作符来实现:
- // 假设SharedMemoryManager是一个用于管理共享内存的类
- class SharedMemoryManager {
- public:
- static void* allocate(std::size_t size);
- static void deallocate(void* p);
- };
-
- class SharedMemoryObject {
- public:
- static void* operator new(std::size_t size) {
- return SharedMemoryManager::allocate(size);
- }
-
- static void operator delete(void* p) {
- SharedMemoryManager::deallocate(p);
- }
- };
在这个例子中,SharedMemoryObject类的对象将会在共享内存中创建和销毁。这允许我们在多个进程间共享这些对象。
放置new (placement new) 是一个特殊版本的new操作符,它允许程序员将对象创建在已经分配的内存上。换句话说,它允许我们"放置"一个新的对象在我们指定的、已经存在的内存位置上。
在已经分配的原始内存中初始化一个对象;
在普通的new操作中,首先会申请一块足够的内存,然后在这块内存上构造对象。但是在placement new中,内存必须已经存在,它只负责在指定的内存上构造对象。以下是一个使用placement new的例子:
- #include <new> // 需要包含这个头文件来使用placement new
-
- char buffer[1024]; // 预分配的内存
-
- int* p = new (buffer) int(123); // 在buffer上放置一个int对象
对于类,placement new最好在我们需要使用的类中重载,否则在类外重载会影响到其它类型分配空间
- #include <iostream>
- using namespace std;
-
- void *operator new(size_t size)
- {
- void *p = malloc(size);
- std::cout << "Allocated " << size << " bytes at address " << p << std::endl;
- return p;
- }
-
- void operator delete(void *p)
- {
- std::cout << "Deallocated memory at address " << p << std::endl;
- free(p);
- }
-
- class A
- {
- public:
- int m_num;
-
- public:
- A(){cout<<"default construct"<<endl;};
- A(int num) : m_num(num)
- {
- cout << "construct" << endl;
- }
- ~A()
- {
- cout << "disconstruct" << endl;
- };
- void *operator new(size_t size, void *p)
- {
- cout << "placement new" << endl;
- return p;
- }
- };
-
- int main(int argc, char const *argv[])
- {
- void *p = (void *)new char[sizeof(A)];
- A *pa = new (p) A();
-
- pa->m_num = 5;
- cout << *((int *)p) << endl;
- delete pa;
- return 0;
- }
由于placement new仅仅在已经分配的内存上创建对象,而不会分配内存,所以当不再需要这个对象时,我们需要手动调用该对象的析构函数来销毁对象:
p->~int(); // 手动调用析构函数
需要注意的是,这里只销毁了对象,但并没有释放内存。内存的释放需要根据实际的情况来处理。例如,如果这块内存是在栈上分配的,那么当退出作用域时会自动释放;如果是在堆上分配的,那么可能需要手动释放。
placement new的一个主要应用是当我们需要在特定的位置创建对象时,比如在已分配的堆内存上,或者在栈内存上,甚至在硬件指定的特定内存地址上。
此外,placement new也常用于实现自定义的内存池,内存池可以减少动态分配和释放内存带来的开销。我们可以预先分配一大块内存,然后通过placement new在这块内存上创建对象,从而提高内存使用的效率。
如果知道了硬件设备的地址,想要将那个硬件设备与一个C++类直接关联,那么定位new就非常有效了
通过将placement new可以将C++的类之间关联到硬件设备上,操作该对象就相当于操作硬件
如下面程序所示,假如操作STM32的GPIOB->GPIO_Pin1,假设GPIO_Pin1的存储器映射地址为0x00005600。由于类A的对象pa的地址就是对象pa内首个字段m_num的地址,因此操作m_num就相当于操作地址0x00005600。
- #include <iostream>
- using namespace std;
-
- class A
- {
- public:
- int m_num;
-
- public:
- A(){};
- A(int num) : m_num(num)
- {
- cout << "construct" << endl;
- }
- ~A()
- {
- cout << "disconstruct" << endl;
- };
- };
-
- int main(int argc, char const *argv[])
- {
- //访问硬件:将C++的类之间关联到硬件设备上,操作该对象就相当于操作硬件
- //单片机/STM32/ARM9:操作硬件的物理地址就相等于操作该硬件
- //GPIOB->GPIO_Pin1
- void *p = (void*)0x00005600;
- A *pa = new(p) A();
- pa->m_num = 1;//拉高电平
- pa->m_num = 0;//拉低电平
-
- return 0;
- }
基础库一般为了效率要先预分配内存,然后在预分配的内存上执行构造,几乎所有的C++容器都用到了定位new
优先级:内部new、全局new
可以重载很多版本的operator new,只要每个版本参数不同就行,但是第一个参数是固定的,都是size_t,表示要new对象的sizeof值
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。