从REPLACEMENT_OPERATOR_NEW_AND_DELETE看UE的堆内存管理及gcc相关实现

观察

为了让庞大代码库看起来更简洁一些,UE使用了不少C/C++黑魔法:宏。把一些重复或者繁琐的实现细节隐藏在了宏里面(例如最为常见且繁琐的GENERATED_BODY宏),尽管代码看起来更简洁,但也隐藏了一些(重要的)细节。

看UE插件实现时,意外的看到IMPLEMENT_MODULE宏定义中,不仅包含了初始化的InitializeModule接口,还包含了一个看起来更复杂的PER_MODULE_BOILERPLATE宏定义。

///@file: Engine\Source\Runtime\Core\Public\Modules\ModuleManager.h
	#define IMPLEMENT_MODULE( ModuleImplClass, ModuleName ) \
		\
		/**/ \
		/* InitializeModule function, called by module manager after this module's DLL has been loaded */ \
		/**/ \
		/* @return	Returns an instance of this module */ \
		/**/ \
		extern "C" DLLEXPORT IModuleInterface* InitializeModule() \
		{ \
			return new ModuleImplClass(); \
		} \
		/* Forced reference to this function is added by the linker to check that each module uses IMPLEMENT_MODULE */ \
		extern "C" void IMPLEMENT_MODULE_##ModuleName() { } \
		PER_MODULE_BOILERPLATE \
		PER_MODULE_BOILERPLATE_ANYLINK(ModuleImplClass, ModuleName)

这个宏的内容如下所示,可以看到主要是new/delete操作符的定义,这也就是说在每个模块的C++中都包含包含有一个这样的操作符实现。更重要的是:这是一个重载定义而没有重载声明,C++执行new/delete操作符时,编译器甚至不知道这个定义的存在,这种情况下会用到这个定义吗?会不会用到编译器的缺省实现(毕竟我们通常是不会自定义这些实现的)?

