Я сейчас работаю над трекером памяти для работы, и мы перегружаем оператор функции new [] во многих его вариантах. При написании некоторых модульных тестов я наткнулся на тот факт, что MSV C C ++ 2019 (с использованием настройки компилятора ISO C ++ 17 Standard (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
s, следующие за je
, записывают размер массива. Следующие три строки (mov
, add
, mov
) - это место, где он перемещает указатель после того, как он записал размер массива. В основном это все хорошо.
Мы также используем MS VirtualAlloc
в качестве внутреннего распределителя для перегруженной функции operator new []. Адрес, возвращаемый из VirtualAlloc
, должен быть выровнен для оператора функции new [], который использует std::align_t
, и когда выравнивание больше, чем максимальное выравнивание по умолчанию, перемещение указателя в последних трех строках дизассемблирования мешает возвращаемый выровненный адрес. Первоначально я думал, что все выделения, сделанные с помощью оператора функции 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
до operator new[] (07FF73E07D6F8h)
нет записи размера массива. Когда я смотрю на два на предмет различий, я вижу, что один пишет в указатель, а другой записывает в указатель на указатель. Однако, насколько мне известно, никакая из этой информации не доступна внутри, во время выполнения, для функции operator new [].
Код здесь взят из Debug | x64 сборка. Есть идеи, как определить, когда это поведение произойдет?
Обновление (для приведенного ниже конво): Class 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>;