C++ 应用程序,使用 memory 的途径

image.png

C++ memory primitives

分配释放类属可否重载
malloc()free()C 函数不可
newdeleteC++ 表达式不可
::operator new()::operator delete()C++ 函数
allocator<T>::allocate()allocator<T>::deallocateC++ 标准库可自由设计并予以搭配任何容器
void *p1 = malloc(512); // 512 bytes
free(p1)
complex<int>* p2 = new complex<int>;    // one object
delete p2;
void *p3 = ::operator new(512); // 512 bytes
::operator delete(p3);
// 以下使用 C++ 标准库提供的 allocators
// 其接口虽有标准规格,但实现厂商并未完全遵守;下面三种形式略异
#ifdef _MSV_VER
    // 以下两个函数都是 no-static, 要通过 object 调用。
    // 分配 3 个 ints
    int *p4 = allocator<int>().allocate(3, (int*)0);    // 对应标准库分配器的第二个参数
    allocator<int>().deallocate(p4, 3);
#endif

#ifdef __BORLANDC__
    // 以下两个函数都是 no-static, 要通过 object 调用。
    // 分配 5 个 ints
    int *p4 = allocator<int>().allocate(5); // 同样包含第二个参数,但声明处有默认值,因此调用处可不写
    allocator<int>().deallocate(p4, 5);
#endif

#ifdef __GNUC__ // 早期版本的实现, 2.9
    // 以下两个啊含糊都是 static, 可通过全名调用。
    // 分配 512 bytes
    void *p4 = alloc::allocate(512);
    alloc::deallocate(p4, 512);
#endif
#ifdef __GNUC__ // 现代版本的实现, 4.9
    // 以下两个函数都是 no-static,要通过 object 调用。
    // 分配 7 个 ints
    void *p4 = allocator<int>().allocate(7);
    allocator<int>().deallocate((int*)p4, 7);
    
    // 以下两个函数都是 no-static,要通过 object 调用。
    // 分配 9 个 ints
    void *p4 = __gnu_cxx::pool_alloc<int>().allocate(9);
    __gnu_cxx::pool_alloc<int>.deallocate((int*)p4, 9);
#endif

new expression (new 背后的行为)

Complex *pc = new Complex(1, 2);

编译器转换为 ==>>

Complex *pc;
try {
    /* 1 */ void mem = operator new (sizeof(Complex));  // allocate 申请内存空间
    /* 2 */ pc = static_cast<Complex*>(mem);            // cast 类型转换
    /* 3 */ pc->Complex::Complex(1, 2);                 // construct 调用构造函数
    // 注意:只有编译器才可以像上面那样直接呼叫 ctor
}
catch (std::bad_alloc) {
    // 若 allocation 失败,就不执行 constructor 
}
注:
  • 申请内存可能会失败,因此引入 try...catch...
  • new 做两个动作

    • 申请内存
    • 调用构造函数
  • 欲直接调用 ctor, 可调用 placement new, new(p)Complex(1, 2)
// ...\vc98\crt\src\newop2.cpp (其中一个实现版本)
void *operator(size_t size, const std::nothrow_t &_THROW0())
{
    // try to allocate size bytes
    void *p;
    while ((p = malloc(size)) == 0)
    {
        // buy more memory or return null pointer
        __TRY_BEGIN
        if (_callnewh(size) == 0) break;
        _CATCH(std::bad_alloc)  return (0);
        _CATCH_END
    }
}
注:
  • std::nothrow_t 结构用作 new 运算符的函数参数,指示该函数应返回空指针以报告分配失败,而不是引发异常(struct std::nothrow_t{})
  • 当内存申请失败,_callnewh 调用 typedef void (*new_handler)(); new_handler set_new_handler(new_handler p) throw() 设置的函数,使得我们有机会释放掉我们认为可以释放的内存空间

delete expression (delete 背后的行为)

Complex *pc = new Complex(1, 2);
delete pc;

编译器转换为 ==>>

pc->~Complex();         // 调用析构函数
operator delete (pc);   // 释放内存
注:delete 的两个动作
  • 调用析构函数
  • 释放内存
// ...\vc98\crt\src\delop.cpp (其中一个实现版本)
void __cdelc operator delete(void *p) __THROW0()
{
    // free an allocated object
    free(p);
}

Ctor(构造函数) & Dtor(析构函数) 直接调用

#include <iostream> 

using namespace std;

class A {
public:
    int id;
    
    A() : id(0) {
        cout << "default ctor. this=" << this << " id=" << id << endl;
    }
    
    A(int i) : id(i) {
          cout << "ctor. this=" << this << " id=" << id << endl;
    }    
    
    ~A() {
         cout << "dtor. this=" << this << " id=" << id << endl; 
    }
};

void test_1()
{
    cout << endl << "test_1" << endl;
    
    A *pA = new A(1);
    
    cout << pA->id << endl;

    delete pA;
}

// simulate new
void test_2()
{
    cout << endl << "test_2" << endl;
    
    void *p = ::operator new(sizeof(A));    
    
    cout << "p=" << p << endl;
    
    A *pA = static_cast<A*>(p);
    
    cout << pA->id << endl;
    
    pA->~A();
    
    ::operator delete(pA);
}

void test_3()
{
    cout << endl << "test_3" << endl;
    
    A *pA = new A(3);
    
    cout << pA->id << endl;
    
    // pA->A::A(3);    // [Error] cannot call constructor 'A::A' directly
    
    // A::A(5);        // [Error] cannot call constructor 'A::A' directly

    pA->~A();        // 编译无错误, 析构函数被调用 

    delete pA;
}

int main()
{
    test_1();
    
    test_2();
    
    test_3();
    
    return 0;    
}

输出:[编译器 gnu 4.9,2]

test_1      // 构造、析构函数被调用,一切正常
ctor. this=0x781510 id=1
1
dtor. this=0x781510 id=1

test_2      // 构造函数未被调用
p=0x781510
7870992     // id 为内存中的随机值
dtor. this=0x781510 id=7870992  // 析构函数可被直接调用,但存在风险!!

test_3
ctor. this=0x781510 id=3        // 构造函数被调用
3
dtor. this=0x781510 id=3        // 析构函数被直接调用,但存在风险!!
dtor. this=0x781510 id=3        // 析构函数被 delete 时调用
注:
  • 语法上构造函数不可被直接调用;编译器发出错误
  • 语法上析构函数可被直接调用;编译通过;但不可以这样使用,比如析构函数中需要释放系统资源时,析构函被多次调用,资源也将被释放多次,行为未定义
  • 实际使用时,构造函数、析构函数都不应该直接调用

TianSong
737 声望139 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