Мы обновляем старый проект MFC с VS2010 до VS2017 и обнаружили другое поведение при назначении данных в std::string внутри структуры, которая была очищена с помощью ZeroMemory.
Я создал простую программу MFC для воспроизведения проблемы.
std::string getStrData() {
std::string temp = "world";
return temp;
}
CMainFrame::CMainFrame()
{
struct mystruct{
std::string mystr_in1;
std::string mystr_in2;
};
std::string mystr_out = "hello";
mystruct* sttemp = new mystruct();
ZeroMemory(sttemp, sizeof(mystruct)); // <-- we think this is bad
sttemp->mystr_in1 = mystr_out; // <-- VS2010: "hello" is assigned, but VS2017: garbage is assigned
sttemp->mystr_in2 = getStrData(); // <-- VS2010 and VS2017: "world" is assigned
}
В VS2010 значение mystr_out ("hello") правильно присваивается mystr_in1. Однако в VS2017 данные мусора назначаются mystr_in1. Я читал подобные вопросы здесь, в StackOverflow, что выполнение ZeroMemory в этом случае уничтожает std::string, поэтому мы думаем, что это является причиной проблемы. Но в mystr_in2 он может правильно назначать «мир» как в VS2010, так и в VS2017.
У меня есть 2 вопроса об этом поведении, если кто-нибудь может объяснить.
В первом случае (mystr_in1) почему код, скомпилированный в VS2010 и VS2017, ведет себя по-разному? Есть ли какие-то настройки проекта в VS2017, чтобы исправить это? При обновлении старого проекта VS2010 мы просто открыли решение VS2010 в VS2017 и выбрали решение для обновления. (Большинство наших старых проектов не имеют проблем, просто делая это)
Для 2-го случая (mystr_in2) предполагается, что он будет таким же (?) как и 1-й случай, но назначенный ему std::string был возвращен из функции. Чем это отличается от первого случая, который также присваивает std::string?
@chris в настоящее время мы планируем просто удалить ZeroMemory в качестве исправления, но вы правы, могут быть другие изменения в поведении других частей кода, подобных этому. Может ли это быть проблемой Visual Studio (компилятора?), которая вызвала другое поведение?
После VS2010 было по крайней мере один или два перерыва в работе ABI. Не исключено, что std::string
был частью этого и изменил свое внутреннее представление, но я не знаю. В любом случае, если в структуре в фактическом коде есть какие-либо другие данные, которые необходимо обнулить, убедитесь, что они обнулены после того, как ZeroMemory
исчезнет. Даже инициализация с помощью new Type()
может оставить простые старые данные неинициализированными, если в структуре определен конструктор по умолчанию, который ничего не инициализирует, так что просто нужно следить.
@chris спасибо за совет. Мы просто инициализируем переменные вручную и правильно после удаления ZeroMemory. Я также попытаюсь задать этот вопрос на форумах MSDN, так как это может быть проблема Visual Studio.
Код никогда не был правильным. Инициализация объекта путем записи буквально нулевых значений в память не имеет и никогда не имел1 любого четко определенного поведения. Для типов тривиально копируемый на практике это часто сходит с рук, но поведение по-прежнему не определено. Однако рассматриваемый код не может претендовать на эту серую зону для себя: mystruct
не является тривиально копируемым.
Наблюдаемое изменение в поведении вполне может быть связано с SSO (оптимизация коротких строк), хотя спорно рассуждать об изменении в поведении, которое никогда не имело четко определенного поведения с самого начала.
К счастью, исправление простое: просто удалите вызов ZeroMemory
. Элементы mystruct
, созданные по умолчанию, будут иметь элементы std::string
, созданные по умолчанию, поэтому нет причин снова их «инициализировать».
1Я верю, во всяком случае. В конце концов, это C++, и кажется, что его сложность в значительной степени переросла средние умственные способности.
Даже если есть пластырь, это то, что должно быть исправлено должным образом. У вас даже нет гарантии, что это не повлияет на VS2010, которые вы никогда не обнаруживали.