html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

Большой брат помогает тебе

В очередной раз убедился, что программисты пишут программы совершенно безалаберно. И работают они не благодаря их заслугам, а благодаря удачному стечению обстоятельств и заботе разработчиков компиляторов в Microsoft или Intel. Да, да, именно они заботятся и в нужный момент подставляют костылики нашим кривобоким программкам.



Молитесь, молитесь на компиляторы и их разработчиков. Они столько сил прилагают, чтобы наши программы работали, несмотря на многие недостатки и даже ошибки. Причем эта их работа трудна и не видна. Они — благородные рыцари кодирования и ангелы-покровители для всех нас.

Я знал, что в Microsoft существует отдел, который занимается вопросами обеспечения максимальной совместимости новых версий операционных систем со старыми приложениями. В их базе более 10000 наиболее известных старых программ, которые должны обязательно работать в новых версиях Windows. Именно благодаря таким усилиям я недавно смог без проблем поиграть в Heroes of Might and Magic II (игра 1996 года) под управлением 64-битной Windows Vista. Думаю, игра успешно запустится и в Windows 7. Вот интересные заметки Алексея Пахунова на тему совместимости [1, 2, 3], очень рекомендую почитать.

Но видимо существуют еще и отделы, которые занимаются тем, чтобы помочь нашему ужасному коду на Си/Си++ работать, работать и работать. Начну эту историю с самого начала.

Я участвую в разработке инструмента PVS-Studio для анализа исходного кода приложений. Тихо товарищи, тихо — это не реклама. В этот раз это точно богоугодное дело, ибо мы начали создавать бесплатный статический анализатор общего назначения. Пока даже до альфа-версии далеко, но работы потихоньку идут и когда-нибудь я сделаю про этот анализатор пост на Хабрахабр. Заговорил я об этом потому, что мы начали собирать наиболее интересные типовые ошибки и учиться их диагностировать.

Множество ошибок связано с использованием в программах эллипсисов. Теоретическая справка:

Существуют функции, в описании которых невозможно указать число и типы всех допустимых параметров. Тогда список формальных параметров завершается эллипсисом (...), что означает: «и, возможно, еще несколько аргументов». Например: int printf(const char* ...);

Одной такой неприятной, но легко диагностируемой ошибкой является передача в функцию с переменным количеством аргументов объекта типа класс, вместо указателя на строку. Вот как выглядит пример этой ошибки:

wchar_t buf[100];
std::wstring ws(L"12345");
swprintf(buf, L"%s", ws);


Такой код приведет к формированию в буфере белиберды или к аварийному завершению программы. В реальной программе конечно код будет более запутанный, поэтому просьба — не надо писать комментарии о том, что в отличие от Visual C++, компилятор GCC проверит аргументы и предупредит. Строки могут поступать из ресурсов или других функций и проверить ничего не удастся. Здесь же диагностика проста — в функцию формирования строки передается объект класса, что и приводит к ошибке.

Корректный вариант кода должен выглядеть так:

wchar_t buf[100];
std::wstring ws(L"12345");
swprintf(buf, L"%s", ws.c_str());


Именно из-за того, что в функции с переменным количеством аргументов можно передать все что угодно их и не рекомендуют использовать практически во всех книгах по программированию на языке Си++. Вместо этого предлагается использовать безопасные механизмы, например, boost::format. Однако рекомендации рекомендациями, а кода с разными printf, sprintf, CString::Format огромное количество и мы с ним будем жить еще очень долго. Именно поэтому мы и реализовали диагностическое правило, выявляющее подобные опасные конструкции.

Давайте разберемся теоретически, в чем неверен приведенный выше код. Оказывается он некорректен дважды.
  1. Несоответствие аргумента заданному формату. Раз мы указываем "%s", то и передать должны указатель на строку. Однако теоретически мы можем написать свою функцию sprintf, которая будет знать, что ей передан объект класса std::wstring и корректно распечатает его. Однако и это невозможно в силу причины номер 2.
  2. Аргументом для эллипсиса "..." может быть только POD-тип. А std::string POD типом не является.

Теоретическая справка про POD типы:

POD это аббревиатура от «Plain Old Data», что можно перевести как «Простые данные в стиле Си». К POD-типам относятся:
  1. все встроенные арифметические типы (включая wchar_t и bool);
  2. типы, объявленные с помощью ключевого слова enum;
  3. указатели;
  4. POD-структуры (struct или class) и POD-объединения (union), которые удовлетворяют нижеприведенным требованиям:
    1. не содержат пользовательских конструкторов, деструктора или копирующего оператора присваивания;
    2. не имеют базовых классов;
    3. не содержат виртуальных функций;
    4. не содержат защищенных (protected) или закрытых (private) нестатических членов данных;
    5. не содержат нестатических членов данных не-POD-типов (или массивов из таких типов), а также ссылок.

Соответственно, класс std::wstring к POD-типам не относится, так как у него есть конструкторы, базовый класс и так далее.

При этом если вы передаете в эллипсис объект, не являющийся POD типом, то это приводит к неопределенному поведению. Таким образом, по крайней мере, теоретически, мы никак не можем корректно передать объект типа std::wstring в качестве эллипсис аргумента.

Та же самая картина у нас должна наблюдаться и с функций Format из класса CString. Некорректный вариант код:

CString s;
CString arg(L"OK");
s.Format(L"Test CString: %s\n", arg);


Корректный вариант кода:

s.Format(L"Test CString: %s\n", arg.GetString());


