Comments 36
В дебаге работало, в релизе ломалось. VS 2017.
В подавляющем большинстве случаев причина такого поведения ("в дебаге работало, в релизе ломалось") — неинициализированная переменная.
(по моему мнению)
Он же сообщает о использовании неинициализированной переменной. (по моему мнению)Сообщает если может. А может он далеко не всегда (по традиции задача выяснения — может ли переменная использоваться без инициализации сводится к проблеме остановки и решается только в частных случаях).
Так что бывают случаи, когда неинициализированные переменные полезны. Одна беда: опыт показывает, что хорошо бы, чтобы это была опция, которую нужно специально «заказывать», а не умолчание. Сейчас, увы, сделано наоборот.
в PROD может оказаться что лучше выделить фиксированную железяку под фиксированную нагрузку, и не заигрывать с переподпиской по аппаратным ресурсамЕсли вам нужно именно «железяку» заточить, то можно выключить overcommit. К сожалению это только на уровне всей системы делается…
P.S. Время выделения памяти под 100-мегабайтный массив всё равно будет мгновенным, если вы его инициализировать не будете, и заметно не мгновенным, если будете…
Хотел тут у себя воспроизвести, сделать скрин и разоблачить, что дотнет под массив память сразу выделяет всю и с нулями! Начал проверять — и что-то как-то резко передумал это делать...
С другой стороны такое ощущение, что это винда молодец, а не дотнет. Дотнет коммитит сразу весь гигабайт, а потребление постепенно растет (Private bytes):
using System;
namespace ConsoleApp17
{
class Program
{
static void Main(string[] args)
{
int[] bytes = new int[int.MaxValue/8];
Console.ReadKey();
for (int i = 0; i < bytes.Length; i+=1000)
{
bytes[i] = i;
Console.WriteLine(i);
}
Console.WriteLine(bytes);
}
}
}
Просто .NET вызывает функцию Windows для выделения памяти с одновременным заполнением нулями, а не заполняет нулями сам.
Не понимаю, при чем тут .Net, если это стандартный сишный calloc. Не знаю ни одного примера ни одного рантайма, которое бы делалло malloc + ручную инициализацию вместо этого.
Просто приведён в качестве примера фреймворка, где это стандартный механизм выделения памяти в противовес выделению памяти в C++.
Существует неплохая эвристика, реализованная в языке C#, которая несмотря на свою простоту закрывает большинство случаев.
В C# просто не решает проблему остановки, потому что анализ флоу производится локально. Он не позволяет передать неинициализированную переменную по ref например, чтобы её внутри кто-то проинициализировал. В большинстве случаев оно и не нужно, но собственно это единственный способ решать нерешаемые задачи — на уровне семантики обрубать случаи, когда анализ слишком усложняется, и реализовать тривиальную проверку "до первого использования идентификатора в качестве rvalue он используется в качестве lvalue"
Разве что тут, как я понял, никаким ub и не пахло
Статья отличная, в ней описывается тот момент, когда ты получил двойку по диктанту из-за неправильной бумаги, на которой ты писал.
Обычно всё сводилось к какой-нибудь замене строки на аналогичную, но «немного другую».
поскольку на программу, собранную в Update 3, внезапно стал ругаться антивирус, причём именно тот, который мы устанавливаем клиентам
#include <stdio.h>
#define TRIGGER_BUG
// Tested with:
// cl.exe ver 16.00.40219.01 for 80x86
// cl.exe ver 16.00.40219.01 for x64
// compile: cl /O2 /Ob2 vc2010_bug.cpp
// correct ouptut: 001001000
// actual ouptut: 001001100
volatile int val = 36;
static unsigned char *mkbits(unsigned int Inp, unsigned char *buf, unsigned int BitNum)
{
unsigned int i;
for (i = 0; i < BitNum; i++)
{
#ifndef TRIGGER_BUG
buf[i] = (Inp & (1 << i)) ? 1 : 0;
#else
buf[i] = (Inp & 1);
Inp >>= 1;
#endif
}
return buf + BitNum;
}
void Pack()
{
unsigned char buf[10];
unsigned char *pbuf = buf;
pbuf = mkbits(val, pbuf, 7);
pbuf = mkbits(0, pbuf, 2);
for (int i = 0; i < 9; i++)
printf("%c", buf[i] ? '1' : '0');
printf("\n");
}
int main(int argc, char** argv)
{
Pack();
return 0;
}
Работает только на указанной версии, в сервиспаках исправлено.
Код, который уже много лет работал в продакшн и давно не менялся, вдруг начал выдавать ошибки. Отладочная версия, естественно, работала как надо. Отладка с помощью принтов не помогла, все условия в цикле выполнялись, но он почему-то не завершался. Пришлось смотреть на сгенерированный код. Компилятор вместо условного перехода поставил безусловный, вот и получился бесконечный цикл. В компиляторе 4.9 это удалось исправить с помощью флага no-agressive-loop-optimization, а в 4.8 и это не помогло. Я так понял, что виноват не компилятор, а кривой код, который компилятор трактует как код, который может вызвать undefined behavior. Вот ссылка на эту тему: http://en.cppreference.com/w/cpp/language/ub.
Правда все они гораздо проще, чем современные оптимизирующие компиляторы.
Первый раз баг я в Borland C++ Builder поймал, когда заполнял значениями многомерный массив типа float. В Debug всё работало корректно, а в Release компилятор генерил код, который в ячейку типа float почему-то писал double.
Второй раз бодался с Intel C++ Compiler, который тупо падал при компиляции кода, богатого новейшими фишками C++.
«VS 2015 is not going to have any more bug fixes released, the hotfix you tried was a last effort to release fixes for some of the more ugly bugs exposed mostly by the new SSA Optimizer. I advise to move to VS 2017, especially since it’s binary compatible with 2015. There will also be some big improvements to the optimizer this year in the following VS 2017 updates.»
Правда, ошибка до сих пор воспроизводится и в VS 2017.
Как мы искали и нашли ошибку в Visual Studio C++