c++ - 在什么情况下 MSVC C++ 编译器有时会在函数 operator new[] 返回的指针之前直接写入数组大小?
问题描述
我目前正在开发一个内存跟踪器,我们正在重载函数 operator new[],它有很多变体。在编写一些单元测试时,我偶然发现了一个事实,即 MSVC C++ 2019(使用 ISO C++ 17 标准(std:c++17)编译器设置)在指针返回到来电者,但只是有时。我一直找不到任何记录在案的情况下会发生这种情况。谁能解释一下这些条件是什么,我如何在运行时检测到它们,或者指向我的任何文档?
为了确定这是否发生,我不得不反汇编代码。这是C++:
const size_t k_NumFoos = 6;
Foo* pFoo = new Foo[k_NumFoos];
这是反汇编:
00007FF747BB3683 call operator new[] (07FF747A00946h)
00007FF747BB3688 mov qword ptr [rbp+19E8h],rax
00007FF747BB368F cmp qword ptr [rbp+19E8h],0
00007FF747BB3697 je ____C_A_T_C_H____T_E_S_T____0+0FF7h (07FF747BB36F7h)
00007FF747BB3699 mov rax,qword ptr [rbp+19E8h]
00007FF747BB36A0 mov qword ptr [rax],6
00007FF747BB36A7 mov rax,qword ptr [rbp+19E8h]
00007FF747BB36AE add rax,8
00007FF747BB36B2 mov qword ptr [rbp+1B58h],rax
cmp
和je
行来自我们用于单元测试的 Catch2 库。后面的两个mov
sje
是它写入数组大小的地方。接下来的三行 ( mov
, add
, mov
) 是将指针移动到写入数组大小的位置之后的位置。大多数情况下,这一切都很好。
我们还使用 MSVirtualAlloc
作为重载函数 operator new[] 内部的分配器。从返回的地址VirtualAlloc
必须与使用 的函数 operator new[]std::align_t
对齐,当对齐大于默认的最大对齐时,在最后三行反汇编中指针的移动会与返回的对齐地址混淆。最初,我认为使用函数 operator new[] 进行的所有分配都会有这种行为。因此,我测试了函数运算符 new[] 的其他一些用法,并发现在我测试的所有情况下它都是正确的。我编写了代码来调整这种行为,然后遇到了一种情况,它没有表现出在返回分配之前写入数组大小的行为。
这是在返回分配之前未写入数组大小的 C++:
char **utf8Argv = new char *[ argc ];
argc
等于 1。该行来自Session::applyCommandLine
Catch2 库中的方法。反汇编看起来像这样:
00007FF73E189C6A call operator new[] (07FF73E07D6D8h)
00007FF73E189C6F mov qword ptr [rbp+168h],rax
00007FF73E189C76 mov rax,qword ptr [rbp+168h]
00007FF73E189C7D mov qword ptr [utf8Argv],rax
注意在call
to之后operator new[] (07FF73E07D6F8h)
没有写入数组大小。在查看两者的差异时,我可以看到一个写入指针,而另一个写入指向指针的指针。但是,据我所知,在运行时,这些信息都不能在内部用于函数 operator new[]。
这里的代码来自一个 Debug | x64 构建。关于如何确定这种行为何时发生的任何想法?
更新(对于下面的 convo): Foo 类:
template<size_t ArrLen>
class TFoo
{
public:
TFoo()
{
memset(m_bar, 0, ArrLen);
}
TFoo(const TFoo<ArrLen>& other)
{
strncpy_s(m_bar, other.m_bar, ArrLen);
}
TFoo(TFoo<ArrLen>&& victim)
{
strncpy_s(m_bar, victim.m_bar, ArrLen);
}
~TFoo()
{
}
TFoo<ArrLen>& operator= (const TFoo<ArrLen>& other)
{
strncpy_s(m_bar, other.m_bar, ArrLen);
}
TFoo<ArrLen>& operator= (TFoo<ArrLen>&& victim)
{
strncpy_s(m_bar, victim.m_bar, ArrLen);
}
const char* GetBar()
{
return m_bar;
}
void SetBar(const char bar[ArrLen])
{
strncpy_s(m_bar, bar, ArrLen);
}
protected:
char m_bar[ArrLen];
};
using Foo = TFoo<8>;
解决方案
猜测一下,我认为编译器在分配具有析构函数的对象时,会在指针返回给您之前写出分配的对象数,当您调用delete []
. 在这种情况下,编译器必须发出代码来销毁您调用时分配的每个对象delete []
,为此,它需要知道数组中存在多少对象。
OTOH,对于类似的东西char *
,不需要计数,因此,作为一个小的优化,没有发出任何东西,或者看起来如此。
我认为您不会在任何地方找到此文档,并且该行为可能会在编译器的未来版本中发生变化。它似乎不是标准的一部分。
推荐阅读
- python - 为什么即使安装了 FBX 模块也找不到?
- cors - 如何在 Cloud Foundry Staticfile Buildpack 中配置 CORS 策略以添加缺少的“Access-Control-Allow-Origin”标头
- c# - 无法将当前 JSON 对象(例如 {"name":"value"})反序列化为 List 类型 Collection
- monaco-editor - 带有原生 ECMAScript 模块导入的 monaco-editor
- windows - 如何跳过 PostgreSQL 寻呼机中的“-- More --”?
- android - Android 无法在 Android Q 10 上获取 IMEI
- c# - 如何使 DataGrid 列水平对齐拉伸和自定义列名 - UWP DataGrid
- vba - 如何选择最后一个活动单元格?
- java - 无法启动 Spring Boot 应用程序
- javascript - 客户端发布响应中的 jQuery 错误 - POST HTTP/1.1" 400