Или как предлагается в MSDN [4] для получения указателя на строку можно использовать явный оператор приведения LPCTSTR, реализованный в классе CString. Пример корректного кода из MSDN:

CString kindOfFruit = "bananas";
int howmany = 25;
printf("You have %d %s\n", howmany, (LPCTSTR)kindOfFruit); 


Итак, вроде бы все прозрачно и понятно. Как сделать правило тоже ясно. Будем обнаруживать опечатки при использовании функций с переменным количеством аргументов.

Это и было и сделано. И вот здесь я был шокирован результатом. Оказывается большинство разработчиков вообще никогда не задумываются над этими проблемами и спокойно пишут код вида:

class CRuleDesc
{
  CString GetProtocol();
  CString GetSrcIp();
  CString GetDestIp();
  CString GetSrcPort();
  CString GetIpDesc(CString strIp);
...

CString CRuleDesc::GetRuleDesc()
{
  CString strDesc;
  strDesc.Format(
    _T("%s all network traffic from <br>%s "
       "on %s<br>to %s on %s <br>for the %s"),
    GetAction(), GetSrcIp(), GetSrcPort(),
    GetDestIp(), GetDestPort(), GetProtocol());
  return strDesc;
}
//---------------

CString strText;
CString _strProcName(L"");
...
strText.Format(_T("%s"), _strProcName);

//---------------

CString m_strDriverDosName;
CString m_strDriverName;
...
m_strDriverDosName.Format(
  _T("\\\\.\\%s"), m_strDriverName);

//---------------

CString __stdcall GetResString(UINT dwStringID);
...
_stprintf(acBuf, _T("%s"),
  GetResString(IDS_SV_SERVERINFO));

//---------------

// Думаю понятно,
// что примеры можно приводить и приводить.


А некоторые и задумываются, но забываются. И поэтому так трогательно смотрится код следующего вида:

CString sAddr;
CString m_sName;
CString sTo = GetNick( hContact );

sAddr.Format(_T("\\\\%s\\mailslot\\%s"),
  sTo, (LPCTSTR)m_sName);


И таких примеров в проектах, на которых мы тестируем PVS-Studio, оказалась столько, что стало не понятно, как это вообще может быть. А, тем не менее, это все замечательно работает, в чем я смог убедиться, написав тестовую программу и попробовав различные варианты использования CString.

В чем же дело? Видимо разработчики компиляторов не выдержали бесконечных вопросов почему программы индусов, использующие CString не работают и обвинений в «глючности компилятора, который неверно работает со строками». И они тихо совершили священный ритуал экзорцизма, изгнав зло из CString. Они сделали невозможное возможным. А именно класс CString реализован специальным хитрым образом, так, чтобы его можно было передавать в функции вида printf, Format.

Сделано это достаточно хитро и кто интересуется, то может почитать исходный код класса CStringT, а также познакомиться с вот эти развернутым обсуждением "Pass CString to printf?" [5]. Я вдаваться в подробности не буду. Отмечу только важный момент. Специальная реализация CString не достаточна, теоретически передача не POD-типа приводит к непредсказуемому поведению. Так вот разработчики Visual C++, а вместе с ними и Intel C++ сделали так, что непредсказуемое поведение представляет из себя всегда корректный результат. :) Ведь правильная работа программы вполне себе подмножество непредсказуемого поведения. :)

А еще я теперь начинаю задумываться над некоторыми странными особенностями поведения компилятора при построении 64-битных программ. Есть подозрение, что разработчики компилятора сознательно делают поведение программы не теоретическим, а практическим (работоспособным), в тех простых случаях, когда они распознают некоторый паттерн. Наиболее понятным примером может быть паттерн цикла. Пример некорректного кода:

size_t n = BigValue;
for (unsigned i = 0; i < n; i++) { ... }


Теоретически, если значение n > UINT_MAX больше, то должен возникнуть бесконечный цикл. Однако в Release версии он не возникает, так как для переменной «i» используется 64-битный регистр. Конечно, если код будет посложнее, то бесконечный цикл возникнет, но хотя бы в ряде случаев программе повезет. Подробнее я писал про это в статье "64-битный конь, который умеет считать" [6].

Раньше я думал, что такое неожиданно удачное поведение программы связано исключительно с особенностями оптимизации Release версий. Однако теперь я в этом не уверен. Возможно, это сознательная попытка хотя бы иногда сделать неработоспособную программу работоспособной. Конечно, я не знаю, причина в оптимизации или в заботе большого брата, но это волне повод пофилософствовать. :) Ну а кто знает, тот вряд ли скажет. :)

Уверен, что есть и другие моменты, когда компилятор подставляет руку программам калекам. Если попадется что-то еще интересно, обязательно расскажу.

Желаю вам безглючного кода!

Библиографический список


  1. Блог Алексея Пахунова. Обратная совместимость это серьезно. http://www.viva64.com/go.php?url=390
  2. Блог Алексея Пахунова. AppCompat. http://www.viva64.com/go.php?url=391
  3. Блог Алексея Пахунова. Windows 3.x жив? http://www.viva64.com/go.php?url=392
  4. MSDN. CString Operations Relating to C-Style Strings. Topic: Using CString Objects with Variable Argument Functions. http://www.viva64.com/go.php?url=393
  5. Обсуждение на сайте eggheadcafe.com. Pass CString to printf? http://www.viva64.com/go.php?url=394
  6. Андрей Карпов. 64-битный конь, который умеет считать. http://www.viva64.com/art-1-1-1064884779.html
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

4

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • AndreyKarpov2012
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции