Обнаружены ошибки в библиотеках C++Builder

    Мы проверили заголовочные файлы, входящие в состав Embarcadero C++Builder XE3. Фактически, это означает только проверку небольшого числа inline-функций. Соответственно найдено совсем немного подозрительных мест, но достаточно для небольшой заметки.

    Введение


    Мы регулярно проверяем открытые проекты, а так же все другое, что можно проверить. Например, мы проверяли библиотеки, входящие в состав Visual C++ 2012. Результатом стала заметка: "Обнаружены ошибки в библиотеках Visual C++ 2012".

    В дистрибутив Visual C++ входят исходные коды библиотек. Ситуация с C++Builder хуже. Есть только заголовочные файлы. Поэтому удалось проверить только некоторые inline-функции. Тем не менее, удалось найти кое-что интересное. Рассмотрим соответствующие участки кода.

    Работа с предупреждениями


    #pragma warning(disable : 4115)
    #include <objbase.h>
    #pragma warning(default : 4115)

    Диагностическое сообщение, выданное PVS-Studio:

    V665 Possibly, the usage of '#pragma warning(default: X)' is incorrect in this context. The '#pragma warning(push/pop)' should be used instead. Check lines: 16, 18. iaguid.h 18

    Нехорошо устанавливать режим вывода предупреждения в состояние по умолчанию. Правильным подходом, будет сохранить, а затем восстановить предыдущее состояние. Для это следует использовать "#pragma warning(push[ ,n ])" и "#pragma warning(pop)".

    Неудачный макрос


    #define SET_VTYPE_AND_VARREF(type, val) \
      this->vt = VT_ ## type | VT_BYREF; \
      V_ ## type ## REF (this) = val;
    
    TVariantT& operator=(System::Currency* src)
    {
      Clear();
      if(src)
        SET_VTYPE_AND_VARREF(CY,
          reinterpret_cast<tagCY*>(&(src->Val)));
      return* this;
    }

    Диагностическое сообщение, выданное PVS-Studio:

    V640 The code's operational logic does not correspond with its formatting. The second statement will always be executed. It is possible that curly brackets are missing. utilcls.h 1781

    Макрос SET_VTYPE_AND_VARREF написан плохо. Его содержимое не обрамлено фигурными скобками { }. В результате условие «if (src)» будет относиться только к первой строчке макроса.

    Неопределенное поведение


    #define _BITS_BYTE    8
    template<class _Uint,
        _Uint _Ax,
        _Uint _Cx,
        _Uint _Mx>
        class linear_congruential
    {
      static _CONST_DATA int _Nw =
        (_BITS_BYTE * sizeof (_Uint) + 31) / 32;
    
      void seed(seed_seq& _Seq)
      {
        _Uint _Arr[3 + _Nw];
        ....
        int _Lsh = _BITS_BYTE * sizeof (_Uint);
        ....
    
        for (int _Idx = _Nw; 0 < --_Idx; )
          _Arr[3 + _Idx - 1] |=
            _Arr[3 + _Idx] << _Lsh;
        ....
      }
    }

    Диагностическое сообщение, выданное PVS-Studio:

    V610 Instantiate linear_congruential < unsigned long, 40014, 0, 2147483563 >: Undefined behavior. Check the shift operator '<<. The right operand '_Lsh' is greater than or equal to the length in bits of the promoted left operand. random 738

    В этой функции, переменная '_Lsh' примет значение 32. Нельзя сдвигать 32-битные типы, более чем на 31 бит. Выдержка из стандарта: The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.

    Также, опасным образом сделан макрос DXVABitMask:
    #define DXVABitMask(__n) (~((~0) << __n))

    Процитируем соответствующую строчку из стандарта: Otherwise, if E1 has a signed type and non-negative value, and E1*2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

    Из-за этого макроса, PVS-Studio выдает несколько предупреждений. Пример:

    V610 Undefined behavior. Check the shift operator '<<. The left operand '(~0)' is negative. dxva.h 1080

    Подробнее о сдвигах и неопределенном поведении можно почитать в заметке: Не зная брода, не лезь в воду. Часть третья.

    Неучтенное изменение в поведении оператора new.


    Обнаружилось достаточно много мест, где после вызова оператора 'new', проверяется, что указатель не равен NULL. Сейчас это не имеет смысла и даже вредно. В случае ошибки выделения памяти, оператор 'new' генерирует исключение std::bad_alloc.

    Можно вызывать оператор 'new', который не генерирует исключение. В C++Builder даже есть специальный макрос для этого:
    #define NEW_NOTHROW(_bytes) new (nothrow) BYTE[_bytes]

    Однако видимо не везде удалось навести порядок с выделением памяти. Приведу несколько примеров:
    inline void _bstr_t::Assign(BSTR s) throw(_com_error)
    {
      if (m_Data != NULL) {
        m_Data->Assign(s); 
      } 
      else {
        m_Data = new Data_t(s, TRUE);
        if (m_Data == NULL) {
          _com_issue_error(E_OUTOFMEMORY);
        }
      }
    }

    Диагностическое сообщение, выданное PVS-Studio:

    V668 There is no sense in testing the 'm_Data' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. comutil.h 454

    Строчка "_com_issue_error(E_OUTOFMEMORY);" никогда не выполняется. В случае ошибки будет сгенерировано исключение std::bad_alloc().
    static inline BYTE *__CorHlprNewThrows(size_t bytes)
    {
      BYTE *pbMemory = new BYTE[bytes];
      if (pbMemory == NULL)
        __CorHlprThrowOOM();
      return pbMemory;
    }

    Диагностическое сообщение, выданное PVS-Studio:

    V668 There is no sense in testing the 'pbMemory' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. corhlpr.h 56
    template<class TYPE, class ARG_TYPE>
    void CDXArray<TYPE, ARG_TYPE>::SetSize(int nNewSize, int nGrowBy)
    {
      ....
      TYPE* pNewData = (TYPE*) new BYTE[nNewMax * sizeof(TYPE)];
    
      // oh well, it's better than crashing
      if (pNewData == NULL)
        return;
      ....
    }

    Диагностическое сообщение, выданное PVS-Studio:

    V668 There is no sense in testing the 'pNewData' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. dxtmpl.h 338

    Остальные фрагменты кода похожи, и приводить их смысла нет. Ограничусь только диагностическими сообщениями:
    • V668 There is no sense in testing the 'p' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. d3dx10math.inl 1008
    • V668 There is no sense in testing the 'p' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. dxtmpl.h 123
    • V668 There is no sense in testing the 'pNewData' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. dxtmpl.h 395
    • V668 There is no sense in testing the 'm_pHashTable' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. dxtmpl.h 1126
    • V668 There is no sense in testing the 'newBrush' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdiplusbrush.h 44
    • V668 There is no sense in testing the 'retimage' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdiplusbrush.h 374
    • V668 There is no sense in testing the 'argbs' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdiplusbrush.h 615
    • V668 There is no sense in testing the 'argbs' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdiplusbrush.h 645
    • V668 There is no sense in testing the 'argbs' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdipluspath.h 1196
    • V668 There is no sense in testing the 'argbs' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdipluspath.h 1231
    • V668 There is no sense in testing the 'argbs' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdipluspath.h 1372
    • V668 There is no sense in testing the 'argbs' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdipluspath.h 1405
    • V668 There is no sense in testing the 'newLineCap' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdipluslinecaps.h 153
    • V668 There is no sense in testing the 'nativeRegions' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdiplusgraphics.h 1415
    • V668 There is no sense in testing the 'newRegion' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdiplusregion.h 89
    • V668 There is no sense in testing the 'nativeFamilyList' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdiplusfontcollection.h 57
    • V668 There is no sense in testing the 'newImage' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdiplusbitmap.h 334
    • V668 There is no sense in testing the 'bitmap' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdiplusbitmap.h 819
    • V668 There is no sense in testing the 'bitmap' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gdiplusbitmap.h 862
    • V668 There is no sense in testing the 'm_pData' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. spcollec.h 266
    • V668 There is no sense in testing the 'pNewData' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. spcollec.h 325

    И это только в inline-функциях! Представляю, что творится в *.cpp файлах. :)

    Примечание


    В тот момент, когда я закончил писать эту статью, вышел Embarcadero C++Builder XE4. Впрочем, это не отменяет пользу от проделанного анализа и хорошо демонстрирует возможности PVS-Studio. Плюс я ещё немножко подождал с публикацией этой статьи. Сегодня мы выпустили новую версию PVS-Studio, которая уже поддерживает C++Builder XE4. Это хороший повод для публикации и предложения попробовать наш анализатор кода.

    Заключение


    Спасибо всем за внимание. Надеюсь, разработчики C++Builder обратят на нас внимание и захотят проверить с помощью PVS-Studio исходные коды компилятора и библиотек. В заключении, хочу предложить вниманию несколько полезных ссылок:
    1. Описание инструмента PVS-Studio. Можно скачать полнофункциональную пробную версию.
    2. Андрей Карпов. C++Builder, сборка 64-битных приложений и ренессанс Viva64.
    3. Наш твиттер @Code_Analysis. Публикуем много интересных ссылок по тематике Си/Си++.
    4. О том, что умеет PVS-Studio. Ошибки, обнаруженные в Open Source проектах разработчиками PVS-Studio с помощью статического анализа.
    PVS-Studio
    Static Code Analysis for C, C++, C# and Java

    Comments 33

      +1
      The exception will be generated in the case of memory allocation error.
      Скажите пожалуйста, если вы знаете, что будет вызвано конкретное исключение, то почему вы просто не напишите какое? Мне кажется, разработчику будет проще вспомнить какой try...catch блок написать.
        +1
        Промахнулся. Ответ.
          +3
          Вообще предложение попросту безграмотное. "An The exception will be generated in the case of memory allocation error"

          Наймите нормального корректора, лучше всего носителя языка.

          И вообще, не «generated », а «thrown»
            +3
            Ну и вот это тоже по-русски английскими словами.

            «There is no sense in testing the 'm_Data' pointer against null, as the memory was allocated using the 'new' operator»

            По-английски нужно так

            "There is no sense in Testing the 'm_Data' pointer against null makes no sense, as the memory was allocated using the operator 'new'"
              0
              А концовка не

              "… using the 'new' operator"


              должна быть?
                –1
                Не нужен там артикль, operator 'new' — это фактически имя собственное.
                  0
                  Подождите, ведь the относится к оператору, которому нужен артикль.
                  Либо надо написать using 'new', либо как выше. Во всяком случае меня так учили.
                    –1
                    Смотрите ответ ниже про «The boy Vasya»
                      +1
                      Спасибо.
                      Однако, странно… Если просто ткнуть в ссылки на документацию от МС или Википедию, то там есть не только the new operator, но и the C++ language.
                      Даже книжка Бьярна называется the C++ programming language. Получается, что с точки зрения имени оператора — всё правильно.
              0
              Ну и

              «Testing the 'm_Data' pointer ...»


              Существительные же
                –1
                То же самое, 'm_Data' — указывает на конкретый поинтер (извините за тавтологию). Вы же не говорите «The boy Vasya is playing with his ball»
                  +2
                  Естественно, ведь правильно говорить «playing with his balls».
                  (Извините, не удержался =))
        +2
        Можно конечно, хотя особенной надобности не вижу. А зачем ему вспоминать какой try..catch писать? Это неправильный подход. Тогда уж лучше старый добрый malloc() и вперёд. :)
          0
          malloc не любит инициализировать классы, поэтому для С++ его не стоит использовать. Для этого как раз и существует конструкция new(std::nothrow) MyClass(...)
            +2
            Это понятно. Кстати, для случая new(std::nothrow) мы и не ругаемся. Я имею в виду, что код, подобный приведенному ниже, очень плох.

            int *a = 0;
            int *b = 0;
            FILE *f = 0;
            try
            {
              int *a = new int[10];
              int *b = new int[10];
              f = fopen(...);
              ....
              delete [] a;
              a = 0;
              delete [] b;
              b = 0;
              fclose(f);
              f = 0;
            }
            catch (...)
            {
              delete [] a;
              delete [] b;
              fclose(f);
            }
            


            Следует использовать умные указатели и т.п. Тогда никаких catch(...) не надо. Только где-то выше по уровню. Поэтому я и удивился, зачем здесь catch.
              –1
              Скажите пожалуйста, а вы выдаёте предупреждения на строки вида?
              int *a = 0;
              
              Я понимаю, что это С++ и такое присваивание легально, но может быть лучше советовать либо NULL, либо nullptr?
                +2
                Такой диагностики нет. И сомневаюсь, что появится. Подобные диагностики портят инструменты статического анализа. Анализатор выдаст очень много сообщений в некоторых проектах. И большинство программистов, будут считать их ложными (бессмысленными). Можно возразить, что непонравившуюся диагностику можно легко отключить. Однако, инструмент который выдает по умолчанию огромный поток бестолковых сообщений, не понравится на этапе знакомства. Программист смотрит в список сообщений, а там муть. Отключил одно сообщение, второе, а там всё муть и муть. Скорее всего, на этом знакомство и закончится.

                Значит, делать такую диагностику в основном наборе правил никак нельзя. Хорошо, у нас есть «пользовательские» правила, которые по умолчанию выключены. Сейчас такими правилами являются: V2001-V2009 (см. документацию).

                Но и туда, я бы не стал добавлять это правило. В силу огромного количества срабатываний в некоторых проектах, лог будет очень быстро расти. Сделаешь одно подобное правило, вроде не страшно. А сделаешь 5, и лог вдруг уже вырос 2 раза. А это замедление загрузки отчета, торможение при использовании фильтров для сообщений и так далее.
          +6
          Сейчас по старой доброй традиции, обязательно будет вопрос, уведомили ли мы разработчиков. Ответ: Да.
            –1
            Наверное стоило указать, что обнаружены ошибки в заголовках библиотечных именно, а не «Errors detected in C++Builder (PVS-Studio)».
            Передам привет питерским ребятам из Embarcadero.
              0
              Там в Description:
              We have checked the header files from the Embarcadero C++Builder XE3 project...
            +3
            Помнится, пару лет назад в каком-то топике «Чего бы вы хотели добавить в PVS-Studio?» я описывал как раз случай «Неудачный макрос» и мне ответили, что фиг его знает, может и сделаем когда его детектирование. А ты ж смотри — действительно сделали!
              +2
              It is possible that curly brackets are missing.

              У меня просто нет слов. Не оборачивать макрос скобками — это детский сад.
                0
                > Обнаружилось достаточно много мест, где после вызова оператора 'new', проверяется, что указатель не равен NULL.
                Эта вещь наверное не относится к стандарту и, возможно, отступает от его буквы — тяжёлое наследие. Однако у всех компиляторов C++, которые я видел, есть ключ «запретить использование исключений C++». Впрочем, современные компиляторы, точнее стандартная библиотека C++ поставляемая с компилятором, плюёт на этот ключ (она ведь скомпилирована с исключениями и по другому быть не должно), однако в истории каких только вариантов не было.

                Вот с использованием этого ключа поведение может отличаться: начиная от честного предупреждения или ошибки, что для использования такой конструкции исключения должны быть разрешены, до тихой проверки на NULL (как в приведённом примере). Скорее поведение библиотечных функций при отключенных исключениях должно быть чётко документированно.
                  +1
                  Допускаю. Но мне кажется, в большинстве случае всё проще. Местами поправили, а местами нет. Вот и всё. :)
                  +5
                  Хотел бы я посмотреть на код PVS-Studio в плане архитектуры ) Феерическая сложность, наверное
                    +2
                    Думаю, по сравнению со многими проектами, в PVS-Studio нет ничего сложного. Самой сложной частью, я бы пожалуй назвал механизм, который пытается угадать диапазон возможных значений в переменной.

                    Сложность анализатора в другом. Это огромное количество условий и паттернов, которые следует учитывать. По большой части, диагностические правила представляют собой леса запутанных if-ов. Идея диагностики часто проста и быстро программируется. А потом начинается настоящая работу по отсечению ложных срабатываний. Вот если так человек писал, то скорее всего не ошибка. И если тут число маленькое, то тоже не ошибка. А что такое маленькое число? А еще не ругаться, если это макрос. А если вот так, то наоборот уровень предупреждения поднять.

                    И начинается множество прогонов по базе open-source проектов, чтобы свести число ложных срабатываний к адекватному минимуму. Собственно, часто настройка правила отнимает в 10-20 раз больше времени, чем написание первоначального кода.

                    Конечно, из такого моего описания мало что понятно. Чтобы пояснить глубину омута, приведу комментарий, сделанный к диагностике V512. Эта полезная диагностика до сих пор дает много ложных срабатываний, хотя сделана масса исключений. Аналогичная ситуация со многими другими проверками.

                    /*
                    Генерирует предупреждение V512.
                    
                    ДЛЯ ФУНКЦИЙ MEMCPY, MEMCHR, MEMCMP, MEMMOVE, MEMSET:
                    
                    Опасным следует считать заполнение или копирование массива, если размер заполнения
                    не кратен размеру (или меньше размера) элементов массива.
                    Если указатель, получается при помощи операции взятия адреса & или это массив заданного размера,
                    то размер должен быть точно равен.
                    
                    Что такое, AA, BB, CC:
                    Это аргументы с номером 1,2,3.
                    xxxx(AA, BB, CC)
                    
                    ИСКЛЮЧЕНИЯ:
                    
                    1) Если мы лезем в середину буфера, то скорее всего мы знаем что делаем и
                       если находится underflow, то это не ошибка
                       Пример: memcmp(p+7, "AA", 2);
                    2) Мы копируем в буфер (или сравниваем буфер) строку меньшего размера.
                       Пример: char vendor[16]; memcpy(vendor, "Unknown", 8);
                    3) Мы копируем/сравниваем в буфер (но не memset и не memcmp) меньше чем его размер:
                       sizeof(AA) < CC
                       При этом размер данных определен как sizeof(простой тип/массив простых типов): CC = sizeof(простой тип).
                       При этом размер второго аргумента, совпадает с заданным размером: sizeof(BB) == CC
                       А адрес куда записывается (АА), не взят с помощью '&'. А то пропустим: memset(&size_t_var, 0, sizeof(int_var));
                    4) Мы делаем memset/memcpy/memmove, но на N байт меньше, чем размер буфера.
                       А следующая строчками дозаписываем в конец какие-то значения.
                       Признаки: в следующих строках есть AA, CC [+N], =.
                       Из 'CC' может отсутствовать '*' и 'sizeof(...)'
                    5) У нас underflow и при этом в следующей строке вновь используется эта функция и тот-же буфер.
                       memcpy(temp, status_flag, 2);
                       memcpy(&temp[2], status, 2);
                       Как вариант, в следующей строке к указателю прибавляется количество переданных байт.
                       memcpy(temp, status_flag, 2);
                       temp += 2;
                    6) Различные сравнения со строками. В целом можно сформулировать так.
                       Безопасно сравнивать что-то с массивом типа char/wchar фиксированного размера, если
                       мы сравниваем количество байт равное размеру строки (с учетом или без учёта терминального 0).
                       Примеры:
                       1) png_byte chunk_name[5]
                          png_byte png_IDAT[5] = { 73,  68,  65,  84, '\0'}
                          if (!png_memcmp(png_ptr->chunk_name, png_IDAT, 4))
                       2) char fileStart[5];
                          static const char header[] = "12345";
                          if(!memcmp(header, fileStart, 5))
                    7) Третий аргумент содержит в себе название "BITMAPINFO" или "RGNDATA".
                    8) Это проверка производителя процессора: if (memcmp(&result._ebx, "GenuineIntel", 12) == 0)
                       Признаки: сравниваем 12 байт и есть что-то из: AMDisbetter!, AuthenticAMD и т.д. (http://en.wikipedia.org/wiki/CPUID)
                    10) Размер массива равен 1/2 байта и первый/второй аргумент содержит в себе:
                        .bmiColors, ->bmiColors, .FileSystemName, ->FileSystemName
                    11) Копирование данных в рамках одного буфера. memmove(a, a+1, x);
                    12) Если указатели вычисляются и все размеры не совпадают, то ошибки нет.
                        Пример: memcpy (&(ProcessorName[4]), &(CPUExtendedIdentity[1]), sizeof (int));
                    13) Если проверяем структуру и нет переполнения. И сравниваем что-то с константной строкой.
                        typedef struct {
                          char ID[4];
                          uint32 info;
                        } UNIF_HEADER;
                        if(memcmp(&unhead,"UNIF", 4)) 
                    14) Мы явно обрабатываем только первый элемент массива.
                        int A[10]; if (!memcmp(&A[0], "1234", sizeof(int)));
                    15) Если мы ищем что-то в строке с помощью memchr, игнорируя последний 0.
                    16) Мы копируем в строку типа char/uchar несколько байт, а в следующей строке записываем терминальный 0.
                        Пример: memcpy(text, P, 12);
                                text[12] = '\0';
                        Признаки: a) запись в строку типа char/uchar
                                  b) нет переполнения
                                  c) в следующей строке есть AA, CC, =, '\0'
                    17) Адрес буфера берется с помощью оператора & у переменной типа класс, у которого
                        есть operator &.
                    18) Не связываемся со структурами, в конце которых есть массив из одного элемента.
                        Пример: char buf[1];
                        (Исключение реализовано с помощью информации, передаваемой в переменной oneElemArrayAtEnd).
                    19) Включается при заданном расширении "_UNDERFLOW_OFF"/"_OVERFLOW_OFF".
                        Не считаем опасным underflow / overflow.
                    20) Джедайская работа с многомерными массивами вида: T A[4][4]; memcpy(&A[0][0], src, 16*sizeof(T));
                    Пример:
                    struct foo { 
                      size_t a;
                    };
                    foo x;
                    memset(&x, 0, sizeof(unsigned));
                    
                    
                    ДЛЯ ФУНКЦИЙ STRCPY, WCSCPY, LSTRCPYA, LSTRCPYW, QSTRCPY, STRCAT
                    
                    Опасно копировать/объединять строку в буфер меньшего размера.
                    При этом копируема строка явно представлена строковым литералом.
                    
                    ИСКЛЮЧЕНИЯ:
                    
                    1) Длина буфера равна 1. Такой прием используется при работе со структурами переменной длины.
                    
                    ДЛЯ ФУНКЦИЙ STRNCPY, WCSNCPY
                    
                    Опасно копировать строку в буфер меньшего размера, чем указано в count.
                    Пример:
                        char szTarget[3];
                        char *szState ="PVS";    
                        strncpy(szTarget, szState, 3); 
                    */
                    

                      0
                      Не менее «ветвист» бывает у нас и код плагинов.

                      ЕСЛИ это плагин для VS2005 ИЛИ VS2005, ТО
                      ...
                      ЕСЛИ для VS2010 ИЛИ 2012, ТО
                      ...
                      ЕСЛИ для C++Builder XE3 UPDATE1, ТО
                      ... а ЕСЛИ еще и включен 64-битный компилятор в C++Builder, ТО
                      ....
                      

                        0
                        ООП (полиморфизм) — не? Не для правил (понятно, что там слишком сложно), для плагинов.
                          0
                          Конечно все это есть и активно используется :-). Но любой полиморфизм в конечном итоге все-равно превращается в кучу «ЕСЛИ, ТО».
                            0
                            Не любой и не всегда. Если у вас просто разный код для 4-ех разных IDE, то это вполне себе укладывается в концепцию «один базовый класс с общими методами и 4 наследника с кастомным кодом для отдельных IDE». Конечно, «ЕСЛИ, ТО» в коде тоже будет, но уже не по поводу разделения функционала по IDE.
                    +1
                    Макрос без фигурных скобок на пару с if убил насмерть. Они это тестом явно не покрыли. Вы ведь понимаете, что TVariant в результате от Currency* инициализируется с ошибкой для nullptr.
                      0
                      Громковатый стиль заголовка за «заметки». Искажает ожидания от содержимого.

                      Only users with full accounts can post comments. Log in, please.