///@file: Engine\Source\Runtime\Core\Public\Modules\Boilerplate\ModuleBoilerplate.h
// Disable the replacement new/delete when running the Clang static analyzer, due to false positives in 15.0.x:
// https://github.com/llvm/llvm-project/issues/58820
#if !FORCE_ANSI_ALLOCATOR && !defined(__clang_analyzer__)
static_assert(__STDCPP_DEFAULT_NEW_ALIGNMENT__ <= 16, "Expecting 16-byte default operator new alignment - alignments > 16 may have bloat");
#define REPLACEMENT_OPERATOR_NEW_AND_DELETE \
	OPERATOR_NEW_MSVC_PRAGMA void* operator new  ( size_t Size                                                    ) OPERATOR_NEW_THROW_SPEC      { return FMemory::Malloc( Size ? Size : 1, __STDCPP_DEFAULT_NEW_ALIGNMENT__ ); } \
	OPERATOR_NEW_MSVC_PRAGMA void* operator new[]( size_t Size                                                    ) OPERATOR_NEW_THROW_SPEC      { return FMemory::Malloc( Size ? Size : 1, __STDCPP_DEFAULT_NEW_ALIGNMENT__ ); } \
	OPERATOR_NEW_MSVC_PRAGMA void* operator new  ( size_t Size,                             const std::nothrow_t& ) OPERATOR_NEW_NOTHROW_SPEC    { return FMemory::Malloc( Size ? Size : 1, __STDCPP_DEFAULT_NEW_ALIGNMENT__ ); } \
	OPERATOR_NEW_MSVC_PRAGMA void* operator new[]( size_t Size,                             const std::nothrow_t& ) OPERATOR_NEW_NOTHROW_SPEC    { return FMemory::Malloc( Size ? Size : 1, __STDCPP_DEFAULT_NEW_ALIGNMENT__ ); } \
	OPERATOR_NEW_MSVC_PRAGMA void* operator new  ( size_t Size, std::align_val_t Alignment                        ) OPERATOR_NEW_THROW_SPEC      { return FMemory::Malloc( Size ? Size : 1, (std::size_t)Alignment ); } \
	OPERATOR_NEW_MSVC_PRAGMA void* operator new[]( size_t Size, std::align_val_t Alignment                        ) OPERATOR_NEW_THROW_SPEC      { return FMemory::Malloc( Size ? Size : 1, (std::size_t)Alignment ); } \
	OPERATOR_NEW_MSVC_PRAGMA void* operator new  ( size_t Size, std::align_val_t Alignment, const std::nothrow_t& ) OPERATOR_NEW_NOTHROW_SPEC    { return FMemory::Malloc( Size ? Size : 1, (std::size_t)Alignment ); } \
	OPERATOR_NEW_MSVC_PRAGMA void* operator new[]( size_t Size, std::align_val_t Alignment, const std::nothrow_t& ) OPERATOR_NEW_NOTHROW_SPEC    { return FMemory::Malloc( Size ? Size : 1, (std::size_t)Alignment ); } \
	void operator delete  ( void* Ptr                                                                             ) OPERATOR_DELETE_THROW_SPEC   { FMemory::Free( Ptr ); } \
	void operator delete[]( void* Ptr                                                                             ) OPERATOR_DELETE_THROW_SPEC   { FMemory::Free( Ptr ); } \
	void operator delete  ( void* Ptr,                                                      const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); } \
	void operator delete[]( void* Ptr,                                                      const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); } \
	void operator delete  ( void* Ptr,             size_t Size                                                    ) OPERATOR_DELETE_THROW_SPEC   { FMemory::Free( Ptr ); } \
	void operator delete[]( void* Ptr,             size_t Size                                                    ) OPERATOR_DELETE_THROW_SPEC   { FMemory::Free( Ptr ); } \
	void operator delete  ( void* Ptr,             size_t Size,                             const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); } \
	void operator delete[]( void* Ptr,             size_t Size,                             const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); } \
	void operator delete  ( void* Ptr,                          std::align_val_t Alignment                        ) OPERATOR_DELETE_THROW_SPEC   { FMemory::Free( Ptr ); } \
	void operator delete[]( void* Ptr,                          std::align_val_t Alignment                        ) OPERATOR_DELETE_THROW_SPEC   { FMemory::Free( Ptr ); } \
	void operator delete  ( void* Ptr,                          std::align_val_t Alignment, const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); } \
	void operator delete[]( void* Ptr,                          std::align_val_t Alignment, const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); } \
	void operator delete  ( void* Ptr,             size_t Size, std::align_val_t Alignment                        ) OPERATOR_DELETE_THROW_SPEC   { FMemory::Free( Ptr ); } \
	void operator delete[]( void* Ptr,             size_t Size, std::align_val_t Alignment                        ) OPERATOR_DELETE_THROW_SPEC   { FMemory::Free( Ptr ); } \
	void operator delete  ( void* Ptr,             size_t Size, std::align_val_t Alignment, const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); } \
	void operator delete[]( void* Ptr,             size_t Size, std::align_val_t Alignment, const std::nothrow_t& ) OPERATOR_DELETE_NOTHROW_SPEC { FMemory::Free( Ptr ); }
#else
	#define REPLACEMENT_OPERATOR_NEW_AND_DELETE
#endif

allocator

和UE的很多功能一样,allocator的相关功能并没有详细文档,不过在EMemoryAllocatorToUse可以看到所有的支持的allocator列表。

enum EMemoryAllocatorToUse
{
    Ansi,
    Stomp,
    TBB,
    Jemalloc,
    Binned,
    Binned2,
    Binned3,
    Platform,
    Mimalloc,
}

通过查看UE的代码可知在Unix系统下进程可以通过命令行设置-jemalloc,-ansimalloc,-binnedmalloc,-mimalloc,-binnedmalloc2等来订制分配器。

