<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Блоги &#187; Andrey Karpov</title>
	<atom:link href="http://software.intel.com/ru-ru/blogs/author/andrey-karpov/feed/" rel="self" type="application/rss+xml" />
	<link>http://software.intel.com/ru-ru/blogs</link>
	<description></description>
	<lastBuildDate>Thu, 24 May 2012 12:16:29 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.3</generator>
		<item>
		<title>Коллекция интересных ресурсов по тематике программирования на языке Си/Си++</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/05/16/2007556/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/05/16/2007556/#comments</comments>
		<pubDate>Wed, 16 May 2012 09:13:49 +0000</pubDate>
		<dc:creator>Andrey Karpov</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[cpp]]></category>
		<category><![CDATA[си плюс плюс]]></category>
		<category><![CDATA[Си++]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/05/16/2007556/</guid>
		<description><![CDATA[Если вы присутствуете на одном из этих сайтов, то приглашаю следовать за мной. Со временем вам попадется немало интересных статей. А чтобы заинтересовать вас, я решил сделать небольшую подборку материалов.]]></description>
			<content:encoded><![CDATA[<p>Я постоянно изучаю интернет на тему новых статей про программированию на языке Си/Си++/Си++11. Если мне они кажутся интересными, я делюсь ссылками на них в твиттере <a href="https://twitter.com/Code_Analysis" target="_blank">@Code_Analysis</a>, в реддите <a href="http://www.reddit.com/r/viva64/" target="_blank">/r/Viva64</a> и голосую в <a href="http://www.stumbleupon.com/stumbler/AndreyKarpov2010" target="_blank">StumbleUpon</a>. Если вы присутствуете на одном из этих сайтов, то приглашаю следовать за мной. Со временем вам попадется немало интересных статей. А чтобы заинтересовать вас, я решил сделать небольшую подборку материалов.</p>
<p>В первую очередь я, конечно, хочу порекомендовать наш сайт viva64.com. Здесь можно найти много <a href="http://www.viva64.com/ru/articles/">статей</a> и записей в <a href="http://www.viva64.com/ru/b/">блоге</a>, касающихся разработке качественного кода. Например, предлагаю познакомиться с <a href="http://www.viva64.com/ru/l/">28 уроками</a>, посвященными разработке 64-битных приложений на языке Си/Си++.</p>
<p>Для удобства я разбил ссылки на несколько тем. Желаю приятного чтения.</p>
<p><i>Примечание. Как правило, мы оборачиваем ссылки, чтобы контролировать их целостность. Про это бесстрашное сражение с хаосом, я писал в статье "<a href="http://www.viva64.com/ru/b/0075/"><i>Д'Артаньян и интернет, или работа над проблемой битых ссылок</i></a>". К сожалению, в этот раз я буду давать прямые ссылки. Эти материалы существенно увеличат базу ссылок, а значит, существенно прибавят нам работы в случае перемещения статей.</i></p>
<h2>Си++ живее всех живых</h2>
<ul>
<li>Большой список известных проектов, которые разрабатываются на языке Си/Си++: <a href="http://www2.research.att.com/~bs/applications.html" target="_blank">C++ Applications</a>. И после этого кто-то говорит, что язык Си++ вымирает?</li>
<li><a href="http://shootout.alioth.debian.org/u32/which-programming-languages-are-fastest.php" target="_blank">Computer Language Benchmarks Game</a>. Как всегда по скорости лидируют языки Fortran, Си и Си++. И вряд ли что-то в обозримом времени изменится. Если вы разрабатываете ресурсоемкое программное обеспечение, то Си++ может оказаться оптимальным выбором.</li>
<li><a href="http://quant.stackexchange.com/questions/1764/why-is-c-still-a-very-popular-language-in-quantitative-finance">Why is C++ still a very popular language in quantitative finance?</a></li>
<li><a href="http://lambda-the-ultimate.org/node/663">Why do they program in C++?</a></li>
<li><a href="http://www.cpprocks.com/2012/05/07/9-reasons-to-start-using-c11/">9 reasons to start using C++11</a></li>
</ul>
<h2>Интересные блоги</h2>
<ul>
<li><a href="http://stevehanov.ca/blog/" target="_blank">Steve Hanov's Blog</a></li>
<li><a href="http://blogs.msdn.com/b/vcblog/" target="_blank">Visual C++ Team Blog</a></li>
<li><a href="http://blogs.msdn.com/b/oldnewthing/" target="_blank">The Old New Thing</a></li>
</ul>
<h2>Арифметика с плавающей запятой</h2>
<ul>
<li><a href="http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html">What Every Computer Scientist Should Know About Floating-Point Arithmetic</a></li>
<li><a href="http://randomascii.wordpress.com/2012/01/11/tricks-with-the-floating-point-format/">Tricks With the Floating-Point Format</a></li>
<li><a href="http://www.altdevblogaday.com/2012/01/21/stupid-float-tricks/" target="_blank">Stupid Float Tricks</a></li>
<li><a href="http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/" target="_blank">Comparing Floating Point Numbers</a>, 2012 Edition</li>
</ul>
<h2>Разные приёмы при программировании</h2>
<ul>
<li><a href="http://graphics.stanford.edu/~seander/bithacks.html" target="_blank">Bit Twiddling Hacks</a></li>
<li><a href="http://www.macieira.org/blog/2011/07/table-driven-methods-with-no-relocations/">Table-driven methods with no relocations</a></li>
<li><a href="">Reduce Compilation Dependencies</a> in Large Scale C++ Projects: Factory Pattern</li>
<li><a href="http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html">Exceptions in C with Longjmp and Setjmp</a></li>
<li>Catching Integer Overflows in C. Article <a href="http://www.fefe.de/intof.html">N1</a>, <a href="">N2</a></li>
<li><a href="http://www.endofunctor.org/~rpearl/blog/cool-c-tricks.html" target="_blank">Cool C Tricks</a></li>
</ul>
<h2>О кодировках символов и управляющих символах</h2>
<ul>
<li><a href="http://triptico.com/docs/unicode.html">Unicode, UTF-8 and all that</a>: An intentionally incomplete character set introduction for hasty C programmers</li>
<li><a href="http://code.alexreisner.com/articles/character-encoding.html">What the xxxx Is UTF-8?</a> A Character Encoding Primer.</li>
<li><a href="http://www.oocities.org/dtmcbride/tech/charsets/ascii.html" target="_blank">ASCII Characters</a></li>
<li><a href="http://en.wikipedia.org/wiki/Control_character" target="_blank">Control character</a></li>
<li>The <a href="http://www.utf8everywhere.org/" target="_blank">UTF-8 Everywhere</a> manifesto</li>
</ul>
<h2>Стандарты кодирования</h2>
<ul>
<li><a href="http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml" target="_blank">Google C++ Style Guide</a></li>
<li>CERT. <a href="https://www.securecoding.cert.org/confluence/display/seccode/CERT+C+Secure+Coding+Standard">C Secure Coding Standard</a>, <a href="">C++ Secure Coding Standard</a></li>
<li><a href="http://www2.research.att.com/~bs/JSF-AV-rules.pdf">Joint Strike Fighter (F-35) C++ Coding Standard</a></li>
</ul>
<h2>Группы</h2>
<ul>
<li>Reddit: <a href="http://www.reddit.com/r/cpp/" target="_blank">Cpp</a>, <a href="http://www.reddit.com/r/C_Programming/" target="_blank">C_Programming</a>.</li>
<li>Facebook: <a href="http://www.facebook.com/groups/CPPProgramming/">C++ Programming</a>, <a href="">For the love of C++</a>, <a href="http://www.facebook.com/groups/11366002065/">C Programming</a>, <a href="http://www.facebook.com/groups/2248174480/">Visual C++ Developer</a>, <a href="http://www.facebook.com/groups/2204920930/">C Plus Plus (C++)</a></li>
<li>LinkedIN: <a href="http://www.linkedin.com/groups?home=&amp;gid=133720&amp;trk=anet_ug_hm">C++ Community Group</a>, <a href="http://www.linkedin.com/groups?home=&amp;gid=87434&amp;trk=anet_ug_hm">C++ Developers Forum</a>, <a href="">C++ Developers Group</a>, <a href="http://www.linkedin.com/groups?home=&amp;gid=100895&amp;trk=anet_ug_hm">C++ Professionals</a></li>
</ul>
<h2>Разное</h2>
<ul>
<li><a href="http://www.conifersystems.com/whitepapers/gnu-make/">What's Wrong With GNU make?</a></li>
<li>Почему strncpy не безопаснее strcpy: <a href="http://blog.liw.fi/posts/strncpy/">strncpy? just say no</a></li>
<li>A Guide to Undefined Behavior in C and C++. Part <a href="http://blog.regehr.org/archives/213" target="_blank">N1</a>, <a href="http://blog.regehr.org/archives/226" target="_blank">N2</a>, <a href="http://blog.regehr.org/archives/232" target="_blank">N3</a></li>
<li>Компиляция 64-битных приложений в Visual C++ 2008 Express Edition. <a href="http://jenshuebel.wordpress.com/2009/02/12/visual-c-2008-express-edition-and-64-bit-targets/" target="_blank">Visual C++ 2008 Express Edition And 64-Bit Targets</a></li>
<li><a href="http://msdn.microsoft.com/en-us/library/aa290049%28VS.71%29.aspx">Windows Data Alignment on IPF, x86, and x64</a></li>
<li>C/C++ Low Level Curriculum. Part <a href="http://www.altdevblogaday.com/2011/11/09/a-low-level-curriculum-for-c-and-c/">N1</a>, <a href="http://www.altdevblogaday.com/2011/11/24/c-c-low-level-curriculum-part-2-data-types/">N2</a>, <a href="">N3</a>, <a href="http://www.altdevblogaday.com/2011/12/24/c-c-low-level-curriculum-part-4-more-stack/">N4</a>, <a href="">N5</a>, <a href="http://www.altdevblogaday.com/2012/03/07/c-c-low-level-curriculum-part-6-conditionals/">N6</a>, <a href="http://www.altdevblogaday.com/2012/04/10/cc-low-level-curriculum-part-7-more-conditionals/">N7</a>, <a href="">N8</a></li>
<li>Ссылки на различные ресурсы по тематике Си++. <a href="Internet%20sites%20and%20files%20of%20interest%20to%20C++%20users">Список</a> ссылается на много старых ресурсов, но его стоит посмотреть.</li>
<li><a href="http://www.altdevblogaday.com/2012/04/26/functional-programming-in-c/" target="_blank">Functional Programming in C++</a></li>
<li><a href="http://goldns.ru/cppmap-2012.png" target="_blank">The C++ Lands</a> (забавная картинка)</li>
<li><a href="http://www.cdecl.org/" target="_blank">Translate</a> C declaration</li>
<li><a href="http://dashmorethan.wordpress.com/2012/03/26/arbitrary-data-sizes/" target="_blank">Arbitrary Data Sizes with C</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/05/16/2007556/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Не зная брода, не лезь в воду. Часть третья.</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/04/19/2007119/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/04/19/2007119/#comments</comments>
		<pubDate>Thu, 19 Apr 2012 10:24:40 +0000</pubDate>
		<dc:creator>Andrey Karpov</dc:creator>
				<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[cpp]]></category>
		<category><![CDATA[си плюс плюс]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/04/19/2007119/</guid>
		<description><![CDATA[Продолжу рассказы о том, как программисты ходят по краю, даже не подозревая об этом. Поговорим об операциях сдвига &#60;&#60;, &#62;&#62;. Принципы работы операторов сдвига очевидны и многие программисты даже не знают, что их использование согласно стандарту Си/Си++ может  приводить к неопределенному или к неуточненному поведению (undefined behaviour/unspecified behavior).]]></description>
			<content:encoded><![CDATA[<p>Продолжу рассказы о том, как программисты ходят по краю, даже не подозревая об этом. Поговорим об операциях сдвига &lt;&lt;, &gt;&gt;. Принципы работы операторов сдвига очевидны и многие программисты даже не знают, что их использование согласно стандарту Си/Си++ может  приводить к неопределенному или к неуточненному поведению (undefined behaviour/unspecified behavior).</p>
<p>Предыдущие статьи доступны здесь: [<a href="http://www.viva64.com/ru/b/0127/">1</a>], [<a href="http://www.viva64.com/ru/b/0129/">2</a>].</p>
<h3>Исторический экскурс</h3>
<p>В начале немного истории. Необходимость операций сдвига битов очевидна любому  программисту. Рано или поздно каждый сталкивается с необходимостью работать с отдельными битами и битовыми масками. Тем не менее, операции сдвига намного более популярны, чем должны были быть. Причина в том, что используя сдвиги можно умножать и делить числа на степень двойки. Например, операция "X &lt;&lt; 3" умножит X на 8. Преимущество такого способа умножения и деления заключалось в скорости их работы в прошлом.</p>
<p>Сейчас я достал с пыльной полки книжку с описанием ассемблерных команд для процессоров, начиная с 8086 и заканчивая 80486. И нашёл таблицу о количестве тактов, необходимых для выполнения различных инструкций.</p>
<p>Умножение 16-битного регистра на ячейку памяти с использованием инструкции MUL на процессоре 8086 требует порядка 124-139 тактов!</p>
<p>Сдвиг 16-битного регистра на N позиций с использованием инструкции SHL на процессоре 8086 требует 8+4*N тактов. То есть в худшем случае получается 72 такта.</p>
<p>Можно было получить существенное ускорение при вычислении арифметических выражений используя различные трюки при работе с битовыми операциями. Это и стало причиной массового использования сдвигов, в начале, на языке ассемблер, потом в языках Си и Си++. Первые компиляторы Си/Си++ были просты. Человек мог получить выигрыш в скорости, явно подсказав компилятору, что здесь следует использовать сдвиг, а не инструкции умножения или деления.</p>
<p>С развитием процессоров польза от использования сдвигов долгое время сохранялась. В 80486 процессоре умножение стало занимать около 26 тактов. Вроде намного лучше. Однако сдвиг стал занимать всего 3 такта и вновь, был более привлекателен, чем умножение.</p>
<p>К счастью эти вынужденные оптимизации в большинстве своём канули в небытие. Во-первых, компиляторы стали умны и используют оптимальный набор инструкций для вычисления арифметических выражений. Во-вторых, процессоры также колоссально изменились. Появились конвейеры, предсказание переходов, переименование регистров и много чего ещё. Поэтому сказать, сколько времени займет выполнение той или иной инструкции обыкновенный программист уже не в состоянии. Но ясно, что если код местами будет не идеален, этого можно даже не заметить. Процессор разобьет инструкции на микроинструкции и начнет выполнять их параллельно. Если честно, то я уже не разбираюсь, как сейчас там всё происходит. Я понял, что разбираться в тонкостях бессмысленно, начиная с процессора Intel Pentium. И сделал вывод, что не стоит думать, что ты лучше знаешь, как нужно писать оптимизирующий код, использовать сдвиги и битовые операции, где только можно. Далеко не факт, что в результате код будет быстрее, чем сделает это оптимизатор в компиляторе. Зато точно можно сказать, что программа станет запутанной и сложной для понимания.</p>
<p>Примечание. Вышесказанное не значит, что использование битовых операций больше не может приносить выгоды. Есть много интересных и полезных трюков [<a href="http://www.viva64.com/go.php?url=837">3</a>]. Главное не увлекаться.</p>
<h3>Неопределенное поведение</h3>
<p>Началось всё с того, что я решил увеличить в анализаторе PVS-Studio количество предупреждений связанных с undefined behaviour [<a href="http://www.viva64.com/go.php?url=663">4</a>] и unspecified behavior [<a href="http://www.viva64.com/go.php?url=738">5</a>].  Достаточно быстро и просто было реализовано правило, выявляющее некорректное использование операций сдвига. После этого мне пришлось остановиться и задуматься.</p>
<p>Оказалось, что программисты очень любят сдвиги. И используют их всевозможнейшим способом, приводящим часто с точки зрения стандарта к неопределенному поведению. Но одно дело теория, а другое практика. Есть ли смысл ругаться на код, который верой и правдой служил десятилетия и пережил уже не один компилятор? Сложный вопрос. Не смотря на то, что код некорректен, компиляторы следуют некому негласному соглашению и обрабатывают его единообразным способом.</p>
<p>После долгих раздумий, я всё-таки решил оставить данное диагностическое правило в PVS-Studio, не делая из него исключений. Если будет слишком много жалоб от пользователей, то возможно я изменю своё мнение. Впрочем, возможно пользователи удовлетворятся возможностью выключить эту диагностику или используют другие <a href="http://www.viva64.com/ru/d/0021/">методы</a> подавления предупреждений.</p>
<p>Кстати, именно эти душевные терзания и побудили меня написать статью. Надеюсь информация, которую я покажу, будет интересна и полезна.</p>
<p>Итак, посмотрим, что написано в стандарте C++11 по поводу операторов сдвига:</p>
<p><i>The shift operators &lt;&lt; and &gt;&gt; group left-to-right.</i></p>
<p><i>shift-expression &lt;&lt; additive-expression</i></p>
<p><i>shift-expression &gt;&gt; additive-expression</i></p>
<p><i>The operands shall be of integral or unscoped enumeration type and integral promotions are performed.</i></p>
<p><i>1. The type of the result is that of the promoted left operand. 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.</i></p>
<p><i>2. The value of E1 &lt;&lt; E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1 * 2^E2, reduced modulo one more than the maximum value representable in the result type. 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.</i></p>
<p><i>3. The value of E1 &gt;&gt; E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a non-negative value, the value of the result is the integral part of the quotient of E1/2^E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.</i></p>
<p>Читать подобные тексты грустно и печально. Но не волнуйтесь. Сейчас мы рассмотрим различные некорректные ситуации на примерах.</p>
<p>Самый простой случай приводящий к неопределенному поведению, это когда правый операнд имеет отрицательное значение. Пример:</p>
<pre name="code" class="cpp">int A = 10;
int B = A &lt;&lt; -5;</pre>
<p>Так, слава богу, никто не делает. По крайней мере, проанализировав более 70 open-source проектов, мы не встретили подобных ошибок.</p>
<p>Следующий случай гораздо интереснее. Это сдвиг на N бит, где N, большее, чем количество бит в левом операнде. Простейший пример:</p>
<pre name="code" class="cpp">int A = 10;
int B = A &lt;&lt; 100;</pre>
<p>Посмотрим, как такая ошибка может выглядеть на практике. Следующий фрагмент кода был обнаружен нами в библиотеке Lib7z:</p>
<pre name="code" class="cpp">SZ_RESULT
SafeReadDirectUInt64(ISzInStream *inStream, UInt64 *value)
{
  int i;
  *value = 0;
  for (i = 0; i &lt; 8; i++)
  {
    Byte b;
    RINOK(SafeReadDirectByte(inStream, &amp;b));
    *value |= ((UInt32)b &lt;&lt; (8 * i));
  }
  return SZ_OK;
}</pre>
<p>Диагностическое сообщение PVS-Studio: V610 Undefined behavior. Check the shift operator '&lt;&lt;. The right operand ('(8 * i)' = [0..56]) is greater than or equal to the length in bits of the promoted left operand. lib7z 7zin.c 233</p>
<p>Функция пытается побайтно прочитать 64-битное значение. К сожалению, у неё это не получится, если число было больше 0x00000000FFFFFFFF. Обратите внимание на сдвиг "(UInt32)b &lt;&lt; (8 * i)". Размер левого операнда составляет 32 бита. При этом сдвиг происходит от 0 до 56 бит. На практике это приведёт к тому, что старшая часть 64-битного значения останется заполнена нулями. Теоретически здесь вообще имеет место неопределенное поведение и результат непредсказуем.</p>
<p>Корректный код должен выглядеть так:</p>
<pre name="code" class="cpp">*value |= ((UInt64)b &lt;&lt; (8 * i));</pre>
<p>У читателя может возникнуть вопрос, а корректен ли код приведенный ниже?</p>
<pre name="code" class="cpp">char A = 1;
int B = A &lt;&lt; 20;</pre>
<p>Да, корректен. Слева от оператора &lt;&lt; находится переменная A, состоящая только из 8 бит. Но перед началом операции сдвига, левая часть будет расширена до типа int. Соответственно, значение типа 'int' можно сдвинуть на 20 бит.</p>
<p>А теперь самый интересный момент. Это сдвиг отрицательных значений. Простейший пример:</p>
<pre name="code" class="cpp">int A = (-1) &lt;&lt; 5; // undefined behavior
int B = (-1) &gt;&gt; 5; // unspecified behavior</pre>
<p>В этом коде имеет место неопределённое и неуточненное поведение. С практической точки зрения разницы нет. Вывод один - так писать нельзя.</p>
<p>На этом можно было бы поставить точку и привести пару примеров. К сожалению, есть два нюанса, которые портят идеальную картину мира.</p>
<h3>Нюансы, которые портят идеальную картину мира</h3>
<p><b>Нюанс N1. </b> В старом стандарте языка Си++ от 1998 года ситуации с неопределенным поведением обходятся стороной. Сказано, как ведет себя оператор &lt;&lt; при сдвиге беззнаковых значений. А про знаковые значения ничего не сказано. В общем, тот самый случай, когда чтение стандарта не прибавляет ясности. Не рассматривается такой случай и всё.</p>
<p>Итак, с точки зрения Си++ от 1998 года, конструкция "(-1) &lt;&lt; 5" не приводит к неопределенному поведению. Впрочем, как она должна работать, тоже не описывается.</p>
<p><b>Нюанс N2.</b> Программисты смело во многих программах сдвигают отрицательные значения. И спорить с ними будет сложно, ведь код работает.</p>
<p>Попробуем разобраться, следует ли из-за названных нюансов отказаться от новой диагностики. Мы думаем, что нет.</p>
<p>В старом стандарте Си++ и не сказано про неопределенное поведение. В новом сказано. Получается, что старый стандарт просто был недостаточно точен. Кстати, в новом стандарте языка Си (я смотрел черновик от 25 июня 2010),  также сказано, что сдвиги отрицательных значений приводят к неопределенному поведению. Вывод - следует избавиться от некорректного кода.</p>
<p>Теперь по поводу повсеместного использования опасных сдвигов. Их действительно много. Например, в библиотеке JPEG необходимо заполнить массив следующими значениями: </p>
<pre name="code" class="cpp">11...11111111111111b
11...11111111111101b
11...11111111111001b
11...11111111110001b
....</pre>
<p>Это записано так:</p>
<pre name="code" class="cpp">/* entry n is (-1 &lt;&lt; n) + 1 */
static const int extend_offset[16] = { 0,
  ((-1)&lt;&lt;1) + 1, ((-1)&lt;&lt;2) + 1, ((-1)&lt;&lt;3) + 1,
  ((-1)&lt;&lt;4) + 1, ((-1)&lt;&lt;5) + 1, ((-1)&lt;&lt;6) + 1,
  ((-1)&lt;&lt;7) + 1, ((-1)&lt;&lt;8) + 1, ((-1)&lt;&lt;9) + 1,
  ((-1)&lt;&lt;10) + 1, ((-1)&lt;&lt;11) + 1, ((-1)&lt;&lt;12) + 1,
  ((-1)&lt;&lt;13) + 1, ((-1)&lt;&lt;14) + 1, ((-1)&lt;&lt;15) + 1
};</pre>
<p>Сложно назвать библиотеку JPEG плохой. И этот код проверен временем и разными компиляторами.</p>
<p>С точки зрения стандарта его теперь следует переписать так:</p>
<pre name="code" class="cpp">static const int extend_offset[16] =
{ 0,
  ((~0u)&lt;&lt;1) | 1, ((~0u)&lt;&lt;2) | 1, ((~0u)&lt;&lt;3) | 1,
  ((~0u)&lt;&lt;4) | 1, ((~0u)&lt;&lt;5) | 1, ((~0u)&lt;&lt;6) | 1,
  ((~0u)&lt;&lt;7) | 1, ((~0u)&lt;&lt;8) | 1, ((~0u)&lt;&lt;9) | 1,
  ((~0u)&lt;&lt;10) | 1, ((~0u)&lt;&lt;11) | 1, ((~0u)&lt;&lt;12) | 1,
  ((~0u)&lt;&lt;13) | 1, ((~0u)&lt;&lt;14) | 1, ((~0u)&lt;&lt;15) | 1
};</pre>
<p>Но стоит ли вносить такие правки решать вам. Я могу только дать совет всё-таки это сделать. Неизвестно когда и как, это может себя проявить.</p>
<p>Можно привести другие примеры сдвигов отрицательных значений в разных программах. Но они все однотипны, так что читать про них будет не интересно.</p>
<h3>Выводы</h3>
<ol>
<li>Раньше использование битовых операций и сдвигов было признаком мастерства программиста и позволяло писать быстрый код. Сейчас это почти не актуально. Намного важнее, чтобы код был понятным. Используйте игры с битами только в случае реальной необходимости.</li>
<li>Выражения вида "(-1) &lt;&lt; N" теперь объявлены некорректными и производящими к неопределенному поведению.</li>
<li>Выражения вида "(-1) &lt;&lt; N" давно и часто используются. Поэтому сложно привести реальные аргументы против использования таких конструкций. Аргументом являются только новые стандарты языков Си и Си++. </li>
<li>Решайте сами, исправить сдвиг отрицательных значений или нет. Но я рекомендую исправить. Хотя бы на всякий случай.</li>
<li>Диагностические сообщения, касающиеся опасных сдвигов будут доступны в <a href="http://www.viva64.com/ru/pvs-studio/">PVS-Studio</a> начиная с версии 4.60, которая скоро выйдет.</li>
</ol>
<h3></h3>
<h3>Дополнительные ресурсы</h3>
<ol>
<li>Не зная брода, не лезь в воду. Часть первая. <a href="http://www.viva64.com/ru/b/0127/">http://www.viva64.com/ru/b/0127/</a></li>
<li>Не зная брода, не лезь в воду. Часть вторая. <a href="http://www.viva64.com/ru/b/0129/">http://www.viva64.com/ru/b/0129/</a></li>
<li>Sean Eron Anderson. Bit Twiddling Hacks. <a href="http://www.viva64.com/go.php?url=837">http://www.viva64.com/go.php?url=837</a></li>
<li>Wikipedia. Неопределённое поведение. <a href="http://www.viva64.com/go.php?url=663">http://www.viva64.com/go.php?url=663</a></li>
<li>Wikipedia. Неуточняемое поведение. <a href="http://www.viva64.com/go.php?url=738">http://www.viva64.com/go.php?url=738</a></li>
<li>Алёна C++. Разница между unspecified behavior и undefined behavior. <a href="http://www.viva64.com/go.php?url=739">http://www.viva64.com/go.php?url=739</a></li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/04/19/2007119/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PVS-Studio vs IPP Samples (part 3)</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/04/19/pvs-studio-vs-ipp-samples-part-3/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/04/19/pvs-studio-vs-ipp-samples-part-3/#comments</comments>
		<pubDate>Thu, 19 Apr 2012 10:24:14 +0000</pubDate>
		<dc:creator>Andrey Karpov</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[c plus plus]]></category>
		<category><![CDATA[cpp]]></category>
		<category><![CDATA[IPP Sample]]></category>
		<category><![CDATA[PVS-Studio]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/04/19/pvs-studio-vs-ipp-samples-part-3/</guid>
		<description><![CDATA[Меня не покидает желание продать команде разработчиков Intel Performance Primitives Library лицензию на PVS-Studio :)]]></description>
			<content:encoded><![CDATA[<p>Меня не покидает желание продать команде разработчиков Intel Performance Primitives Library лицензию на PVS-Studio <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .</p>
<p>Конечно, в другие подразделения Intel я тоже хочу сделать продажи. Просто разработчики IPP кажутся ближе. Во-первых, часть из них находится в России. Во-вторых, они уже применяют в работе статические анализаторы. В-третьих, у нас уже есть задел в данном направлении. Я имею в виду, что мы уже проверяли два раза проект IPP Samples (<a href="http://www.viva64.com/ru/a/0069/">первая проверка</a>, <a href="http://www.viva64.com/ru/b/0112/">вторая проверка</a>). Конечно, намного было бы интереснее проверить не примеры, а саму библиотеку IPP, но к её исходным кодам у нас нет доступа.</p>
<p>Итак, пытаться прорекламировать себя я буду стандартной методикой. Мы будем продолжать время от времени перепроверять IPP Samples  и находить новые ошибки. Это говорит о том, что анализатор кода PVS-Studio активно развивается. Хотя конечно основная ценность его применения не в разовых запусках, а в регулярном использовании.  Не стоит забывать про возможности <a href="http://www.viva64.com/ru/d/0006/">ночных запусков</a> и <a href="http://www.viva64.com/ru/d/0218/">фоновый анализ</a> после компиляции. </p>
<p>Привожу некоторые новые ошибки, выявленные с помощью <a href="http://www.viva64.com/ru/pvs-studio/">PVS-Studio</a> v4.60. Конечно, я привожу не все подозрительные места. Их гораздо больше, но я затрудняюсь понять, являются они ошибочными или нет. Заодно это повод разработчикам IPP установить и попробовать наш инструмент. Мы изменили триальную модель, и теперь инструмент обладает полной функциональностью. Попробуйте.</p>
<p>Итак, рассмотрим подозрительные места в коде IPP Samples.</p>
<p><b>Ситуация N1. Бессмысленное высказывание</b> </p>
<pre name="code" class="cpp">AACStatus bsacdecSetNumChannels(Ipp32s channelConfiguration,
                                AACDec *state)
{
  state-&gt;com.m_channel_number = channelConfiguration;
  if (channelConfiguration == 7) {
    state-&gt;com.m_channel_number;
  }

  return AAC_OK;
}</pre>
<p>Диагностическое сообщение PVS-Studio: V607 Ownerless expression 'state-&gt;com.m_channel_number'. aac_dec aac_dec_api_fp.c 1404</p>
<p>Высказывание "state-&gt;com.m_channel_number;" очень странное. Возможно, здесь забыто присваивание или что-то ещё.</p>
<p><b>Ситуация N2. Заполнение таблицы виртуальных методов</b> </p>
<p>В IPP Samples встречается достаточно много мест, где память для классов выделяется с помощью функции malloc() и инициализируется с помощью функции memset(). Может в этом и нет ничего страшного, но смущает то, что у этих классов есть виртуальные методы. Таким образом, есть опасения, что таблица виртуальных методов может быть испорчена, и что-то будет работать не так.</p>
<p>Например, класс _MediaDataEx содержит виртуальные функции:</p>
<pre name="code" class="cpp">virtual bool TryStrongCasting(....) const;
virtual bool TryWeakCasting(....) const;</pre>
<p>А вот как создаётся объект этого класса:</p>
<pre name="code" class="cpp">Status VC1Splitter::Init(SplitterParams&amp; rInit)
{
  MediaDataEx::_MediaDataEx *m_stCodes;
  ...
  m_stCodes = (MediaDataEx::_MediaDataEx *)
    ippsMalloc_8u(
      START_CODE_NUMBER*2*sizeof(Ipp32s)+
      sizeof(MediaDataEx::_MediaDataEx));
  ...
  memset(m_stCodes, 0,
   (START_CODE_NUMBER*2*sizeof(Ipp32s)+
   sizeof(MediaDataEx::_MediaDataEx)));
  ...
}</pre>
<p>Диагностическое сообщение PVS-Studio: V598 The 'memset' function is used to nullify the fields of '_MediaDataEx' class. Virtual method table will be damaged by this. vc1_spl umc_vc1_spl.cpp 131</p>
<p>Не знаю, есть здесь беда или нет, но предупредить стоит.</p>
<p>Аналогичную картину можно наблюдать в следующих местах:</p>
<p>V598 The 'memset' function is used to nullify the fields of '_MediaDataEx' class. Virtual method table will be damaged by this. vc1_dec umc_vc1_video_decoder.cpp 641 False</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_DISASSEMBLING_CONTEXT' class. Virtual method table will be damaged by this. avs_enc umc_avs_enc_slice.cpp 45</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_DISASSEMBLING_CONTEXT' class. Virtual method table will be damaged by this. avs_enc umc_avs_enc_slice.cpp 29</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_DISASSEMBLING_CONTEXT' class. Virtual method table will be damaged by this. avs_enc umc_avs_enc_slice.cpp 22</p>
<p>V598 The 'memcpy' function is used to copy the fields of 'AVSVideoEncoderParams' class. Virtual method table will be damaged by this. avs_enc umc_avs_enc.cpp 115</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_DECODING_CONTEXT' class. Virtual method table will be damaged by this. avs_dec umc_avs_dec_slice_init.cpp 65</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_DEBLOCKING_CONTEXT' class. Virtual method table will be damaged by this. avs_common umc_avs_slice.cpp 153</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_RECONSTRUCTING_CONTEXT' class. Virtual method table will be damaged by this. avs_common umc_avs_slice.cpp 133</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_DEBLOCKING_CONTEXT' class. Virtual method table will be damaged by this. avs_common umc_avs_slice.cpp 43</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_RECONSTRUCTING_CONTEXT' class. Virtual method table will be damaged by this. avs_common umc_avs_slice.cpp 42</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_DECODING_CONTEXT' class. Virtual method table will be damaged by this. avs_common umc_avs_slice.cpp 41</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_DEBLOCKING_CONTEXT' class. Virtual method table will be damaged by this. avs_common umc_avs_slice.cpp 32</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_RECONSTRUCTING_CONTEXT' class. Virtual method table will be damaged by this. avs_common umc_avs_slice.cpp 31</p>
<p>V598 The 'memset' function is used to nullify the fields of 'AVS_DECODING_CONTEXT' class. Virtual method table will be damaged by this. avs_common umc_avs_slice.cpp 30</p>
<p><b>Ситуация N3. Проверка указателей после использования</b></p>
<p>Было найдено несколько мест, где указатель в начале используется, а затем проверяется на нулевое значение. Возможно, нулевым указатель никогда не будет и код всегда работает корректно, но всё равно это не порядок.</p>
<p>Пример:</p>
<pre name="code" class="cpp">VIDEO_DRV_CREATE_BUFFERS_FUNC(....)
{
  ...
  VideoDrvVideoMemInfo* drv_vm = &amp;(driver-&gt;m_VideoMemInfo);
  ...
  if ((NULL == driver) || (NULL == bufs))
  {
    ERR_SET(VM_NULL_PTR, "null ptr");
  }
  ...
}</pre>
<p>Диагностическое сообщение PVS-Studio: V595 The 'driver' pointer was utilized before it was verified against nullptr. Check lines: 40, 46. video_renders drv.c 40</p>
<p>Здесь проверку указателя нужно переместить выше или совсем удалить, если ситуация "dreiver==NULL" невозможна.</p>
<p>Вот другие аналогичные примеры кода:</p>
<pre name="code" class="cpp">Ipp16s *pNewSpeech = encoderObj-&gt;stEncState.pSpeechPtrNew;
if (NULL==encoderObj || NULL==src || NULL ==dst )
  return APIGSMAMR_StsBadArgErr;</pre>
<p>Диагностическое сообщение PVS-Studio: V595 The 'encoderObj' pointer was utilized before it was verified against nullptr. Check lines: 296, 298. speech encgsmamr.c 296</p>
<pre name="code" class="cpp">m_pAVSCompressorParams = DynamicCast&lt;AVSVideoEncoderParams&gt; (pParams);
...
m_qp = m_pAVSCompressorParams-&gt;m_iConstQuant;
// check error(s)
if (NULL == m_pAVSCompressorParams)
  return UMC_ERR_NULL_PTR;</pre>
<p>Диагностическое сообщение PVS-Studio: V595 The 'm_pAVSCompressorParams' pointer was utilized before it was verified against nullptr. Check lines: 88, 91. avs_enc umc_avs_enc_fusion_core.cpp 88</p>
<p><b>Ситуация N4. Подозрительные выражения с запятыми</b></p>
<p>Есть пара ситуаций с подозрительными запятыми ','. Первый пример:</p>
<pre name="code" class="cpp">void GetIntraDCPredictors(VC1Context* pContext)
{
  DCPred.DC[13] = pC-&gt;DCBlkPred[5].DC,QurrQuant;
  ...
}</pre>
<p>Диагностическое сообщение PVS-Studio: V521 Such expressions using the ',' operator are dangerous. Make sure the expression is correct. vc1_dec umc_vc1_dec_mb_com.cpp 370</p>
<p>Быть может просто опечатка, а возможно здесь что-то не хватает.</p>
<p>Второй пример:</p>
<p>V521 Such expressions using the ',' operator are dangerous. Make sure the expression is correct. speech usc_dtmf.c 309</p>
<pre name="code" class="cpp">static int DTMF_16s(....)
{
  ...
  for (i = pIppTDParams-&gt;dtmf_fs, j = 0;
       i &lt; dtmf_frame_size+pIppTDParams-&gt;dtmf_fs, j &lt; nbytes;
       i++, j++)
}</pre>
<p>Диагностическое сообщение PVS-Studio: V521 Such expressions using the ',' operator are dangerous. Make sure the expression is correct. speech usc_dtmf.c 309</p>
<p>Это более интересный пример, чем предыдущий. По всей видимости, логическое условие составлено некорректно. Наверное, условие должно было выглядеть так:</p>
<pre name="code" class="cpp">i &lt; dtmf_frame_size+pIppTDParams-&gt;dtmf_fs &amp;&amp; j &lt; nbytes</pre>
<p><b>Ситуация N5. Подозрительное неявное приведение типа</b></p>
<pre name="code" class="cpp">class MeMV
{
public:
  MeMV(){};
  MeMV(int a0){x = (Ipp16s)a0; y=(Ipp16s)a0;};
  MeMV(int a0, int a1){x = (Ipp16s)a0; y=(Ipp16s)a1;};
  ...
}

MeMV MePredictCalculatorVC1::GetPrediction8x8()
{
  ...
  default:
    return false;
  ...
}</pre>
<p>Диагностическое сообщение PVS-Studio: V601 The 'false' value becomes a class object. me umc_vec_prediction.cpp 754</p>
<p>Функция GetPrediction8x8() возвращает тип MeMV. Однако в одной из ветвей, она возвращает значение 'false'. Это значение неявно приводится int и вызывается конструктор MeMV(int a0). Я не знаю, но возможно здесь необходимо вернуть что-то ещё или сгенерировать исключение.</p>
<p>Аналогичное неявное приведение типа присутствует здесь:</p>
<p>V601 The 'false' value becomes a class object. me umc_vec_prediction.cpp 717</p>
<p><b>Ситуация N6. Неопределенное поведение</b></p>
<p>В очень многих местах IPP Samples встречаются конструкции, которые приводят к неопределенному или неуточненному поведению. О некоторых я писал в предыдущей заметке. Теперь обнаружилось огромное количество сдвигов отрицательных значений. Я не берусь утверждать, что это может создать где-то проблемы, но предлагаю на всякий случай обратить внимание на эту статью: "<a href="http://www.viva64.com/ru/b/0142/">Не зная брода, не лезь в воду. Часть третья</a>".</p>
<p>Где располагается потенциально опасный код можно посмотреть в этом файле: <a href="http://www.viva64.com/external-pictures/txt/ipp-samples-ub.txt">ipp-samples-ub.txt</a>.</p>
<p><b>Заключение</b></p>
<p>Уважаемые разработчики IPP и IPP Samples, мы ждем ваших писем. Мы готовы обсудить и реализовать недостающий функционал в инструменте PVS-Studio, который препятствует его использованию. Мы также готовы реализовать критичные для вашего проекта диагностические правила.</p>
<p>Всем остальным желаю безглючного кода и приглашаю в твиттер <a href="https://twitter.com/Code_Analysis" target="_blank">@Code_Analysis</a>. В нём мы размещаем ссылки на интересные статьи про Си++, программирование, статический анализ кода и инструмент PVS-Studio.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/04/19/pvs-studio-vs-ipp-samples-part-3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Новые статьи</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/03/13/2006992/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/03/13/2006992/#comments</comments>
		<pubDate>Tue, 13 Mar 2012 12:47:36 +0000</pubDate>
		<dc:creator>Andrey Karpov</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>
		<category><![CDATA[c plus plus]]></category>
		<category><![CDATA[Си++]]></category>
		<category><![CDATA[статический анализ кода]]></category>
		<category><![CDATA[статьи]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/03/13/2006992/</guid>
		<description><![CDATA[Я разместил на сайте ISN несколько новых статей.]]></description>
			<content:encoded><![CDATA[<p>Я разместил на сайте ISN несколько новых статей. К сожалению, в статьи заглядывает значительно меньше читателей, чем в блоги. Поэтому я решил сделать небольшой обзор, чтобы привлечь к ним внимание.</p>
</p>
<p><a href="http://software.intel.com/ru-ru/articles/wade-not-in-unknown-waters-part-one-ru/" target="_blank"><b>Не зная брода, не лезь в воду. Часть первая.</b></a></p>
<p><b>Аннотация.</b> Программисты - ленивые существа. Поэтому норовят решить задачу минимальным количеством кода. Это похвальное и хорошее стремление. Главное не увлечься процессом и вовремя остановиться. Например, программистам бывает лень создавать единую функцию инициализации в классе, чтобы затем вызывать её из разных конструкторов. Программист думает: "Зачем мне лишняя функция? Я лучше вызову один конструктор из другого". К сожалению, даже эту простую задачу программисту удается решить не всегда.</p>
</p>
<p><a href="http://software.intel.com/ru-ru/articles/wade-not-in-unknown-waters-part-two-ru/" target="_blank"><b>Не зная брода, не лезь в воду. Часть вторая.</b></a></p>
<p><b>Аннотация. </b> В этот раз я хочу поговорить о функции printf. Все наслышаны об уязвимостях в программах, и что функции наподобие printf объявлены вне закона. Но одно дело знать, что лучше не использовать эти функции. А совсем другое - понять почему. В этой статье я опишу две классических уязвимости программ, связанных с printf. Хакером после этого вы не станете, но, возможно, по-новому взгляните на свой код. Вдруг, вы реализуете аналогичные уязвимые функции, даже не подозревая об этом.</p>
<p><b>СТОП</b>. Подожди читатель, не проходи мимо. Я знаю, что ты увидел слово printf. И уверен, что автор статьи сейчас расскажет банальную историю о том, что функция не контролирует типы передаваемых аргументов. Нет! Статья будет не про это, а именно про уязвимости. Заходи почитать.</p>
</p>
<p><a href="http://software.intel.com/ru-ru/articles/Static-code-analysis-ru/" target="_blank"><b>Статический анализ кода</b></a></p>
<p><b>Аннотация.</b> В этой статье я описал, как я понимаю методологию "Статического анализа кода". Я считаю, что статический анализ можно рассматривать как автоматизированный процесс обзора кода.</p>
</p>
<p>P.S. Приглашаю в мой твиттер: <a href="https://twitter.com/Code_Analysis">@Code_Analysis</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/03/13/2006992/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PVS-Studio: анализируем код Doom 3</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/11/30/pvs-studio-doom-3/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/11/30/pvs-studio-doom-3/#comments</comments>
		<pubDate>Wed, 30 Nov 2011 06:09:16 +0000</pubDate>
		<dc:creator>Andrey Karpov</dc:creator>
				<category><![CDATA[Игры]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[c plus plus]]></category>
		<category><![CDATA[doom 3]]></category>
		<category><![CDATA[PVS-Studio]]></category>
		<category><![CDATA[статический анализ кода]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/11/30/pvs-studio-doom-3/</guid>
		<description><![CDATA[Компания id Software имеет лицензию на PVS-Studio. Тем не менее, мы решили проверить исходные коды Doom 3, которые недавно были выложены в сеть.]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.viva64.com/external-pictures/habr91/image1.jpeg" alt="PVS-Studio VS Doom3"></p>
<p>Компания id Software имеет лицензию на PVS-Studio. Тем не менее, мы решили проверить исходные коды Doom 3, которые недавно были выложены в сеть. Результат - ошибок найдено мало, но всё-таки найдено. Я предполагаю, что это можно объяснить так.</p>
<p>Часть кода Doom3 используется и сейчас и, наверное, там ошибки уже исправлены. Часть кода устарела и не используется. Скорее всего, именно там и найдены подозрительные участки кода.</p>
<p>Для тех, кто интересуется данной тематикой, предлагаю вниманию фрагменты кода, на которые указал анализатор PVS-Studio. Как всегда напоминаю, что рассматриваю только некоторые предупреждения. Другие участки проекта требуют знания структуры программы, и я их не изучал.</p>
<p>Исходный код Doom 3 опубликован на <a href="http://www.viva64.com/go.php?url=757">GitHub</a> и на официальном <a href="http://www.viva64.com/go.php?url=758">FTP</a> компании под лицензией GPL v3. Для анализа я использовал инструмент <a href="http://www.viva64.com/en/pvs-studio/">PVS-Studio</a> 4.39. Кряки для программы прошу не искать. Я уже встречал трояны, замаскированные под ключи и кряки к PVS-Studio. Лучше <a href="http://www.viva64.com/en/about-feedback/">напишите</a> нам, и мы дадим пробный ключ на некоторое время.</p>
<h2>Фрагмент 1. Подозрительное условие.</h2>
<pre name="code" class="cpp">#define BIT( num ) ( 1 &lt;&lt; ( num ) )
const int BUTTON_ATTACK = BIT(0);
void idTarget_WaitForButton::Think( void ) {
  ...
  if ( player &amp;&amp;
      ( !player-&gt;oldButtons &amp; BUTTON_ATTACK ) &amp;&amp;
      ( player-&gt;usercmd.buttons &amp; BUTTON_ATTACK ) ) {
  ...
}</pre>
<p>Диагностическое сообщение PVS-Studio: </p>
<blockquote><p>V564 The '&amp;' operator is applied to bool type value. You've probably forgotten to include parentheses or intended to use the '&amp;&amp;' operator. Game target.cpp 257</p></blockquote>
<p>Обратите внимание на фрагмент "!player-&gt;oldButtons &amp; BUTTON_ATTACK". Здесь хотели проверить, что самый младший бит равен 0. Но <a href="http://www.viva64.com/ru/t/0064/">приоритет</a> оператора '!' выше, чем оператора '&amp;'. Это значит, что условие работает следующим образом:</p>
<pre name="code" class="cpp">(!player-&gt;oldButtons) &amp; 1</pre>
<p>Получается, что условие истинно, только если все биты равны нулю. Корректный вариант кода:</p>
<pre name="code" class="cpp">if ( player &amp;&amp;
    ( ! ( player-&gt;oldButtons &amp; BUTTON_ATTACK ) ) &amp;&amp;
    ( player-&gt;usercmd.buttons &amp; BUTTON_ATTACK ) ) {</pre>
<h2>Фрагмент 2. Подозрительный цикл.</h2>
<pre name="code" class="cpp">void idSurface_Polytope::FromPlanes(...)
{
  ...
  for ( j = 0; j &lt; w.GetNumPoints(); j++ ) {
    for ( k = 0; k &lt; verts.Num(); j++ ) {
      if ( verts[k].xyz.Compare(w[j].ToVec3(),
                                POLYTOPE_VERTEX_EPSILON ) ) {
        break;
      }
    }
    ...
  }
  ...
}</pre>
<p>Диагностическое сообщение PVS-Studio: </p>
<blockquote><p>V533 It is likely that a wrong variable is being incremented inside the 'for' operator. Consider reviewing 'j'. idLib surface_polytope.cpp 65</p></blockquote>
<p>Вложенный цикл увеличивает переменную 'j', а не 'k'. Переменная 'k' вообще не увеличивается. Последствия работы такого цикла непредсказуемы. Корректный вариант кода:</p>
<pre name="code" class="cpp">for ( k = 0; k &lt; verts.Num(); k++ ) {</pre>
<h2>Фрагмент 3. Другой подозрительный цикл.</h2>
<pre name="code" class="cpp">bool idMatX::IsOrthonormal( const float epsilon ) const {
  ...
  for ( int i = 0; i &lt; numRows; i++ ) {
    ...
    for ( i = 1; i &lt; numRows; i++ ) {
      ...
    }
    if ( idMath::Fabs( sum ) &gt; epsilon ) {
      return false;
    }
  }
  return true;
}</pre>
<p>Диагностическое сообщение PVS-Studio: </p>
<blockquote><p>V535 The variable 'i' is being used for this loop and for the outer loop. idLib matrix.cpp 3128</p></blockquote>
<p>Вложенный цикл организован с помощью той же переменной, что и внешний цикл. Оба цикла имеют условие остановки: i &lt; numRows. Получается, что внешний цикл всегда выполняет только одну итерацию. Для исправления кода, можно использовать другую переменную во внутреннем цикле. </p>
<h2>Фрагмент 4. Неопределенное поведение.</h2>
<pre name="code" class="cpp">int idFileSystemLocal::ListOSFiles(...)
{
  ...
  dir_cache_index = (++dir_cache_index) % MAX_CACHED_DIRS;
  ...
}</pre>
<p>Диагностическое сообщение PVS-Studio:</p>
<blockquote><p> V567 Undefined behavior. The 'dir_cache_index' variable is modified while being used twice between sequence points. TypeInfo filesystem.cpp 1877</p></blockquote>
<p>Переменная "dir_cache_index" изменяется дважды в одной <a href="http://www.viva64.com/ru/t/0065/">точке следования</a>. То, что используется префиксный инкремент, не имеет значение. Компилятор теоретически вправе создать код следующего вида:</p>
<pre name="code" class="cpp">A = dir_cache_index;
A = A + 1;
B = A % MAX_CACHED_DIRS;
dir_cache_index = B;
dir_cache_index = A;</pre>
<p>Конечно, скорее всего, выражение вычисляется правильно. Но уверенным в этом быть нельзя. На результат может влиять тип и версия компилятора, настройки оптимизации. Корректный вариант кода:</p>
<pre name="code" class="cpp">dir_cache_index = (dir_cache_index + 1) % MAX_CACHED_DIRS;</pre>
<h2>Фрагмент 5. Подозрительная очистка массива.</h2>
<pre name="code" class="cpp">void idMegaTexture::GenerateMegaMipMaps() {
  ...
  byte *newBlock = (byte *)_alloca( tileSize );
  ...
  memset( newBlock, 0, sizeof( newBlock ) );
  ...
}</pre>
<p>Диагностическое сообщение PVS-Studio: </p>
<blockquote><p>V579 The memset function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. DoomDLL megatexture.cpp 542</p></blockquote>
<p>Нулями заполняется только часть массива 'newBlock'. По всей видимости, это неправильно. Как мне кажется, раньше было написано так:</p>
<pre name="code" class="cpp">byte newBlock[ CONST_ARRAY_SIZE ];
...
memset( newBlock, 0, sizeof( newBlock ) );</pre>
<p>Потом требования изменились и размер массива 'newBlock' стал изменяться. Но про функцию его очистки забыли. Корректный вариант кода:</p>
<pre name="code" class="cpp">memset( newBlock, 0, tileSize );</pre>
<h2>Фрагмент 6. Еще одна подозрительная очистка массива.</h2>
<pre name="code" class="cpp">void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &amp;stats ) {
  ...
  memset( &amp;statex, sizeof( statex ), 0 );
  ...
}</pre>
<p>Диагностическое сообщение PVS-Studio: </p>
<blockquote><p>V575 The 'memset' function processes '0' elements. Inspect the third argument. DoomDLL win_shared.cpp 177</p></blockquote>
<p>Здесь при вызове функции 'memset' перепутаны аргументы. Функция очищает 0 байт. Это кстати весьма распространенная ошибка. Я встречал её многократно в разных проектах.</p>
<p>Корректный вызов функции:</p>
<pre name="code" class="cpp">memset( &amp;statex, 0, sizeof( statex ) );</pre>
<h2>Фрагмент 7. Здравствуй, Copy-Paste.</h2>
<pre name="code" class="cpp">void idAASFileLocal::DeleteClusters( void ) {
  ...
  memset( &amp;portal, 0, sizeof( portal ) );
  portals.Append( portal );

  memset( &amp;cluster, 0, sizeof( portal ) );
  clusters.Append( cluster );
}</pre>
<p>Диагностическое сообщение PVS-Studio: </p>
<blockquote><p>V512 A call of the 'memset' function will lead to underflow of the buffer '&amp; cluster'. DoomDLL aasfile.cpp 1312</p></blockquote>
<p>Обратите внимания, как похожи две верхние и две нижние строчки кода. Скорее всего, две последних строки были написаны с использованием технологии Copy-Paste. Это и привело к ошибке. В одном месте забыли заменить слово 'portal' на слово 'cluster'. В результате очищается только часть структуры. Корректный вариант кода:</p>
<pre name="code" class="cpp">memset( &amp;cluster, 0, sizeof( cluster ) );</pre>
<p>Я видел в коде и другие "недоочищенные" массивы, но про них писать не интересно.</p>
<h2>Фрагмент 8. Подозрительная работа с указателем.</h2>
<pre name="code" class="cpp">void idBrushBSP::FloodThroughPortals_r(idBrushBSPNode *node, ...)
{
  ...
  if ( node-&gt;occupied ) {
    common-&gt;Error( "FloodThroughPortals_r: node already occupied\n" );
  }
  if ( !node ) {
    common-&gt;Error( "FloodThroughPortals_r: NULL node\n" );
  }
  ...
}</pre>
<p>Диагностическое сообщение PVS-Studio: </p>
<blockquote><p>V595 The 'node' pointer was utilized before it was verified against nullptr. Check lines: 1421, 1424. DoomDLL brushbsp.cpp 1421</p></blockquote>
<p>Сначала указатель 'node' разименовывается: node-&gt;occupied. А затем, вдруг проверяется, не равен ли он NULL. Это очень подозрительный код. Я не знаю, как его сделать правильным, так как не знаю логику работы функции. Возможно, достаточно написать так:</p>
<pre name="code" class="cpp">if ( node &amp;&amp; node-&gt;occupied ) {</pre>
<h2>Фрагмент 9. Подозрительный формат строки.</h2>
<pre name="code" class="cpp">struct gameVersion_s {
  gameVersion_s( void )
  {
    sprintf(string, "%s.%d%s %s %s",
            ENGINE_VERSION, BUILD_NUMBER, BUILD_DEBUG,
            BUILD_STRING, __DATE__, __TIME__ );
  }
  char string[256];
} gameVersion;</pre>
<p>Диагностическое сообщение PVS-Studio:</p>
<blockquote><p> V576 Incorrect format. A different number of actual arguments is expected while calling 'sprintf' function. Expected: 7. Present: 8. Game syscvar.cpp 54</p></blockquote>
<p>Подозрительно то, что аргумент '__TIME__' никак не используется.</p>
<h2>Фрагмент 10. Код, который сбивает с толку.</h2>
<p>Неоднократно встречается код, который как я понимаю, работает правильно, но выглядит странно. Приведу только один пример такого кода.</p>
<pre name="code" class="cpp">static bool R_ClipLineToLight(..., const idPlane frustum[4], ...)
{
  ...
  for ( j = 0 ; j &lt; 6 ; j++ ) {
    d1 = frustum[j].Distance( p1 );
    d2 = frustum[j].Distance( p2 );
    ...
  }
  ...
}</pre>
<p>Для подсказки, программист написал, что массив 'frustum' состоит из 4 элементов. Но обрабатывается 6 элементов. Если посмотреть вызов 'R_ClipLineToLight', то там массив из 6 элементов. То есть всё должно работать правильно, но код заставляет задуматься.</p>
<p>Другие ошибки и недочеты, можно увидеть, запустив анализатор PVS-Studio. Кстати, пользуясь, случаем хочу передать привет Джону Кармаку и сообщить ему, что мы скоро исправим недочет, который не позволяет в полную силу использовать PVS-Studio в компании id Software.</p>
<p>Этим недочетом является низкая скорость работы анализатора. Учитывая большой объем исходного кода, с которым работает компания, это существенное ограничение. В PVS-Studio версии 4.50, которая выйдет в этом году, можно будет в качестве препроцессора использовать Clang, а не препроцессор от Visual C++. Это позволит существенно ускорить проверку проектов. Например, при использовании препроцессора от Visual C++ исходные коды Doom 3 проверяются за 26 минут. А при использовании препроцессора от Clang - за 16 минут. Пример, правда, получился не очень удачный. На большинстве проектах выигрыш по скорости анализа будет значительно сильнее.</p>
<p>Правда по умолчанию, пока придется использовать препроцессор от Visual C++. У Clang все еще есть некоторые несовместимости и недоделки, касающиеся Windows-платформы. Так что успешно удаётся проверить только 80% проектов.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/11/30/pvs-studio-doom-3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Мифы о статическом анализе. Миф пятый – можно составить маленькую программу, чтобы оценить инструмент.</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/11/15/2005687/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/11/15/2005687/#comments</comments>
		<pubDate>Tue, 15 Nov 2011 11:29:52 +0000</pubDate>
		<dc:creator>Andrey Karpov</dc:creator>
				<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[статический анализ]]></category>
		<category><![CDATA[статический анализ кода]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/11/15/2005687/</guid>
		<description><![CDATA[Продолжаем опровергать типичные и стойкие заблуждения, касающиеся методологии статического анализа. Миф пятый: "Возможности статического анализатора легко оценить, написав тестовый пример".]]></description>
			<content:encoded><![CDATA[<p>Общаясь с людьми на форумах, я заметил несколько стойких заблуждений, касающихся методологии статического анализа. Я решил <a href="http://software.intel.com/ru-ru/blogs/2011/11/05/2005616/">сделать</a> <a href="http://software.intel.com/ru-ru/blogs/2011/11/07/2005621/">небольшой</a> <a href="http://software.intel.com/ru-ru/blogs/2011/11/08/2005628/">цикл</a> <a href="http://software.intel.com/ru-ru/blogs/2011/11/10/2005641/">статей</a>, в которых попробую показать, как всё обстоит на самом деле. </p>
<p><strong>Миф пятый: "Возможности статического анализатора легко оценить, написав тестовый пример".</strong></p>
<p>Вот как выглядит такое утверждение при обсуждении (собирательный образ):</p>
<p><i>Я написал специальную программу, размером в 100 строк кода. Но анализатор ничего мне не выдает, хотя у меня включены все уровни предупреждений. Глупость этот ваш [инструмент] / [статический анализ] в целом.</i> </p>
<p>Глупостью является не методология статического анализа, а такой подход к попытке оценить пользу от использования конкретного инструмента. Некорректность исследования инструмента складывается из двух моментов:</p>
<p>1. Программисты думают, что не допускают простых ошибок. Этот феномен был рассмотрен в <a href="http://software.intel.com/ru-ru/blogs/2011/11/07/2005621/">Мифе номер 2</a>. Поэтому они стараются подсунуть анализатору хитрый пример и втайне радуются, если анализатор не смог найти ошибку. Эта игра интересна, но не имеет практического смысла. </p>
<p>Надо осознавать, что большинство ошибок до безобразия просты. И статические анализаторы хорошо их диагностируют. Парадокс в том, что придумать простую ошибку намного сложнее, чем сложную. Вот смотрите сами. Вы сможете догадаться составить вот такой пример?</p>
<pre name="code" class="cpp">int threadcounts[] = { 1, kNumThreads };
for (size_t i = 0;
     i &lt; sizeof(threadcounts) / sizeof(threadcounts); i++) {</pre>
<p>Сомневаюсь. Невозможно представить, что можно сделать такую тупую ошибку и написать "sizeof(threadcounts) / sizeof(threadcounts)". Соответственно, такой пример никогда составлен не будет.  А, между прочим, этот код не из лабораторной работы студента, а из проекта Chromium. И конечно, он прекрасно диагностируется анализатором PVS-Studio.</p>
<p>2. Составленные примеры носят случайный характер. Причем примеров мало. Поэтому получается, что в зависимости от удачи можно получить разнообразнейший результат. Можно придумать 5 ошибок, которые будут успешно найдены первым анализатором, и не обнаружены вторым. А можно придумать программу с пятью ошибками, на которых анализаторы покажут противоположные результаты. Выборка для исследования слишком мала. Чтобы хоть как-то сравнивать и изучать, нужно придумать текст программы, где имеется хотя бы 500 разных ошибок. Исследования на примерах с 5-10 ошибками ничего не дают.</p>
<p>Вдобавок, программисты ожидают увидеть диагностику определенного типа ошибок, забывая о других. Например, практически все пишут один и тот же пример, где не освобождается память:</p>
<pre name="code" class="cpp">void Foo()
{
  int *a = (int *)malloc(X);
  int *b = (int *)malloc(Y);
  //...
  free(a);
}</pre>
<p>Какие-то анализаторы диагностируют эту ошибку, какие-то нет. Например, PVS-Studio пока не работает с утечками памяти. Зато, он обнаружит такое:</p>
<pre name="code" class="cpp">static int rr_cmp(uchar *a,uchar *b)
{
  if (a[0] != b[0])
    return (int) a[0] - (int) b[0];
  if (a[1] != b[1])
    return (int) a[1] - (int) b[1];
  if (a[2] != b[2])
    return (int) a[2] - (int) b[2];
  if (a[3] != b[3])
    return (int) a[3] - (int) b[3];
  if (a[4] != b[4])
    return (int) a[4] - (int) b[4];
  if (a[5] != b[5])
    return (int) a[1] - (int) b[5];
  if (a[6] != b[6])
    return (int) a[6] - (int) b[6];
  return (int) a[7] - (int) b[7];
}</pre>
<p>Здесь вместо "return (int) a[1] - (int) b[5];" должно быть "return (int) a[5] - (int) b[5];".</p>
<p>Почему никто никогда не составляет подобные примеры? А ведь эту ошибку PVS-Studio нашел в проекте MySQL.</p>
<p><strong>Вывод</strong>. Адекватное исследование или сравнение инструментов может быть только на реальных проектах. Взяли проект A. Проверили его с помощью PC-Lint / Visual C++ / PVS-Studio / C++Test. Внимательно проанализировали все сообщения. Составили табличку, какой анализатор сколько ошибок нашел. Вот единственное настоящее изучение и сравнение. Пример: "<a href="http://www.viva64.com/ru/a/0073/">Сравнение статического анализа общего назначения из Visual Studio 2010 и PVS-Studio на примере обнаруженных ошибок в пяти открытых проектах</a> ".</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/11/15/2005687/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Мифы о статическом анализе. Миф четвёртый – программисты хотят добавлять свои правила в статический анализатор.</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/11/10/2005641/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/11/10/2005641/#comments</comments>
		<pubDate>Thu, 10 Nov 2011 08:09:57 +0000</pubDate>
		<dc:creator>Andrey Karpov</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[статический анализ]]></category>
		<category><![CDATA[статический анализ кода]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/11/10/2005641/</guid>
		<description><![CDATA[Миф четвертый: "Статический анализатор должен иметь возможность добавлять пользовательские правила. Программисты хотят добавлять свои собственные правила."]]></description>
			<content:encoded><![CDATA[<p>Общаясь с людьми на форумах, я заметил несколько стойких заблуждений, касающихся методологии статического анализа. Я решил сделать <a href="http://software.intel.com/ru-ru/blogs/2011/11/05/2005616/">небольшой</a> <a href="http://software.intel.com/ru-ru/blogs/2011/11/07/2005621/">цикл</a> <a href="http://software.intel.com/ru-ru/blogs/2011/11/08/2005628/">статей</a>, в которых попробую показать, как всё обстоит на самом деле. </p>
<p><strong>Миф четвертый: "Статический анализатор должен иметь возможность добавлять пользовательские правила. Программисты хотят добавлять свои собственные правила."</strong></p>
<p>Нет, не хотят. На самом деле они хотят решить некоторые задачи по поиску определенных  конструкций языка. И это не то же самое, что создание диагностических правил.</p>
<p>Я всегда отвечал, что реализация собственных правил это не то, что хотят программисты. И не видел другой альтернативы, кроме реализации диагностик разработчиками анализатора по просьбам программистов (<a href="http://www.viva64.com/ru/b/0110/">статья на эту тему</a>). Недавно я плотно пообщался с Дмитрием Петуниным. Он является руководителем отдела тестирования компиляторов и разработки инструментов верификации программ Intel. Он расширил мое понимание этой темы и озвучил идею, о которой я думал, но так и не сформулировал в конечном варианте.</p>
<p>Дмитрий подтвердил моё убеждение, что программисты не будут писать диагностические правила. Причина очень простая - это очень сложно. В ряде инструментов статического анализа есть возможность расширять набор правил. Но сделано это скорее для галочки или для удобства самих создателей инструмента. Требуется очень глубокое погружение в тему, чтобы разрабатывать новые диагностики. Если за это возьмется энтузиаст без опыта, то практической пользы от его правил будет мало.</p>
<p>На этом моё понимание вопроса заканчивалось. Дмитрий, обладая большим опытом, расширил его. Вкратце ситуация выглядит так.</p>
<p>Программисты действительно хотят искать некоторые паттерны/ошибки в своём коде. Им это действительно нужно. Например, человеку нужно найти вся явные приведения от типа int к float. Эту задачу нельзя решить, используя такие инструменты, как grep. Ведь в конструкции вида "float(P-&gt;FOO())" неизвестно какой тип вернет функция FOO(). В этот момент программист и приходит к выводу, что он может реализовать поиск таких конструкций, добавив свою проверку в статическом анализаторе.</p>
<p>Здесь и кроется ключевой момент. Человеку не нужно создавать свои правила анализа. Ему нужно решить частную проблему. То, что он хочет, является очень маленькой задачей с точки зрения механизмов статического анализа. Это то же самое, как использовать автомобиль, чтобы зажигать сигареты от прикуривателя.</p>
<p>Именно поэтому и я, и Дмитрий не поддерживаем идеи предоставлять пользователю API для работы с анализатором. Это крайне сложная задача с точки зрения разработки. И при этом от неё человек вряд ли будет использовать более 1%. Нерационально. Разработчику проще и дешевле реализовывать пожелания пользователей, чем создавать сложный API для модулей расширения или создавать специальный язык описания правил.</p>
<p>Читатель заметит: "тогда откройте в API только 1% от функциональности и все будут довольны". Да, всё правильно. Но смотрите, как сместился акцент. От разработки своих правил мы пришли к тому, что на самом деле достаточен инструмент, схожий с grep, но обладающий некоторой дополнительной информацией о коде программы.</p>
<p>Пока такого инструмента нет. Если вы хотите решить какую-то задачу, то можете написать мне, и мы постараемся реализовать её в анализаторе PVS-Studio. Например, недавно мы как раз реализовывали несколько пожеланий по поиску явных приведениё типов: <a href="http://www.viva64.com/ru/d/0199/">V2003</a>, <a href="http://www.viva64.com/ru/d/0200/">V2004</a>, <a href="http://www.viva64.com/ru/d/0201/">V2005</a>. Воплощать в жизнь такие пожелания нам намного проще, чем создавать и поддерживать открытый интерфейс. Проще это и самим пользователям.</p>
<p>Кстати, возможно такой инструмент со временем появится в рамках Intel C++. Дмитрий Петунин говорил, что они обсуждали возможность создания grep-подобного инструмента, обладающего знаниями о структуре кода и типах переменных. Но это обсуждалось абстрактно. Я не знаю, планируется в действительности создать такое или нет.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/11/10/2005641/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Мифы о статическом анализе. Миф третий – динамический анализ лучше чем статический.</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/11/08/2005628/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/11/08/2005628/#comments</comments>
		<pubDate>Tue, 08 Nov 2011 13:16:55 +0000</pubDate>
		<dc:creator>Andrey Karpov</dc:creator>
				<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[динамический анализ кода]]></category>
		<category><![CDATA[ошибки]]></category>
		<category><![CDATA[Си++]]></category>
		<category><![CDATA[статический анализ кода]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/11/08/2005628/</guid>
		<description><![CDATA[Миф третий: "Динамическая проверка такими инструментами, как valgrind для Си/Си++ намного продуктивнее, чем статический анализ кода".]]></description>
			<content:encoded><![CDATA[<p>Общаясь с людьми на форумах, я заметил несколько стойких заблуждений, касающихся методологии статического анализа. Я решил сделать небольшой <a href="http://software.intel.com/ru-ru/blogs/2011/11/05/2005616/">цикл</a> <a href="http://software.intel.com/ru-ru/blogs/2011/11/07/2005621/">статей</a>, в которых попробую показать, как всё обстоит на самом деле. </p>
<p><strong>Миф третий: "Динамическая проверка такими инструментами, как valgrind для Си/Си++ намного продуктивнее, чем статический анализ кода".</strong></p>
<p>Утверждение достаточно странное. Динамический и статический анализ это просто две разных методологии, которые дополняют друг друга. Программисты вроде бы понимают это. Но я вновь и вновь слышу, что динамический анализ лучше статического.</p>
<p>Перечислю сильные стороны статического анализа кода.</p>
<h2>Диагностика всех ветвей программы</h2>
<p>Динамический анализ на практике не может покрыть все ветвления программы. После этих слов поклонники valgrind заявляют, что нужно делать правильные тесты. Теоретически они правы. Но любой, кто пробовал такое осуществить, понимает всю сложность и объем работы. На практике, даже хорошие тесты покрывают не более 80% программного кода.</p>
<p>Особенно хорошо это видно в участках кода, обрабатывающие нестандартные/аварийные ситуации. Если взять старый проект, то большинство ошибок будет выявлено статическим анализатором именно в этих местах. Причина в том, что даже если проект стар, эти участки практически не протестированы. Приведу очень короткий пример, чтобы показать, что я имею в виду (проект FCE Ultra):</p>
<pre name="code" class="cpp">fp = fopen(name,"wb");
int x = 0;
if (!fp)
  int x = 1;</pre>
<p>Флаг 'x' не станет равен единице, если файл не был открыт. Именно из-за подобных ошибок, когда в программах что-то идёт не так, они вместо адекватных сообщений про ошибки падают или выдают бессмысленные сообщения.</p>
<h2>Масштабируемость</h2>
<p>Чтобы регулярно проверять большие проекты динамическими методами, необходимо создать специальную инфраструктуру. Нужны специальные тесты. Нужен параллельный запуск нескольких экземпляров приложения с различными входными данными. </p>
<p>Статический анализ масштабируется в несколько раз проще. Как правило, достаточно предоставить программе осуществляющей статический анализ машину с большим количеством ядер. </p>
<h2>Более высокоуровневый анализ</h2>
<p>У динамического анализатора есть преимущество, что он знает, какая функция с какими аргументами вызывается. Как следствие он может проверить корректность вызова. Статический анализ в большинстве случаев не может это узнать и проверить значения аргументов. Это минус. Зато статический анализ проводит более высокоуровневый анализ, чем динамический. И это позволяет ему искать такие вещи, которые с точки зрения динамического анализа корректны. Простой пример (проект ReactOS):</p>
<pre name="code" class="cpp">void Mapdesc::identify( REAL dest[MAXCOORDS][MAXCOORDS] )
{
  memset( dest, 0, sizeof( dest ) );
  for( int i=0; i != hcoords; i++ )
    dest[i][i] = 1.0;
}</pre>
<p>С точки зрения динамического анализа, здесь всё хорошо. А вот статический анализ забьет <a href="http://www.viva64.com/ru/d/0100/">тревогу</a>, так как очень подозрительно, что в массиве обнуляется столько байт, из скольких состоит указатель.</p>
<p>Или вот другой пример из проекта Clang:</p>
<pre name="code" class="cpp">MapTy PerPtrTopDown;
MapTy PerPtrBottomUp;
void clearBottomUpPointers() {
  PerPtrTopDown.clear();
}
void clearTopDownPointers() {
  PerPtrTopDown.clear();
}</pre>
<p>Что здесь может заподозрить динамический анализатор? Ничего. А статический анализатор, может заподозрить неладное. Здесь ошибка в том, что внутри clearBottomUpPointers() должно быть:</p>
<blockquote><p>PerPtrBottomUp.clear();</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/11/08/2005628/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Мифы о статическом анализе. Миф второй – профессиональные разработчики не допускают глупых ошибок.</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/11/07/2005621/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/11/07/2005621/#comments</comments>
		<pubDate>Mon, 07 Nov 2011 12:12:55 +0000</pubDate>
		<dc:creator>Andrey Karpov</dc:creator>
				<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[ошибки]]></category>
		<category><![CDATA[статический анализ]]></category>
		<category><![CDATA[статический анализ кода]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/11/07/2005621/</guid>
		<description><![CDATA[Общаясь с людьми на форумах, я заметил несколько стойких заблуждений, касающихся методологии статического анализа. Миф второй – профессиональные разработчики не допускают глупых ошибок.]]></description>
			<content:encoded><![CDATA[<p>Общаясь с людьми на форумах, я заметил несколько стойких заблуждений, касающихся методологии статического анализа. Я решил сделать небольшой <a href="http://software.intel.com/ru-ru/blogs/2011/11/05/2005616/">цикл статей</a>, в которых попробую показать, как всё обстоит на самом деле. </p>
<p><strong>Миф второй: "Профессиональные разработчики не допускают глупых ошибок, которые в основном и ловят статические анализаторы кода".</strong></p>
<p>Вот как выглядит такое утверждение при обсуждении на форуме (собирательный образ):</p>
<p><i>У меня, профессионального разработчика, уже N лет не было проблем с порчей памяти, временем жизни объектов и так далее. Статический анализ - это инструмент для "макдональдса", а здесь (на профессиональном форуме) сидят гики. Сейчас мои основные проблемы это сложно тестируемые алгоритмы и интеграция с другими разработчиками с использованием неявных контрактов по состояниям объектов. </i></p>
<p>Звучит так, как будто проблемы опечаток и ошибок по невнимательности, это удел студентов. Профессиональные разработчики их давно не делают, и основные неприятности доставляют такие сложные ошибки, как проблемы синхронизации или сложные алгоритмы обработки данных.</p>
<p>Это не так. Любые программисты делают глупые ошибки. Я знаю, что вы меня не услышали. Повторю еще раз еретические мысли. Все программисты делают глупые ошибки. Неважно, какие они профессионалы. Людям свойственно делать ошибки. И чаще всего эти ошибки просты.</p>
<p>Программисты очень недружелюбно воспринимают моё утверждение про ошибки. По их мнению, именно они не делали таких ошибок очень давно. Я думаю, здесь работает интересный аспект психики, отсеивающий воспоминание про неинтересные моменты программирования.</p>
<p>Отвлечемся немного и вспомним, почему так живучи различные гороскопы. Первая причина - это очень расплывчатые формулировки, которые человеку легко подстроить под себя. Но нам интересен второй компонент. Люди не запоминают те случаи, когда предсказание не сбылось. Зато очень хорошо помнят и пересказывают те случаи, когда их жизненная ситуация совпала с ситуацией, описанной в гороскопе. В результате получается, что, говоря и вспоминая о гороскопе, мы находим N подтверждений, что гороскопы работают и не вспоминаем про N*10 случаев, когда гороскоп не сработал.</p>
<p>Что-то аналогичное происходит с программистом, когда он занимается поиском ошибок. Он хорошо помнит про сложные и интересные ошибки. Может подискутировать о них с коллегами или написать заметку в блог. Когда же он заметит, что вместо переменной 'AB' он написал 'BA', то он просто исправит ляп, и этот факт сразу исчезнет из его памяти. Фрейд обратил внимание на следующую особенность памяти: человеку свойственно помнить положительные высказывания о себе и забывать отрицательные. Если человек сражается со сложной ошибкой в алгоритмической задаче, то когда он её исправляет, считает себя героем. Это стоит запомнить и даже рассказать другим. Когда он находит глупый баг, то помнить про это нет никакого повода и желания.</p>
<p>Какие у меня есть доказательства? Хотя большинство опечаток и ляпов исправляется, некоторые из них всё-таки остаются незамеченными. И массу примеров таких ошибок можно обнаружить в <a href="http://www.viva64.com/ru/a/0077/">этой</a> статье. В статье хорошо видно, что ошибки допускали не новички, а квалифицированные программисты.</p>
<p>Вывод. Программисты тратят на исправление опечаток гораздо больше времени, чем думают. Инструменты статического анализа могут существенно экономить усилия разработчиков, выявляя часть таких ошибок ещё до этапа тестирования.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/11/07/2005621/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Мифы о статическом анализе. Миф первый – статический анализатор это продукт разового применения</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/11/05/2005616/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/11/05/2005616/#comments</comments>
		<pubDate>Fri, 04 Nov 2011 21:58:35 +0000</pubDate>
		<dc:creator>Andrey Karpov</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[статический анализ кода]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/11/05/2005616/</guid>
		<description><![CDATA[Общаясь с людьми на форумах, я заметил несколько стойких заблуждений, касающихся методологии статического анализа. Миф первый: "Статический анализатор - это продукт разового применения".]]></description>
			<content:encoded><![CDATA[<p>Общаясь с людьми на форумах, я заметил несколько стойких заблуждений, касающихся методологии статического анализа. Я решил сделать небольшой цикл статей, в которых попробую показать, как всё обстоит на самом деле. </p>
<p>Миф первый: "Статический анализатор - это продукт разового применения".</p>
<p>Вот как выглядит такое утверждение при обсуждении на форуме (собирательный образ):</p>
<p><i>Имея пробную/крякнутую версию, можно бесплатно прогнать все свои проекты, найти несколько застаревших ошибок и, собственно, на этом успокоиться на какое-то время.</i>  </p>
<p><i>Все счастливы. Люди проверили. Разработчики анализатора не узнали, что их обманули и обокрали.</i>   </p>
<p>В данном случае программист обманул себя, а не создателей инструмента. Он получил только видимость пользы от проделанной работы, но не настоящую пользу. Пока эту мысль мне не удается донести, но я буду продолжать пытаться это сделать. Нет никакого прока от единичных запусков статического анализатора.</p>
<p>Аналогия:</p>
<p>Ставим уровень предупреждений компилятора /W0. И разрабатываем проект. Ругаемся, правим глупые ошибки и опечатки, тестируем больше и дольше. Потом изредка включаем /W3 и сражаемся с предупреждениями, а потом опять возвращаемся к /W0. При этом то, что мог подсказать компилятор на уровне /W3 мы бесстрашно и долго искали в отладчике и потратили на это в 10-100 раз больше времени. Вдобавок, обратим внимание на то, что теперь программисту не будет нравиться результат /W3. Ведь почти все ошибки он и так поправил методом тестирования и отладки. Компилятор на уровне /W3 выдает теперь в основном ложные срабатывания.</p>
<p>Теперь вернемся к статическому анализу. Картина полностью идентичная. Редкий запуск анализатора выдает массу ложных срабатываний. Настоящих ошибок мало, так как они найдены другими методами.</p>
<p>Так же, как и ключ /W3, использование статического анализа максимально полезно в случае регулярного использования. Кстати, статический анализ и есть своего рода расширение предупреждений, выдаваемых компилятором. Многие диагностики, которые когда то были реализованы в старых анализаторах, постепенно переходят в компилятор. Конечно, анализаторы всегда будут опережать в диагностическом плане компиляторы. Они разработаны для этого специально. У компилятора больше других забот и вдобавок на него накладываются более жесткие требования по производительности.</p>
<p>Некоторые в пылу дискуссии отвечают так:</p>
<p><i>Идея верна для начинающих студентов. Для специалистов это уже не так важно. Если я поставлю /W0 я хуже писать не стану. Нужно совершенствовать стиль программирования, а не увеличивать количество инструментов-костылей.</i></p>
<p>Совершенно согласен со всем написанным выше. Но давайте поиграем и вот так переделаем текст:</p>
<p><i>Идея верна для начинающих водителей. Для профессионалов это уже не так важно. Если я не пристегнусь за рулем, я хуже ездить не стану. Нужно совершенствовать стиль вождения, а не увеличивать количество страхующих компонентов.</i></p>
<p>И опять не поспоришь. Однако любой адекватно мыслящий водитель понимает, что пристегиваться все-таки полезно. Также полезен и статический анализ. Ведь от ошибок и опечаток не застрахован даже самый опытный программист. И примеры, приведенные в <a href="http://www.viva64.com/ru/a/0077/">этой</a> статье, хорошее тому подтверждение. Конечно, все профессиональные программисты уверены, что вот они-то таких глупых ошибок не делают, но про это в следующей заметке про мифы.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/11/05/2005616/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

