operator new, operator new[]

来自cppreference.com
< cpp‎ | memory‎ | new


 
 
工具库
语言支持
类型支持(基本类型、RTTI)
库功能特性测试宏 (C++20)
动态内存管理
程序工具
协程支持 (C++20)
变参数函数
调试支持
(C++26)
三路比较
(C++20)
(C++20)(C++20)(C++20)
(C++20)(C++20)(C++20)
通用工具
日期和时间
函数对象
格式化库 (C++20)
(C++11)
关系运算符 (C++20 中弃用)
整数比较函数
(C++20)(C++20)(C++20)   
(C++20)
交换类型运算
(C++14)
(C++11)
(C++11)
(C++11)
(C++17)
常用词汇类型
(C++11)
(C++17)
(C++17)
(C++17)
(C++11)
(C++17)
(C++23)
初等字符串转换
(C++17)
(C++17)

 
动态内存管理
未初始化内存算法
受约束的未初始化内存算法
分配器
垃圾收集器支持
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)



 
 
在标头 <new> 定义
可替换的分配函数
void* operator new  ( std::size_t count );
(1)
void* operator new[]( std::size_t count );
(2)
void* operator new  ( std::size_t count, std::align_val_t al );
(3) (C++17 起)
void* operator new[]( std::size_t count, std::align_val_t al );
(4) (C++17 起)
可替换的不抛出分配函数
void* operator new  ( std::size_t count, const std::nothrow_t& tag );
(5) (C++11 起为 noexcept)
void* operator new[]( std::size_t count, const std::nothrow_t& tag );
(6) (C++11 起为 noexcept)
void* operator new  ( std::size_t count, std::align_val_t al,
                      const std::nothrow_t& tag ) noexcept;
(7) (C++17 起)
void* operator new[]( std::size_t count, std::align_val_t al,
                      const std::nothrow_t& tag ) noexcept;
(8) (C++17 起)
不分配布置分配函数
void* operator new  ( std::size_t count, void* ptr );
(9) (C++11 起为 noexcept)
(C++26 起为 constexpr)
void* operator new[]( std::size_t count, void* ptr );
(10) (C++11 起为 noexcept)
(C++26 起为 constexpr)
用户定义布置分配函数
void* operator new  ( std::size_t count, /* 实参... */ );
(11)
void* operator new[]( std::size_t count, /* 实参... */ );
(12)
void* operator new  ( std::size_t count,
                      std::align_val_t al, /* 实参... */ );
(13) (C++17 起)
void* operator new[]( std::size_t count,
                      std::align_val_t al, /* 实参... */ );
(14) (C++17 起)
类特定分配函数
void* T::operator new  ( std::size_t count );
(15)
void* T::operator new[]( std::size_t count );
(16)
void* T::operator new  ( std::size_t count, std::align_val_t al );
(17) (C++17 起)
void* T::operator new[]( std::size_t count, std::align_val_t al );
(18) (C++17 起)
类特定布置分配函数
void* T::operator new  ( std::size_t count, /* 实参... */ );
(19)
void* T::operator new[]( std::size_t count, /* 实参... */ );
(20)
void* T::operator new  ( std::size_t count,
                         std::align_val_t al, /* 实参... */ );
(21) (C++17 起)
void* T::operator new[]( std::size_t count,
                         std::align_val_t al, /* 实参... */ );
(22) (C++17 起)

尝试分配请求数量的字节,而且分配请求可能会失败(即使请求分配的字节数为零)。这些分配函数会被 new 表达式调用,以分配将被初始化的对象所在的内存。它们也可以通过常规函数调用语法调用。