class FMalloc* FUnixPlatformMemory::BaseAllocator()
{
///...
	// Allow overriding on the command line.
	// We get here before main due to global ctors, so need to do some hackery to get command line args
	if (FILE* CmdLineFile = fopen("/proc/self/cmdline", "r"))
	{
		char * Arg = nullptr;
		size_t Size = 0;
		while(getdelim(&Arg, &Size, 0, CmdLineFile) != -1)
		{
#if PLATFORM_SUPPORTS_JEMALLOC
			if (FCStringAnsi::Stricmp(Arg, "-jemalloc") == 0)
			{
				AllocatorToUse = EMemoryAllocatorToUse::Jemalloc;
				break;
			}
#endif // PLATFORM_SUPPORTS_JEMALLOC
			if (FCStringAnsi::Stricmp(Arg, "-ansimalloc") == 0)
			{
				// see FPlatformMisc::GetProcessDiagnostics()
				AllocatorToUse = EMemoryAllocatorToUse::Ansi;
				break;
			}

			if (FCStringAnsi::Stricmp(Arg, "-binnedmalloc") == 0)
			{
				AllocatorToUse = EMemoryAllocatorToUse::Binned;
				break;
			}

#if MIMALLOC_ENABLED
			if (FCStringAnsi::Stricmp(Arg, "-mimalloc") == 0)
			{
				AllocatorToUse = EMemoryAllocatorToUse::Mimalloc;
				break;
			}
#endif

			if (FCStringAnsi::Stricmp(Arg, "-binnedmalloc2") == 0)
			{
				AllocatorToUse = EMemoryAllocatorToUse::Binned2;
				break;
			}

			if (FCStringAnsi::Stricmp(Arg, "-fullcrashcallstack") == 0)
			{
				GFullCrashCallstack = true;
			}
			///...
		}
		///....
		
///...

	// This was moved to the fact that we aboved the command line statements above to *include* other things besides allocator only switches
	// Moving here allows the other globals to be set, while we override the ANSI allocator still no matter the command line options
	if (FORCE_ANSI_ALLOCATOR)
	{
		AllocatorToUse = EMemoryAllocatorToUse::Ansi;
	}

	FMalloc * Allocator = NULL;

	switch (AllocatorToUse)
	{
	case EMemoryAllocatorToUse::Ansi:
		Allocator = new FMallocAnsi();
		break;

#if WITH_MALLOC_STOMP
	case EMemoryAllocatorToUse::Stomp:
		Allocator = new FMallocStomp();
		break;
#endif

#if PLATFORM_SUPPORTS_JEMALLOC
	case EMemoryAllocatorToUse::Jemalloc:
		Allocator = new FMallocJemalloc();
		break;
#endif // PLATFORM_SUPPORTS_JEMALLOC

#if MIMALLOC_ENABLED
	case EMemoryAllocatorToUse::Mimalloc:
		Allocator = new FMallocMimalloc();
		break;
#endif

	case EMemoryAllocatorToUse::Binned2:
		Allocator = new FMallocBinned2();
		break;

	default:	// intentional fall-through
	case EMemoryAllocatorToUse::Binned:
		Allocator = new FMallocBinned(FPlatformMemory::GetConstants().BinnedPageSize & MAX_uint32, 0x100000000);
		break;
	}

#if UE_BUILD_DEBUG
	printf("Using %s.\n", Allocator ? TCHAR_TO_UTF8(Allocator->GetDescriptiveName()) : "NULL allocator! We will probably crash right away");
#endif // UE_BUILD_DEBUG

#if UE_USE_MALLOC_REPLAY_PROXY
	if (bAddReplayProxy)
	{
		Allocator = new FMallocReplayProxy(Allocator);
	}
#endif // UE_USE_MALLOC_REPLAY_PROXY

	return Allocator;
}

堆分配

除了基于C++的new/delete操作符之外,UE也必然会用到直接的堆内存操作。例如,最为典型的是从一个UClass创建一个UObject对象,由于UClass描述的对象只有在运行时才能获得,所以必然会涉及到堆内存的分配。尽管堆内存也可以通过类似于new char[SIZE]这种形式实现,但是毕竟看起来还是有些怪。

在真正的堆内存分配中,FUObjectAllocator::AllocateUObject内使用的也是前面在new函数中看到的FMemory::Malloc函数。也就是说明了一个简单的结论:UE是经过自定义的FMemory::Malloc函数(而不是标准C库的malloc)来实现堆内存管理的。

NewObject>>StaticConstructObject_Internal>>StaticAllocateObject==>>FUObjectAllocator::AllocateUObject

///@file: Engine\Source\Runtime\CoreUObject\Private\UObject\UObjectAllocator.cpp
/**
 * Allocates a UObjectBase from the free store or the permanent object pool
 *
 * @param Size size of uobject to allocate
 * @param Alignment alignment of uobject to allocate
 * @param bAllowPermanent if true, allow allocation in the permanent object pool, if it fits
 * @return newly allocated UObjectBase (not really a UObjectBase yet, no constructor like thing has been called).
 */
UObjectBase* FUObjectAllocator::AllocateUObject(int32 Size, int32 Alignment, bool bAllowPermanent)
{
	// Force alignment to minimal of 16 bytes
	Alignment = FMath::Max(16, Alignment);
	int32 AlignedSize = Align( Size, Alignment );
	UObjectBase* Result = nullptr;

	bAllowPermanent &= PermanentObjectPool != nullptr;
	const bool bPlaceInPerm = bAllowPermanent && (Align(PermanentObjectPoolTail,Alignment) + Size) <= (PermanentObjectPool + PermanentObjectPoolSize);
	if (bAllowPermanent && !bPlaceInPerm)
	{
		// advance anyway so we can determine how much space we should set aside in the ini
		uint8* AlignedPtr = Align( PermanentObjectPoolExceededTail, Alignment );
		PermanentObjectPoolExceededTail = AlignedPtr + Size;
	}
	// Use object memory pool for objects disregarded by GC (initially loaded ones). This allows identifying their
	// GC status by simply looking at their address.
	if (bPlaceInPerm)
	{
		// Align current tail pointer and use it for object. 
		uint8* AlignedPtr = Align( PermanentObjectPoolTail, Alignment );
		// Update tail pointer.
		PermanentObjectPoolTail = AlignedPtr + Size;
		Result = (UObjectBase*)AlignedPtr;
		if (PermanentObjectPoolExceededTail < PermanentObjectPoolTail)
		{
			PermanentObjectPoolExceededTail = PermanentObjectPoolTail;
		}
	}
	else
	{
		// Allocate new memory of the appropriate size and alignment.
		Result = (UObjectBase*)FMemory::Malloc( Size, Alignment );
	}

#if !UE_BUILD_TEST && !UE_BUILD_SHIPPING
	checkf(IsAligned(Result, Alignment), TEXT("Allocated memory address does not match requirement of %d byte alignment for size %d"), Alignment, Size);
#endif

	return Result;
}

C++标准

C++标准对于new/delete操作有些规定

replaceable allocation functions

[[nodiscard]] (since C++20)

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) (since C++17)

