Pull to refresh

Comments 36

У меня наблюдалось подобное при рекурсивной сортировке массивов.
В дебаге работало, в релизе ломалось. VS 2017.

В подавляющем большинстве случаев причина такого поведения ("в дебаге работало, в релизе ломалось") — неинициализированная переменная.

А как же компилятор? Он же сообщает о использовании неинициализированной переменной.
(по моему мнению)
Он же сообщает о использовании неинициализированной переменной. (по моему мнению)
Сообщает если может. А может он далеко не всегда (по традиции задача выяснения — может ли переменная использоваться без инициализации сводится к проблеме остановки и решается только в частных случаях).
поэтому переменные нужно инициализировать всегда :)
Если вы аллоцируете неинициализированный массив мег на 100 — то это произойдёт мгновенно и память начнёт портебляться когда вы в него писать начнёте, а если вы его инициализировать будете — то аллокация будет занимать существенное время и память будет занята сразу.

Так что бывают случаи, когда неинициализированные переменные полезны. Одна беда: опыт показывает, что хорошо бы, чтобы это была опция, которую нужно специально «заказывать», а не умолчание. Сейчас, увы, сделано наоборот.
было бы круто иметь возможность укзаать ленивость/неленивость в конфигурации запуска, или в рантайме. Например, в DEV режиме полезно экономить память, а в PROD может оказаться что лучше выделить фиксированную железяку под фиксированную нагрузку, и не заигрывать с переподпиской по аппаратным ресурсам. Надо только придумать, как сделать, чтобы эта фича выглядела «прозрачной» и не заставляла думать о себе постоянно…
в PROD может оказаться что лучше выделить фиксированную железяку под фиксированную нагрузку, и не заигрывать с переподпиской по аппаратным ресурсам
Если вам нужно именно «железяку» заточить, то можно выключить overcommit. К сожалению это только на уровне всей системы делается…

P.S. Время выделения памяти под 100-мегабайтный массив всё равно будет мгновенным, если вы его инициализировать не будете, и заметно не мгновенным, если будете…
Но если инициализировать нулями, то такой проблемы нет, потому что можно просто ссылаться на заполненный нулями кусок памяти с copy-on-write.
Так 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);
        }
    }
}

image

> С другой стороны такое ощущение, что это винда молодец, а не дотнет. Дотнет коммитит сразу весь гигабайт, а потребление постепенно растет (Private bytes):

Просто .NET вызывает функцию Windows для выделения памяти с одновременным заполнением нулями, а не заполняет нулями сам.

Не понимаю, при чем тут .Net, если это стандартный сишный calloc. Не знаю ни одного примера ни одного рантайма, которое бы делалло malloc + ручную инициализацию вместо этого.

Это стандартное поведение calloc'а на многих системах, не только на Windows. А вот C++ — так не умеет, как ни удивительно. Может в каком-нибудь C++23 добавят…
> Не понимаю, при чем тут .Net, если это стандартный сишный calloc.

Просто приведён в качестве примера фреймворка, где это стандартный механизм выделения памяти в противовес выделению памяти в C++.

Существует неплохая эвристика, реализованная в языке C#, которая несмотря на свою простоту закрывает большинство случаев.

В C# просто не решает проблему остановки, потому что анализ флоу производится локально. Он не позволяет передать неинициализированную переменную по ref например, чтобы её внутри кто-то проинициализировал. В большинстве случаев оно и не нужно, но собственно это единственный способ решать нерешаемые задачи — на уровне семантики обрубать случаи, когда анализ слишком усложняется, и реализовать тривиальную проверку "до первого использования идентификатора в качестве rvalue он используется в качестве lvalue"

значение неинициализированной переменной — всего лишь частный случай UB. В gcc, например, была подобная проблема: компилятор мог выкинуть два подряд идущих условия if (condition) {...}, if (!condition) {...} если condition зависел от ub.

Разве что тут, как я понял, никаким ub и не пахло
После прочтения статьи все побежали винить компилятор в ошибках программы.
Статья отличная, в ней описывается тот момент, когда ты получил двойку по диктанту из-за неправильной бумаги, на которой ты писал.
Хотя иногда не стоит так сильно давить на ручку.(да, я король ассоциаций)
UFO just landed and posted this here
Большинство современных технологий мс уже в опенсорсе.
Несколько раз натыкался на internal compiler error со студией, как 2015, так и 2017. Код слишком объёмный, чтобы пытаться уменьшить и найти причину.
Обычно всё сводилось к какой-нибудь замене строки на аналогичную, но «немного другую».
поскольку на программу, собранную в Update 3, внезапно стал ругаться антивирус, причём именно тот, который мы устанавливаем клиентам

Вот так ругаться? (собирал CoreCLR)

Как говорится,
был бы код...
Тоже находил баг в 2010 студии:
#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;
}

Работает только на указанной версии, в сервиспаках исправлено.
Столкнулся с похожей проблемой во время перехода с 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.
Вся линейка компиляторов от Microsoft содержала баги. Всегда. Народ регулярно их находил. Лет 15 назад народ понял что править эти баги никто не собирается. Никогда. И молча ушел в сторону GCC. Но уже и там не всё благополучно. Новая мода: — Давайте модернизировать С++, каждый год! А почему не два раза в месяц? Дальше только хуже будет. IMHO.
UFO just landed and posted this here
Это неправда. Есть программы, которые багов не содержат, а есть такие, где последние баги были найдены многие годы назад: qmail, TeX, etc.

Правда все они гораздо проще, чем современные оптимизирующие компиляторы.

Баг — понятие растяжимое. По определению это поведение, не соответствующее спецификации. Но если мы сделаем спецификацию максимально неопределенную, то багов как бы и не будет. 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.
Sign up to leave a comment.

Articles