Как я могу заставить компилятор MSVC исключить выделение больших временных объектов в стеке?

Этот вопрос не дублирует этот или другие подобные вопросы. Этот вопрос касается очистки структуры после ее инициализации и использования.


Обновить

Прочитав несколько первых ваших комментариев, я хотел бы уточнить свой вопрос:

  • Как я могу заставить компилятор MSVC исключить выделение большого стека?

Я обновил заголовок, текст и код ниже, чтобы прояснить это.


Недавно я начал компилировать свои проекты с параметрами компилятора /GS, /sdl и /analyze. (Microsoft Visual C ++ 2015) С этими параметрами компилятор правильно предупреждает о сомнительных конструкциях кода. Однако я сталкивался с некоторыми предупреждениями, которые я всегда считал хорошим стилем C ++.

Взгляните на следующий пример кода:

struct my_struct {
    char  large_member[64000];
};

void do_something_else(my_struct & ms)
{
    // the intent of the next line is to "clear" the ms object
    ms = {};  // <-- here the compiler claims the large stack allocation

   // ... do some more work with ms
}

my_struct oh_my = {}; // construction, apparently no large stack allocation

int main()
{ 
    // ...
    // do something with the oh_my object
    // 

    do_something_else(oh_my);
}

Мне сказали, что стандартный способ очистки структуры C ++ следующий:

ms = {};

С параметром /analyze компилятор предупреждает об этом следующим образом (пример):

C: \ Dev \ MDS \ Proj \ MDSCPV \ Vaps_Common_lib \ camber_radar.cpp: 162: предупреждение: C6262: функция использует '144400' байт стека: превышает / анализирует: stacksize '16384' .. Это выделение было для сгенерированного компилятором временный для 'struct BitmapBuffer' в строке 162. Рассмотрите возможность перемещения некоторых данных в кучу.

Думаю бывает следующее:

  • в стеке создается временный объект
  • временный объект копируется в объектную переменную

Я бы хотел, чтобы там происходило что-то вроде инициализации по умолчанию. На мой взгляд, компилятор должен иметь возможность оптимизировать распределение стека. Но видимо (по предупреждению) компилятор этого не делает. У меня такой вопрос: Как я могу заставить компилятор исключить выделение стека? Теперь я начал заменять эти места следующим кодом:

std::memset(&ms, 0, sizeof(ms));

person user23573    schedule 11.12.2018    source источник
comment
Получаете ли вы это предупреждение при сборке в режиме Release (с включенной оптимизацией)?   -  person binary01    schedule 11.12.2018
comment
Да, я получаю это в режиме выпуска. Я делаю сборку релиза с /GS /analyze опциями. (/GS и /sdl в некоторой степени исключают друг друга)   -  person user23573    schedule 11.12.2018
comment
Мне кажется, это нормально. Вы выделяете в стеке две структуры размером не менее 64000 байта каждая, компилятор имеет все права жаловаться, учитывая, что порог составлял 16384 байта.   -  person user7860670    schedule 11.12.2018
comment
@ user10605163, что ты имеешь в виду под словом "нельзя пропустить"? GCC и Clang генерируют memset вызов для этой строки: godbolt.org/z/G6CQO_   -  person Evg    schedule 11.12.2018
comment
@Evg Да, я не знаю, о чем я думал.   -  person    schedule 11.12.2018
comment
Обычное приложение MSVC будет связано с размером стека по умолчанию 1M. Вы можете указать cl, чтобы настроить параметр анализа: /analyze:stacksize1000000   -  person tunglt    schedule 11.12.2018
comment
@tunglt, это убивает мессенджер, а не решение проблемы.   -  person user23573    schedule 11.12.2018
comment
Вы хотите, чтобы этот конкретный код был оптимизирован таким образом, или будет достаточно альтернативного кода с таким же эффектом (состояние после этого будет, что ms похоже на инициализацию по умолчанию) и без выделения стека?   -  person    schedule 11.12.2018
comment
@ user10605163 меня беспокоит стандартная правильность, а также оптимальный код. В настоящее время я думаю о методе std::memset, а также о реализации функции-члена clear() структуры.   -  person user23573    schedule 11.12.2018
comment
Вы должны объявить глобальную переменную, например, const my_struct MY_ZERO_ = {};, а затем в своей функции, где вам нужно инициализировать вашу переменную: ms = MY_ZERO_; это стоит только memcpy вместо memset.   -  person tunglt    schedule 11.12.2018
comment
В принципе, вы можете, если не хотите изменять my_struct и хотите, чтобы он работал в менее тривиальных случаях, которые не могут быть решены простым memset, создать новый объект в хранилище ms, например ms.~my_struct(); new(&ms) my_struct();. Это гарантированно будет построено по умолчанию без копирования, но вам нужно быть осторожным, если у вас есть константные или ссылочные элементы данных или базовые подобъекты, и в этом случае нужно std::launder все указатели / ссылки на старый объект.   -  person    schedule 11.12.2018


Ответы (1)


Поскольку my_struct легко копировать, компиляторы должны иметь возможность размещать вызов memset вместо создания временного и затем назначать его, но это не обязательно.

Новое выражение размещения решит вашу проблему: оно создает объект по заранее выделенному адресу с помощью предоставленного конструктора. Например, new(&ms) my_struct{} дает ту же семантику, что и ms = {}. Если my_struct имеет Нетривиальный деструктор, явный вызов ms.~my_struct() должен предшествовать размещению new. Для справки: новое выражение

Я предлагаю не использовать эту технику в обычном порядке. Это своего рода низкоуровневый C ++ «черной магии». Хорошие компиляторы должны оптимизировать, используя memset.

Между прочим, глобальная переменная oh_my не выделяет временный объект в стеке, потому что он инициализируется константой во время компиляции.

person Julien Villemure-Fréchette    schedule 11.12.2018
comment
По-видимому, Microsoft Visual C ++ 2015 не является хорошим компилятором - person Frediano Ziglio; 12.12.2018