void* operator new[]( std::size_t count, std::align_val_t al ); (4) (since C++17)

replaceable non-throwing allocation functions

noexcept (since C++11)

[[nodiscard]] (since C++20)

void* operator new ( std::size_t count, const std::nothrow_t& tag );(5)

void* operator new[]( std::size_t count, const std::nothrow_t& tag );(6)

void* operator new ( std::size_t count,

                  std::align_val_t al, const std::nothrow_t& );(7)	(since C++17)

void* operator new[]( std::size_t count,

                  std::align_val_t al, const std::nothrow_t& );(8)	(since C++17)

non-allocating placement allocation functions

noexcept(since C++11)

[[nodiscard]](since C++20)

void* operator new ( std::size_t count, void* ptr ); (9)

void* operator new[]( std::size_t count, void* ptr ); (10)

user-defined placement allocation functions

void* operator new ( std::size_t count, user-defined-args... ); (11)

void* operator new[]( std::size_t count, user-defined-args... ); (12)

void* operator new ( std::size_t count,

                  std::align_val_t al, user-defined-args... );  (13)	(since C++17)

void* operator new[]( std::size_t count,

                  std::align_val_t al, user-defined-args... );

和声明/定义相关的包括下面内容:

The versions (1-4) are implicitly declared in each translation unit even if the header is not included. Versions (1-8) are replaceable: a user-provided non-member function with the same signature defined anywhere in the program, in any source file, replaces the default version. Its declaration does not need to be visible.

