You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在单个内存块中分配两个不同类型的对象?

如何在单个内存块中分配两个不同类型的对象?

嘿,你这个需求其实挺常见的——就像std::make_shared把控制块和托管对象塞在同一块内存里那样,用一次分配搞定两个不同类型的对象完全可行。核心思路就是:先申请一块足够大的连续内存,然后用**放置new(placement new)**在内存的不同位置分别构造两个对象,同时必须严格处理内存对齐的问题,不然很容易踩坑。

我给你拆解成具体的步骤,再附上实用的代码示例:

核心原理

普通的new会同时完成“分配内存+构造对象”两个操作,但放置new只负责在你指定的已分配内存地址上构造对象,不会额外分配内存。这就给了我们自由:先一次性申请能装下两个对象的内存块,再手动指定位置构造不同类型的对象。

但有个绝对不能忽略的点:不同类型的内存对齐要求可能不一样(比如int可能是4字节对齐,std::complex<double>是8字节对齐)。如果直接把第二个对象硬塞在第一个对象后面,很可能会导致未对齐访问,这在很多架构上是直接触发硬件错误的,就算x86能兼容,也是标准里的未定义行为,绝对不能这么干。

手动实现的基础版本

比如我们要在同一块内存里放一个int和一个std::complex<double>,代码可以这么写:

#include <iostream>
#include <complex>
#include <cstdlib>
#include <memory> // 用于std::align

int main() {
    // 1. 提前计算两个对象的大小和对齐要求
    const size_t obj1_size = sizeof(int);
    const size_t obj1_align = alignof(int);
    const size_t obj2_size = sizeof(std::complex<double>);
    const size_t obj2_align = alignof(std::complex<double>);

    // 2. 分配一块足够大的内存:留足两个对象的大小,再加上最大对齐值的冗余空间,避免对齐调整后空间不够
    const size_t total_size = obj1_size + obj2_size + std::max(obj1_align, obj2_align);
    void* raw_mem = malloc(total_size);
    if (!raw_mem) {
        std::cerr << "内存分配失败啦!" << std::endl;
        return 1;
    }

    // 3. 调整指针,为第一个对象找到符合对齐要求的起始位置
    void* obj1_ptr = raw_mem;
    size_t remaining_space = total_size;
    std::align(obj1_align, obj1_size, obj1_ptr, remaining_space);

    // 4. 为第二个对象计算符合对齐的起始位置
    size_t used_by_obj1 = static_cast<char*>(obj1_ptr) - static_cast<char*>(raw_mem) + obj1_size;
    void* obj2_ptr = static_cast<char*>(raw_mem) + used_by_obj1;
    remaining_space = total_size - used_by_obj1;
    std::align(obj2_align, obj2_size, obj2_ptr, remaining_space);

    // 5. 用放置new构造两个不同类型的对象
    int* my_int = new(obj1_ptr) int(42); // 构造int对象,值为42
    std::complex<double>* my_complex = new(obj2_ptr) std::complex<double>(3.14, 2.71); // 构造复数对象

    // 现在可以正常使用这两个对象了
    std::cout << "第一个对象(int)的值:" << *my_int << std::endl;
    std::cout << "第二个对象(复数)的值:" << *my_complex << std::endl;

    // 6. 必须手动调用析构函数!放置new不会自动帮你析构
    my_complex->~complex<double>();
    my_int->~int();

    // 7. 最后释放一开始申请的整块原始内存
    free(raw_mem);
    return 0;
}

更安全的RAII封装版本

上面的基础版本需要手动管理析构和内存释放,很容易忘,我们可以用RAII(资源获取即初始化)的方式封装一下,让它更安全,用起来也更省心:

#include <iostream>
#include <complex>
#include <cstdlib>
#include <memory>
#include <utility> // 用于std::forward

// 封装两个对象和原始内存的结构体,自动处理析构和释放
template <typename T1, typename T2>
struct DualObject {
    void* raw_memory;
    T1* obj1;
    T2* obj2;

    // 自动析构:先析构对象,再释放内存
    ~DualObject() {
        if (obj2) obj2->~T2();
        if (obj1) obj1->~T1();
        if (raw_memory) free(raw_memory);
    }

    // 禁止拷贝,允许移动(避免double free)
    DualObject(const DualObject&) = delete;
    DualObject& operator=(const DualObject&) = delete;
    DualObject(DualObject&& other) noexcept 
        : raw_memory(other.raw_memory), obj1(other.obj1), obj2(other.obj2) {
        other.raw_memory = nullptr;
        other.obj1 = nullptr;
        other.obj2 = nullptr;
    }
    DualObject& operator=(DualObject&& other) noexcept {
        if (this != &other) {
            // 先清理当前资源
            if (obj2) obj2->~T2();
            if (obj1) obj1->~T1();
            if (raw_memory) free(raw_memory);

            // 接管对方的资源
            raw_memory = other.raw_memory;
            obj1 = other.obj1;
            obj2 = other.obj2;

            // 清空对方的指针,避免重复释放
            other.raw_memory = nullptr;
            other.obj1 = nullptr;
            other.obj2 = nullptr;
        }
        return *this;
    }
};

// 构造两个对象的工厂函数
template <typename T1, typename T2, typename... Args1, typename... Args2>
DualObject<T1, T2> make_dual_object(Args1&&... args1, Args2&&... args2) {
    const size_t size1 = sizeof(T1);
    const size_t align1 = alignof(T1);
    const size_t size2 = sizeof(T2);
    const size_t align2 = alignof(T2);

    // 分配足够大的内存,预留对齐冗余
    const size_t total_size = size1 + size2 + std::max(align1, align2);
    void* raw_mem = malloc(total_size);
    if (!raw_mem) {
        throw std::bad_alloc();
    }

    // 调整第一个对象的指针到符合对齐的位置
    void* ptr1 = raw_mem;
    size_t remaining = total_size;
    std::align(align1, size1, ptr1, remaining);

    // 调整第二个对象的指针到符合对齐的位置
    size_t used = static_cast<char*>(ptr1) - static_cast<char*>(raw_mem) + size1;
    void* ptr2 = static_cast<char*>(raw_mem) + used;
    remaining = total_size - used;
    std::align(align2, size2, ptr2, remaining);

    // 用完美转发构造对象,支持任意构造参数
    T1* t1 = new(ptr1) T1(std::forward<Args1>(args1)...);
    T2* t2 = new(ptr2) T2(std::forward<Args2>(args2)...);

    return {raw_mem, t1, t2};
}

// 使用示例
int main() {
    try {
        // 直接创建两个对象,自动管理资源
        auto dual = make_dual_object<int, std::complex<double>>(42, std::complex<double>(3.14, 2.71));
        std::cout << "int对象的值:" << *dual.obj1 << std::endl;
        std::cout << "复数对象的值:" << *dual.obj2 << std::endl;
        // 离开作用域时,DualObject的析构函数会自动处理所有清理工作
    } catch (const std::bad_alloc& e) {
        std::cerr << "内存分配失败:" << e.what() << std::endl;
        return 1;
    }
    return 0;
}

几个关键注意事项

  • 绝对不能用delete去释放obj1或obj2!普通delete会先析构对象再释放内存,但这里的对象是在我们手动分配的原始内存上构造的,delete会错误地尝试释放这块内存,导致重复释放或者内存泄漏。
  • 对齐问题是硬要求!一定要用std::align来调整指针,或者用std::aligned_alloc来分配已经对齐的内存,不要自己瞎算偏移。
  • 如果用operator new代替malloc分配内存,释放的时候要对应用operator delete,不要混用。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 13:44:50