1)new 调用,以分配为单个对象请求的存储。
标准库实现会从自由存储分配 count 字节。
失败时,标准库实现会调用 std::get_new_handler 所返回的函数指针,并重复尝试分配,直到 new 处理函数不返回或成为空指针,这时它会抛出 std::bad_alloc
此函数必须返回指向拥有请求大小的对象的适当对齐的指针。
2)new[] 调用,以分配为数组请求的所有存储(包含可能的new 表达式 开销)。
标准库实现会调用版本 (1)
3)new 调用,以分配对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的单个对象所要求的存储。
4)new[] 调用,以元素的分配对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象数组要求的所有存储。
5) 被不抛出 new 调用。
标准库实现会调用版本 (1) 并在失败时不会传播异常,而是返回一个空指针。
6) 被不抛出 new[] 调用。
标准库实现会调用版本 (2) 并在失败时不会传播异常,而是返回一个空指针。
7) 在对象的对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 时被不抛出 new 调用。
标准库实现会调用版本 (3) 并在失败时不会传播异常,而是返回一个空指针。
8) 在数组元素的对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 时被不抛出 new[] 调用。
标准库实现会调用版本 (4) 并在失败时不会传播异常,而是返回一个空指针。
9) 被标准布置 new 调用。
标准库实现不会进行任何动作并返回未经修改的 ptr
如果通过布置 new 调用此函数而 ptr 是空指针,那么行为未定义。
10) 被标准布置 new[] 调用。
标准库实现不会进行任何动作并返回未经修改的 ptr
如果通过布置 new[] 调用此函数而 ptr 是空指针时,行为未定义。
11) 有定义时会被拥有匹配签名的自定义布置 new 调用。
如果还有定义类特定版本 (19),那么它会优先于 (11) 被调用。
如果用户既不提供 (11) 又不提供 (19),那么布置 new 非良构。
12) 有定义时会被拥有匹配签名的自定义布置 new[] 调用。
如果还有定义类特定版本 (20),那么它会优先于 (12) 被调用。
如果用户既不提供 (12) 又不提供 (20),那么布置 new[] 非良构。
13) 有定义且对象的对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 时会被拥有匹配签名的自定义布置 new 调用。
如果还有定义类特定版本((15)(17)),那么就改为调用它。
如果既不提供类特定版本又不提供具对齐布置形式(此形式),那么改为查找无对齐布置形式 (11)
14) 有定义且元素的对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 时会被拥有匹配签名的自定义布置 new[] 调用。
如果还有定义类特定版本((16)(18)),那么就改为调用它。
如果既不提供类特定版本又不提供具对齐布置形式(此形式),那么改为查找无对齐布置形式 (12)
15) 有定义且在分配 T 类型对象时会被 new 调用。
16) 有定义且在分配 T 类型对象的数组时会被 new[] 调用。
17) 有定义,在分配 T 类型对象,且它的对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 时会被 new 调用。
如果不提供此重载但提供了无对齐成员形式 (15),那么就会改为调用无对齐成员重载。
18) 有定义,在分配 T 类型的对象数组,且它的元素的对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__时会被 new[] 调用。
如果不提供此重载但提供了无对齐成员形式 (16),那么就会改为调用无对齐成员重载。
19) 有定义且在分配 T 类型对象时会被拥有匹配签名的自定义布置 new 调用。
20) 有定义且在分配 T 类型对象的数组时会被拥有匹配签名的自定义布置 new[] 调用。
21) 有定义,在分配 T 类型对象,且它的对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 时会被拥有匹配签名的自定义布置 new 调用。
如果不提供此重载但提供了无对齐成员形式 (19),那么就会改为调用无对齐成员重载。
22) 有定义,在分配 T 类型的对象数组,且它的元素的对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__时会被拥有匹配签名的自定义布置 new[] 调用。
如果不提供此重载但提供了无对齐成员形式 (20),那么就会改为调用无对齐成员重载。

参数

count - 要分配的字节数
ptr - 指向要初始化的对象所在的内存区域的指针
tag - 用于选择不抛出重载的消歧义标签
al - 使用的对齐,对齐值无效时行为未定义

返回值

1-4) 分配成功时返回指向拥有至少 size 大小的适当对齐的内存的非空指针 p0,它与之前返回的任何值 p1 均不相同,除非 p1 在返回后已经传递给了可替换的解分配函数;分配失败时不返回(此时会抛出异常,见下文)
5-8)(1-4),但在分配失败时返回空指针
9-10) ptr
11-22) 函数在分配失败时不返回的情况下同 (1-4),否则同 (5-8)

异常

1-4) 在分配内存失败时抛出能与类型为 std::bad_alloc 的处理块匹配的异常
11-22) 如果函数在分配失败时不返回则同 (1-4),否则同 (5-8)

全局替换

即使不包含标头 <new>,版本 (1-4) 也会在每个翻译单元隐式声明。版本 (1-8) 可以替换:在程序任意位置定义并在任意源文件的用户提供的拥有相同签名的非成员函数都会替换默认版本。它的声明不需要可见。

对于某个可以替换的分配函数,如果程序中提供了它的多个替换,或它有带 inline 说明符的替换声明,那么程序非良构,不要求诊断。如果替换在全局命名空间以外的命名空间中定义,或它被定义成在全局作用域的静态非成员函数,那么程序非良构。

不抛出版本的标准库实现 (5-8) 直接调用对应的抛出版本 (1-4)。抛出的数组版本的标准库实现 (2,4) 直接调用对应的单对象版本 (1,3)。因此只需要替换抛出单对象分配函数就可以处理所有分配。

独立实现上,(1-8) 的默认版本的行为是否满足以上要求由实现定义。建议独立实现在其中有任何默认版本满足宿主实现的要求的情况下,其他所有版本都应同时满足。

(C++26 起)

替换全局 operator new/delete