这里明确说明了1-4这个四个声明即使在没有include 这个头文件的时候也是可用的(implicitly declared);下一句还强调了对于(1-8)函数,用户提供的、具有相同函数singature的、非成员函数无论在程序的什么地方定义,都会替换缺省版本,并且这些(自定义版本的)声明不需要(对它们的使用者在编译时)可见;反过来还有一个推论就是对于(1-8)之外的原型,它们不是replaceable,用户不能定义全局的实现来替换它们。

这也就对应了UE中只是提供了这些函数的实现而没有提供对应声明的实现机制(其实使用者只需要包含头文件即可,使用的声明位于文件,定义位于REPLACEMENT_OPERATOR_NEW_AND_DELETE)。

std库

在new文件的声明中可以看到,对于不在前面提到的(1-8)列表中的带placement的new(std::size_t, void* __p)等函数,它们是以inline的形式定义在头文件中的,所以如果包含了这个头文件,那么当然在编译的时候直接就内联了这个实现,所以就无法替换。

//@{
/** These are replaceable signatures:
 *  - normal single new and delete (no arguments, throw @c bad_alloc on error)
 *  - normal array new and delete (same)
 *  - @c nothrow single new and delete (take a @c nothrow argument, return
 *    @c NULL on error)
 *  - @c nothrow array new and delete (same)
 *
 *  Placement new and delete signatures (take a memory address argument,
 *  does nothing) may not be replaced by a user's program.
*/
void* operator new(std::size_t) _GLIBCXX_THROW (std::bad_alloc)
  __attribute__((__externally_visible__));
void* operator new[](std::size_t) _GLIBCXX_THROW (std::bad_alloc)
  __attribute__((__externally_visible__));
