Он же сообщает о использовании неинициализированной переменной. (по моему мнению)
Сообщает если может. А может он далеко не всегда (по традиции задача выяснения — может ли переменная использоваться без инициализации сводится к проблеме остановки и решается только в частных случаях).
Если вы аллоцируете неинициализированный массив мег на 100 — то это произойдёт мгновенно и память начнёт портебляться когда вы в него писать начнёте, а если вы его инициализировать будете — то аллокация будет занимать существенное время и память будет занята сразу.
Так что бывают случаи, когда неинициализированные переменные полезны. Одна беда: опыт показывает, что хорошо бы, чтобы это была опция, которую нужно специально «заказывать», а не умолчание. Сейчас, увы, сделано наоборот.
было бы круто иметь возможность укзаать ленивость/неленивость в конфигурации запуска, или в рантайме. Например, в DEV режиме полезно экономить память, а в PROD может оказаться что лучше выделить фиксированную железяку под фиксированную нагрузку, и не заигрывать с переподпиской по аппаратным ресурсам. Надо только придумать, как сделать, чтобы эта фича выглядела «прозрачной» и не заставляла думать о себе постоянно…
в PROD может оказаться что лучше выделить фиксированную железяку под фиксированную нагрузку, и не заигрывать с переподпиской по аппаратным ресурсам
Если вам нужно именно «железяку» заточить, то можно выключить overcommit. К сожалению это только на уровне всей системы делается…
P.S. Время выделения памяти под 100-мегабайтный массив всё равно будет мгновенным, если вы его инициализировать не будете, и заметно не мгновенным, если будете…
Так calloc делает, но это как раз надо просить специально. По умолчанию C++ «заказывает» участок памяти и явно прописывает его нулями. Не знаю ни одного компилятора, который бы делал по другому…
А вот C# делает именно так, как написал я. Выделите массив на гиг, потом начинаете его заполнять и видите, как постепенно растёт потребление памяти. А всё потому, что в .NET любое выделение памяти инициализируется нулями, а для value-типов нельзя переопределить конструктор по умолчанию.
Хотел тут у себя воспроизвести, сделать скрин и разоблачить, что дотнет под массив память сразу выделяет всю и с нулями! Начал проверять — и что-то как-то резко передумал это делать...
С другой стороны такое ощущение, что это винда молодец, а не дотнет. Дотнет коммитит сразу весь гигабайт, а потребление постепенно растет (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);
}
}
}
> С другой стороны такое ощущение, что это винда молодец, а не дотнет. Дотнет коммитит сразу весь гигабайт, а потребление постепенно растет (Private bytes):
Просто .NET вызывает функцию Windows для выделения памяти с одновременным заполнением нулями, а не заполняет нулями сам.
Не понимаю, при чем тут .Net, если это стандартный сишный calloc. Не знаю ни одного примера ни одного рантайма, которое бы делалло malloc + ручную инициализацию вместо этого.
Это стандартное поведение calloc'а на многих системах, не только на Windows. А вот C++ — так не умеет, как ни удивительно. Может в каком-нибудь C++23 добавят…
В C# просто не решает проблему остановки, потому что анализ флоу производится локально. Он не позволяет передать неинициализированную переменную по ref например, чтобы её внутри кто-то проинициализировал. В большинстве случаев оно и не нужно, но собственно это единственный способ решать нерешаемые задачи — на уровне семантики обрубать случаи, когда анализ слишком усложняется, и реализовать тривиальную проверку "до первого использования идентификатора в качестве rvalue он используется в качестве lvalue"
значение неинициализированной переменной — всего лишь частный случай UB. В gcc, например, была подобная проблема: компилятор мог выкинуть два подряд идущих условия if (condition) {...}, if (!condition) {...} если condition зависел от ub.
После прочтения статьи все побежали винить компилятор в ошибках программы.
Статья отличная, в ней описывается тот момент, когда ты получил двойку по диктанту из-за неправильной бумаги, на которой ты писал.
Несколько раз натыкался на internal compiler error со студией, как 2015, так и 2017. Код слишком объёмный, чтобы пытаться уменьшить и найти причину.
Обычно всё сводилось к какой-нибудь замене строки на аналогичную, но «немного другую».
Столкнулся с похожей проблемой во время перехода с gcc 4.4 на 4.8 и 4.9.
Код, который уже много лет работал в продакшн и давно не менялся, вдруг начал выдавать ошибки. Отладочная версия, естественно, работала как надо. Отладка с помощью принтов не помогла, все условия в цикле выполнялись, но он почему-то не завершался. Пришлось смотреть на сгенерированный код. Компилятор вместо условного перехода поставил безусловный, вот и получился бесконечный цикл. В компиляторе 4.9 это удалось исправить с помощью флага no-agressive-loop-optimization, а в 4.8 и это не помогло. Я так понял, что виноват не компилятор, а кривой код, который компилятор трактует как код, который может вызвать undefined behavior. Вот ссылка на эту тему: http://en.cppreference.com/w/cpp/language/ub.
Очень похоже на ошибку, которую мы обнаружили еще в VS2008 и она проявлялась во всех последующих версиях компилятора. На текущий момент она все еще в статусе Active.
Вся линейка компиляторов от Microsoft содержала баги. Всегда. Народ регулярно их находил. Лет 15 назад народ понял что править эти баги никто не собирается. Никогда. И молча ушел в сторону GCC. Но уже и там не всё благополучно. Новая мода: — Давайте модернизировать С++, каждый год! А почему не два раза в месяц? Дальше только хуже будет. IMHO.
Баг — понятие растяжимое. По определению это поведение, не соответствующее спецификации. Но если мы сделаем спецификацию максимально неопределенную, то багов как бы и не будет. UB != баг.
Все компиляторы содержат баги, это не прерогатива Microsoft.
Первый раз баг я в Borland C++ Builder поймал, когда заполнял значениями многомерный массив типа float. В Debug всё работало корректно, а в Release компилятор генерил код, который в ячейку типа float почему-то писал double.
Второй раз бодался с Intel C++ Compiler, который тупо падал при компиляции кода, богатого новейшими фишками C++.
Обнаружил подобную ошибку оптимизатора, отослал в Microsoft получил такой ответ:
«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.
В дебаге работало, в релизе ломалось. VS 2017.
В подавляющем большинстве случаев причина такого поведения ("в дебаге работало, в релизе ломалось") — неинициализированная переменная.
(по моему мнению)
Так что бывают случаи, когда неинициализированные переменные полезны. Одна беда: опыт показывает, что хорошо бы, чтобы это была опция, которую нужно специально «заказывать», а не умолчание. Сейчас, увы, сделано наоборот.
P.S. Время выделения памяти под 100-мегабайтный массив всё равно будет мгновенным, если вы его инициализировать не будете, и заметно не мгновенным, если будете…
Хотел тут у себя воспроизвести, сделать скрин и разоблачить, что дотнет под массив память сразу выделяет всю и с нулями! Начал проверять — и что-то как-то резко передумал это делать...
С другой стороны такое ощущение, что это винда молодец, а не дотнет. Дотнет коммитит сразу весь гигабайт, а потребление постепенно растет (Private bytes):
Просто .NET вызывает функцию Windows для выделения памяти с одновременным заполнением нулями, а не заполняет нулями сам.
Не понимаю, при чем тут .Net, если это стандартный сишный calloc. Не знаю ни одного примера ни одного рантайма, которое бы делалло malloc + ручную инициализацию вместо этого.
Просто приведён в качестве примера фреймворка, где это стандартный механизм выделения памяти в противовес выделению памяти в C++.
Существует неплохая эвристика, реализованная в языке C#, которая несмотря на свою простоту закрывает большинство случаев.
В C# просто не решает проблему остановки, потому что анализ флоу производится локально. Он не позволяет передать неинициализированную переменную по ref например, чтобы её внутри кто-то проинициализировал. В большинстве случаев оно и не нужно, но собственно это единственный способ решать нерешаемые задачи — на уровне семантики обрубать случаи, когда анализ слишком усложняется, и реализовать тривиальную проверку "до первого использования идентификатора в качестве rvalue он используется в качестве lvalue"
Разве что тут, как я понял, никаким ub и не пахло
Статья отличная, в ней описывается тот момент, когда ты получил двойку по диктанту из-за неправильной бумаги, на которой ты писал.
Обычно всё сводилось к какой-нибудь замене строки на аналогичную, но «немного другую».
Работает только на указанной версии, в сервиспаках исправлено.
Код, который уже много лет работал в продакшн и давно не менялся, вдруг начал выдавать ошибки. Отладочная версия, естественно, работала как надо. Отладка с помощью принтов не помогла, все условия в цикле выполнялись, но он почему-то не завершался. Пришлось смотреть на сгенерированный код. Компилятор вместо условного перехода поставил безусловный, вот и получился бесконечный цикл. В компиляторе 4.9 это удалось исправить с помощью флага no-agressive-loop-optimization, а в 4.8 и это не помогло. Я так понял, что виноват не компилятор, а кривой код, который компилятор трактует как код, который может вызвать undefined behavior. Вот ссылка на эту тему: http://en.cppreference.com/w/cpp/language/ub.
Все программы сложнее HelloWorld содержали, содержат и будут содержать баги.
Правда все они гораздо проще, чем современные оптимизирующие компиляторы.
Баг — понятие растяжимое. По определению это поведение, не соответствующее спецификации. Но если мы сделаем спецификацию максимально неопределенную, то багов как бы и не будет. 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.