#include <cstdio>
#include <cstdlib>
#include <new>
 
// 无 inline,由 [replacement.functions]/3 要求
void* operator new(std::size_t sz)
{
    std::printf("1) new(size_t), size = %zu\n", sz);
    if (sz == 0)
        ++sz; // 避免 std::malloc(0),它可能会在成功时返回 nullptr
 
    if (void *ptr = std::malloc(sz))
        return ptr;
 
    throw std::bad_alloc{}; // 由 [new.delete.single]/3 要求
}
 
// 无 inline,由 [replacement.functions]/3 要求
void* operator new[](std::size_t sz)
{
    std::printf("2) new[](size_t), size = %zu\n", sz);
    if (sz == 0)
        ++sz; // 避免 std::malloc(0),它可能会在成功时返回 nullptr
 
    if (void *ptr = std::malloc(sz))
        return ptr;
 
    throw std::bad_alloc{}; // 由 [new.delete.single]/3 要求
}
 
void operator delete(void* ptr) noexcept
{
    std::puts("3) delete(void*)");
    std::free(ptr);
}
 
void operator delete(void* ptr, std::size_t size) noexcept
{
    std::printf("4) delete(void*, size_t), size = %zu\n", size);
    std::free(ptr);
}
 
void operator delete[](void* ptr) noexcept
{
    std::puts("5) delete[](void* ptr)");
    std::free(ptr);
}
 
void operator delete[](void* ptr, std::size_t size) noexcept
{
    std::printf("6) delete[](void*, size_t), size = %zu\n", size);
    std::free(ptr);
}
 
int main()
{
    int* p1 = new int;
    delete p1;
 
    int* p2 = new int[10]; // C++11 中保证调用替换
    delete[] p2;
}

可能的输出:

// 以 GCC-5 的 C++17 模式编译,可获得如下输出:
1) op new(size_t), size = 4
4) op delete(void*, size_t), size = 4
2) op new[](size_t), size = 40
5) op delete[](void* ptr)

operator newoperator new[] 带额外用户定义参数的重载(“布置形式”,版本 (11-14))可以照常在全局作用域声明,而且会被匹配的布置形式 new 表达式调用。

标准库的 operator new 的不分配布置形式 (9,10) 不能被替换,而且只能在布置 new 表达式不使用 ::new 语法时才能通过提供类特定的带匹配签名的布置 new (19,20)自定义:void* T::operator new(size_t, void*)void* T::operator new[](size_t, void*)

不能使用布置形式 void* operator new(std::size_t, std::size_t),因为对应的解分配函数的匹配签名 void operator delete(void*, std::size_t) 是通常(非布置)的解分配函数。

(C++14 起)

类特定重载