void operator delete(void*) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void operator delete[](void*) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
#if __cpp_sized_deallocation
void operator delete(void*, std::size_t) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void operator delete[](void*, std::size_t) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
#endif
void* operator new(std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void* operator new[](std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void operator delete(void*, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void operator delete[](void*, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
#if __cpp_aligned_new
void* operator new(std::size_t, std::align_val_t)
  __attribute__((__externally_visible__));
void* operator new(std::size_t, std::align_val_t, const std::nothrow_t&)
  _GLIBCXX_USE_NOEXCEPT __attribute__((__externally_visible__));
void operator delete(void*, std::align_val_t)
  _GLIBCXX_USE_NOEXCEPT __attribute__((__externally_visible__));
void operator delete(void*, std::align_val_t, const std::nothrow_t&)
  _GLIBCXX_USE_NOEXCEPT __attribute__((__externally_visible__));
void* operator new[](std::size_t, std::align_val_t)
  __attribute__((__externally_visible__));
void* operator new[](std::size_t, std::align_val_t, const std::nothrow_t&)
  _GLIBCXX_USE_NOEXCEPT __attribute__((__externally_visible__));
void operator delete[](void*, std::align_val_t)
  _GLIBCXX_USE_NOEXCEPT __attribute__((__externally_visible__));
void operator delete[](void*, std::align_val_t, const std::nothrow_t&)
  _GLIBCXX_USE_NOEXCEPT __attribute__((__externally_visible__));
#if __cpp_sized_deallocation
void operator delete(void*, std::size_t, std::align_val_t)
  _GLIBCXX_USE_NOEXCEPT __attribute__((__externally_visible__));
void operator delete[](void*, std::size_t, std::align_val_t)
  _GLIBCXX_USE_NOEXCEPT __attribute__((__externally_visible__));
#endif // __cpp_sized_deallocation
#endif // __cpp_aligned_new

// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
inline void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }

// Default placement versions of operator delete.
inline void operator delete  (void*, void*) _GLIBCXX_USE_NOEXCEPT { }
inline void operator delete[](void*, void*) _GLIBCXX_USE_NOEXCEPT { }
//@}

而对于可替换的版本,在std库实现中添加了weak属性,所以如果用户定义了自己的实现版本,则C++库的实现不会生效。

_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
{
  void *p;

  /* malloc (0) is unpredictable; avoid it.  */
  if (sz == 0)
    sz = 1;

  while (__builtin_expect ((p = malloc (sz)) == 0, false))
    {
      new_handler handler = std::get_new_handler ();
      if (! handler)
	_GLIBCXX_THROW_OR_ABORT(bad_alloc());
      handler ();
    }

  return p;
}

不过在具体实现上,由于只有Darwin系统定义了_GLIBCXX_WEAK_DEFINITION宏为__attribute__ ((weak)),其它平台没有定义,所以这个weak其实是利用libstdc++.so的依赖顺序通常靠后,所以最后被使用来实现。

tsecer@harry: readelf -s  /usr/lib64/libstdc++.so.6| c++filt -t | fgrep new
    75: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __newlocale@GLIBC_2.2.5 (41)
   120: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND transaction clone for operator new[](unsigned long)
   245: 000000000008e550    18 FUNC    GLOBAL DEFAULT   12 _ZNSt20bad_array_new_leng@@CXXABI_1.3.8
   373: 000000000037c7d0    24 OBJECT  WEAK   DEFAULT   22 _ZTISt20bad_array_new_len@@CXXABI_1.3.8
   591: 0000000000090ab0     5 FUNC    GLOBAL DEFAULT   12 operator new[](unsigned long, std::align_val_t)@@CXXABI_1.3.11
   606: 000000000008e530    19 FUNC    GLOBAL DEFAULT   12 _ZNSt20bad_array_new_leng@@CXXABI_1.3.8
   853: 0000000000090910     8 FUNC    GLOBAL DEFAULT   12 std::get_new_handler()@@GLIBCXX_3.4.20
   947: 0000000000090a00   132 FUNC    GLOBAL DEFAULT   12 operator new(unsigned long, std::align_val_t)@@CXXABI_1.3.11
  1841: 0000000000090980    88 FUNC    GLOBAL DEFAULT   12 operator new(unsigned long, std::nothrow_t const&)@@GLIBCXX_3.4
  2458: 00000000000909e0     5 FUNC    GLOBAL DEFAULT   12 operator new[](unsigned long)@@GLIBCXX_3.4
  2914: 0000000000091550    27 FUNC    GLOBAL DEFAULT   12 __cxa_vec_new@@CXXABI_1.3
  3100: 000000000008e530    19 FUNC    GLOBAL DEFAULT   12 _ZNSt20bad_array_new_leng@@CXXABI_1.3.8
  3336: 0000000000091450   244 FUNC    GLOBAL DEFAULT   12 __cxa_vec_new2@@CXXABI_1.3
  3340: 0000000000091570   254 FUNC    GLOBAL DEFAULT   12 __cxa_vec_new3@@CXXABI_1.3
  3367: 000000000037c7e8    40 OBJECT  WEAK   DEFAULT   22 _ZTVSt20bad_array_new_len@@CXXABI_1.3.8
  3802: 00000000000909f0     5 FUNC    GLOBAL DEFAULT   12 operator new[](unsigned long, std::nothrow_t const&)@@GLIBCXX_3.4
  3991: 000000000008e520     8 FUNC    GLOBAL DEFAULT   12 _ZNKSt20bad_array_new_len@@CXXABI_1.3.8
  4311: 000000000013d3b0    25 OBJECT  WEAK   DEFAULT   14 _ZTSSt20bad_array_new_len@@CXXABI_1.3.8
  4350: 0000000000090920    92 FUNC    GLOBAL DEFAULT   12 operator new(unsigned long)@@GLIBCXX_3.4
  4852: 000000000008eff0    50 FUNC    GLOBAL DEFAULT   12 __cxa_throw_bad_array_new@@CXXABI_1.3.8
  5346: 0000000000090900    11 FUNC    GLOBAL DEFAULT   12 _ZSt15set_new_handlerPFvv@@GLIBCXX_3.4
tsecer@harry: 

编译器内置声明

在gcc初始化声明时,有很大一部分代码在处理new/delete的这种内置声明。这里编译器纯手工(而不是基于程序分析)搓出了new/delete的相关声明。

///@file: gcc-7.3.1\gcc\cp\decl.c
/* Create the predefined scalar types of C,
   and some nodes representing standard constants (0, 1, (void *)0).
   Initialize the global binding level.
   Make definitions for built-in primitive functions.  */

void
cxx_init_decl_processing (void)
{
///...
{
    tree newattrs, extvisattr;
    tree newtype, deltype;
    tree ptr_ftype_sizetype;
    tree new_eh_spec;

    ptr_ftype_sizetype
      = build_function_type_list (ptr_type_node, size_type_node, NULL_TREE);
    if (cxx_dialect == cxx98)
      {
	tree bad_alloc_id;
	tree bad_alloc_type_node;
	tree bad_alloc_decl;

	push_namespace (std_identifier);
	bad_alloc_id = get_identifier ("bad_alloc");
	bad_alloc_type_node = make_class_type (RECORD_TYPE);
	TYPE_CONTEXT (bad_alloc_type_node) = current_namespace;
	bad_alloc_decl
	  = create_implicit_typedef (bad_alloc_id, bad_alloc_type_node);
	DECL_CONTEXT (bad_alloc_decl) = current_namespace;
	pop_namespace ();

	new_eh_spec
	  = add_exception_specifier (NULL_TREE, bad_alloc_type_node, -1);
      }
    else
      new_eh_spec = noexcept_false_spec;

    /* Ensure attribs.c is initialized.  */
    init_attributes ();

    /* Ensure constraint.cc is initialized. */
    init_constraint_processing ();

    extvisattr = build_tree_list (get_identifier ("externally_visible"),
				  NULL_TREE);
    newattrs = tree_cons (get_identifier ("alloc_size"),
			  build_tree_list (NULL_TREE, integer_one_node),
			  extvisattr);
    newtype = cp_build_type_attribute_variant (ptr_ftype_sizetype, newattrs);
    newtype = build_exception_variant (newtype, new_eh_spec);
    deltype = cp_build_type_attribute_variant (void_ftype_ptr, extvisattr);
    deltype = build_exception_variant (deltype, empty_except_spec);
    tree opnew = push_cp_library_fn (NEW_EXPR, newtype, 0);
    DECL_IS_MALLOC (opnew) = 1;
    DECL_IS_OPERATOR_NEW (opnew) = 1;
    opnew = push_cp_library_fn (VEC_NEW_EXPR, newtype, 0);
    DECL_IS_MALLOC (opnew) = 1;
    DECL_IS_OPERATOR_NEW (opnew) = 1;
    push_cp_library_fn (DELETE_EXPR, deltype, ECF_NOTHROW);
    push_cp_library_fn (VEC_DELETE_EXPR, deltype, ECF_NOTHROW);
    if (flag_sized_deallocation)
      {
	/* Also push the sized deallocation variants:
	     void operator delete(void*, std::size_t) throw();
	     void operator delete[](void*, std::size_t) throw();  */
	tree void_ftype_ptr_size
	  = build_function_type_list (void_type_node, ptr_type_node,
				      size_type_node, NULL_TREE);
	deltype = cp_build_type_attribute_variant (void_ftype_ptr_size,
						   extvisattr);
	deltype = build_exception_variant (deltype, empty_except_spec);
	push_cp_library_fn (DELETE_EXPR, deltype, ECF_NOTHROW);
	push_cp_library_fn (VEC_DELETE_EXPR, deltype, ECF_NOTHROW);
      }

    if (aligned_new_threshold)
      {
	push_namespace (std_identifier);
	tree align_id = get_identifier ("align_val_t");
	align_type_node = start_enum (align_id, NULL_TREE, size_type_node,
				      NULL_TREE, /*scoped*/true, NULL);
	pop_namespace ();

	/* operator new (size_t, align_val_t); */
	newtype = build_function_type_list (ptr_type_node, size_type_node,
					    align_type_node, NULL_TREE);
	newtype = cp_build_type_attribute_variant (newtype, newattrs);
	newtype = build_exception_variant (newtype, new_eh_spec);
	opnew = push_cp_library_fn (NEW_EXPR, newtype, 0);
	DECL_IS_MALLOC (opnew) = 1;
	DECL_IS_OPERATOR_NEW (opnew) = 1;
	opnew = push_cp_library_fn (VEC_NEW_EXPR, newtype, 0);
	DECL_IS_MALLOC (opnew) = 1;
	DECL_IS_OPERATOR_NEW (opnew) = 1;

	/* operator delete (void *, align_val_t); */
	deltype = build_function_type_list (void_type_node, ptr_type_node,
					    align_type_node, NULL_TREE);
	deltype = cp_build_type_attribute_variant (deltype, extvisattr);
	deltype = build_exception_variant (deltype, empty_except_spec);
	push_cp_library_fn (DELETE_EXPR, deltype, ECF_NOTHROW);
	push_cp_library_fn (VEC_DELETE_EXPR, deltype, ECF_NOTHROW);

	if (flag_sized_deallocation)
	  {
	    /* operator delete (void *, size_t, align_val_t); */
	    deltype = build_function_type_list (void_type_node, ptr_type_node,
						size_type_node, align_type_node,
						NULL_TREE);
	    deltype = cp_build_type_attribute_variant (deltype, extvisattr);
	    deltype = build_exception_variant (deltype, empty_except_spec);
	    push_cp_library_fn (DELETE_EXPR, deltype, ECF_NOTHROW);
	    push_cp_library_fn (VEC_DELETE_EXPR, deltype, ECF_NOTHROW);
	  }
      }

    nullptr_type_node = make_node (NULLPTR_TYPE);
    TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
    TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
    TYPE_UNSIGNED (nullptr_type_node) = 1;
    TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
    if (abi_version_at_least (9))
      SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
    SET_TYPE_MODE (nullptr_type_node, ptr_mode);
    record_builtin_type (RID_MAX, "decltype(nullptr)", nullptr_type_node);
    nullptr_node = build_int_cst (nullptr_type_node, 0);
  }
///...
}

wrap up

尽管在UE的代码中没有看到直接的new/delete全局操作符声明和定义,但是由于每个module通过REPLACEMENT_OPERATOR_NEW_AND_DELETE间接定义了new/delete,基于C++对于new/delete操作符的相关规定,最终达到使用的还是UE自定义的堆管理机制。

posted on 2024-04-26 12:20  tsecer  阅读(5)  评论(0编辑  收藏  举报

导航