单对象和数组分配函数都可以定义为类的公开静态成员函数(版本 (15-18)。有定义时 new 表达式会调用这些函数,以分配此类单个对象或数组的内存,除非 new 表达式使用 ::new 形式,这样就会跳过类作用域查找。对这些函数不需要使用关键词 static:不管是否使用,分配函数都是静态成员函数。

new 表达式首先在类作用域中查找适当的函数名,然后在全局作用域查找。注意,与每个名称查找规则一样,就试图分配此类对象的 new 表达式而言,任何在类作用域声明的分配函数都会隐藏所有全局分配函数。

在分配对齐超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象或元素对齐超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象数组时,进行两次重载决议:首先,针对具对齐的函数签名,然后,针对无对齐的函数签名。这意味着如果某个拥有扩展对齐的类拥有针对无对齐的类特定分配函数,那么该函数,而非全局的具对齐分配函数将会被调用。这是有意的:类成员应该对如何处理该类有最好的理解。

(C++17 起)

在分配对齐没有超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象或元素对齐没有超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象数组时,进行两次重载决议:首先,针对无对齐的函数签名,然后,针对具对齐的函数签名。

(C++20 起)
#include <cstddef>
#include <iostream>
 
// 类特定分配函数
struct X
{
    static void* operator new(std::size_t count)
    {
        std::cout << "大小为 " << count << " 的自定义 new\n";
        return ::operator new(count);
    }
 
    static void* operator new[](std::size_t count)
    {
        std::cout << "大小为 " << count << " 的自定义 new[]\n";
        return ::operator new[](count);
    }
};
 
int main()
{
    X* p1 = new X;
    delete p1;
    X* p2 = new X[10];
    delete[] p2;
}

可能的输出:

大小为 1 的自定义 new
大小为 10 的自定义 new[]

operator newoperator new[] 带额外用户定义形参(“布置形式”)的重载也可以定义为类成员 (19-22)。在拥有匹配签名的布置 new 表达式查找要调用的对应分配函数时,查找在检验全局作用域前,从类作用域开始,且在提供了类特定的布置 new 时会调用它。

在分配对齐超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象或元素对齐超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象数组时,与通常形式一样对布置形式进行两次重载决议:首先,针对具对齐的函数签名,然后,针对无对齐的函数签名。

(C++17 起)

在分配对齐没有超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象或元素对齐没有超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象数组时,与通常形式一样对布置形式进行两次重载决议:首先,针对无对齐的函数签名,然后,针对具对齐的函数签名。

(C++20 起)
#include <cstddef>
#include <iostream>
#include <stdexcept>
 
struct X
{
    X() { throw std::runtime_error(""); }
 
    // 自定义布置 new
    static void* operator new(std::size_t count, bool b)
    {
        std::cout << "调用自定义的布置 new,b = " << b << '\n';
        return ::operator new(count);
    }
 
    // 自定义布置 delete
    static void operator delete(void* ptr, bool b)
    {
        std::cout << "调用自定义的布置 delete,b = " << b << '\n';
        ::operator delete(ptr);
    }
};
 
int main()
{
    try
    {
        [[maybe_unused]] X* p1 = new (true) X;
    }
    catch (const std::exception&)
    {}
}

输出:

调用自定义的布置 new,b = 1
调用自定义的布置 delete,b = 1

如果类级别的 operator new 是模板函数,那么它必须拥有返回类型 void*,第一实参类型 std::size_t,且它必须拥有两个或更多形参。换言之,只有布置形式可以是模板。

注解

尽管不分配布置 new (9,10) 不能被替换,但在上面描述的类作用域也可以定义拥有相同签名的函数。另外,可以使用像是布置 new 但接受非 void 指针类型作为第二实参的全局重载,所以希望确保调用真正的布置 new 的代码(例如 std::allocator::construct)必须使用 ::new 并将指针转型到 void*

如果解分配函数的行为无法满足默认约束,则其行为未定义。

要求下列函数是线程安全的:

对这些分配或解分配特定存储单元的函数调用以单独全序出现,而并且在此顺序中,每个解分配调用先发生于下个分配(如果存在)。

(C++11 起)

operator new 的库版本是否产生任何对 std::malloc std::aligned_alloc (C++17 起) 的调用是未指定的。

对于加载大文件,经由如 POSIX 上的 mmap 或 Windows 上的 CreateFileMapping(A/W) 伴随 MapViewOfFile 的操作系统特定函数进行文件映射,比为文件读取分配缓冲区更适合。

功能特性测试 标准 功能特性
__cpp_lib_freestanding_operator_new 202306L (C++26) 可替换的 operator new 的独立支持[1]
0 (C++26) 无独立支持
__cpp_lib_constexpr_new 202406L (C++26) constexpr 的布置 newnew[]
  1. 正式而言,此宏会在所有可替换的全局分配函数的默认版本都满足宿主实现的要求的情况下展开为 202306L

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 521 C++98 任何从 std::bad_alloc 派生的类都可以抛出,
即使该 std::bad_alloc 基有歧义或不可访问
抛出的异常应与类型是
std::bad_alloc 的处理块匹配
LWG 9 C++98 多次请求分配零字节的调用可以产生相同的指针 只有在之前产生的这些指针都
被传递给解分配函数时才可以
LWG 206 C++98 替换掉可替换的分配函数不会影响对应的可替换的不抛出分配函数的默认行为 默认行为也会有相应变更
LWG 404 C++98 可替换的分配函数在替换时可以声明为 inline 已禁止,不要求诊断

引用

  • C++23 标准(ISO/IEC 14882:2024):
  • 17.7 Dynamic memory management [support.dynamic]
  • C++20 标准(ISO/IEC 14882:2020):
  • 17.6 Dynamic memory management [support.dynamic]
  • C++17 标准(ISO/IEC 14882:2017):
  • 21.6 Dynamic memory management [support.dynamic]
  • C++14 标准(ISO/IEC 14882:2014):
  • 18.6 Dynamic memory management [support.dynamic]
  • C++11 标准(ISO/IEC 14882:2011):
  • 18.6 Dynamic memory management [support.dynamic]
  • C++03 标准(ISO/IEC 14882:2003):
  • 18.4 Dynamic memory management [lib.support.dynamic]
  • C++98 标准(ISO/IEC 14882:1998):
  • 18.4 Dynamic memory management [lib.support.dynamic]

参阅

[静态] (C++23)
Allocator 分配内存
(std::generator<Ref,V,Allocator>::promise_type 的公开静态成员函数)
解分配函数
(函数)
获得当前的 new 处理函数
(函数)
注册一个 new 处理函数
(函数)
(C++17 中弃用)(C++20 中移除)
获得未初始化存储
(函数模板)
分配内存
(函数)
分配对齐的内存
(函数)