Статический анализ Си++ кода и новый стандарт языка C++0x
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

Статический анализ Си++ кода и новый стандарт языка C++0x

on

  • 808 views

В статье рассмотрены новые возможности языка Си++, описанные в стандарте C++0x и поддержанные в Visual Studio 2010. На ...

В статье рассмотрены новые возможности языка Си++, описанные в стандарте C++0x и поддержанные в Visual Studio 2010. На примере PVS-Studio рассмотрено, как изменения языка отразятся на инструментах статического анализа кода.

Statistics

Views

Total Views
808
Views on SlideShare
808
Embed Views
0

Actions

Likes
0
Downloads
2
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Статический анализ Си++ кода и новый стандарт языка C++0x Document Transcript

  • 1. Статический анализ Си++ кода иновый стандарт языка C++0xАвтор: Андрей КарповДата: 01.04.2010АннотацияВ статье рассмотрены новые возможности языка Си++, описанные в стандарте C++0x иподдержанные в Visual Studio 2010. На примере PVS-Studio рассмотрено, как изменения языкаотразятся на инструментах статического анализа кода.ВведениеНовый стандарт языка Си++ вот-вот придет в нашу жизнь. Пока его продолжают именовать C++0x,хотя, по всей видимости, его окончательное название - C++11. Новый стандарт уже частичноподдерживается современными Си++ компиляторами, например Intel C++ и Visual C++. Поддержкадалеко не полна, что вполне естественно. Во-первых стандарт еще не принят, а во-вторых дажекогда он будет принят, потребуется время на проработку в компиляторах его особенностей.Разработчики компиляторов не единственные, для кого важна поддержка нового стандарта.Нововведения языка оперативно должны быть поддержаны в инструментах статического анализаисходного кода. Новый стандарт обещает обратную совместимость. Почти гарантировано старыйСи++ код будет корректно скомпилирован новым компилятором без необходимости каких-либоправок. Однако это не означает, что программа, не содержащая новые конструкции языка, сможетбыть по-прежнему обработана статическим анализатором, не поддерживающим новый стандартC++0x. Мы убедились в этом на практике, попытавшись проверить с помощью PVS-Studio проект,созданный еще в бета-версии Visual Studio 2010. Все дело в заголовочных файлах, в которых ужеиспользуются новые конструкции языка. Например, в заголовочном файле "stddef.h" можноувидеть использование нового оператора decltype:namespace std { typedef decltype(__nullptr) nullptr_t; }Естественно, что такие конструкции являются синтаксически неверными для анализатора, неподдерживающего C++0x, и приводят, либо к остановке его работы или неверным результатом.Стала очевидной необходимость поддержать C++0x в PVS-Studio к моменту выхода Visual Studio2010, по крайней мере в том объеме, в котором новый стандарт поддерживается этимкомпилятором.Можно заявить, что данная задача нами была успешно решена и на момент написания статьи, насайте доступна версия PVS-Studio 3.50, интегрирующаяся как в Visual Studio 2005/2008, так и вVisual Studio 2010. Начиная с версии PVS-Studio 3.50 в инструменте реализована поддержка тойчасти С++0x, которая реализована в Visual Studio 2010. Поддержка не идеальна, как например, приработе с "right-angle brackets", но мы продолжим работу по поддержке стандарта C++0x вследующих версиях.
  • 2. В этой статье мы рассмотрим новые возможности языка, поддержка которых реализована впервой редакции Visual Studio 2010. При этом взглянем на эти возможности с различных позиций:что представляет из себя новая возможность, имеется ли связь с 64-битными ошибками, как новаяконструкция языка была поддержана в PVS-Studio и как ее появление отразилось на библиотекеVivaCore.Примечание. VivaCore - библиотека разбора, анализа и трансформации кода. VivaCore являетсяоткрытой библиотекой и поддерживает языки Си и Си++. На основе VivaCore построенпродукт PVS-Studio и на ее же основе могут быть созданы другие программные проекты.Предлагаемую вашему вниманию статью можно назвать отчетом по исследованию и поддержкенового стандарта в PVS-Studio. Инструмент PVS-Studio диагностирует 64-битные и параллельныеOpenMP ошибки. Но поскольку в данный момент более актуальной темой является переход на 64-битные системы, предпочтение будет отдано примерам, демонстрирующих обнаружение спомощью PVS-Studio 64-битных ошибок.1. autoВ Си++, как и в Си, тип переменной должен быть указан явно. Однако, с появлением в языке Си++шаблонных типов и техник шаблонного метапрограммирования, частой стала ситуация, когда типобъекта записать не так просто. Даже в достаточно простом случае, при переборе элементовмассива, нам понадобится объявление типа итератора вида:for (vector<int>::iterator itr = myvec.begin(); itr != myvec.end(); ++itr)Подобные конструкции весьма длинны и неудобны. Для сокращения записи можно использоватьtypedef, но это порождает новые сущности и мало добавляет с точки зрения удобства.C++0x предлагает способ для смягчения этой проблемы. В новом стандарте значение ключевогослова auto будет заменено. Если раньше auto означало, что переменная создается в стеке, иподразумевалось неявно в случае, если вы не указали что-либо другое (register, к примеру), тотеперь это аналог var в C# 3.0. Тип переменной, объявленной как auto, определяетсякомпилятором самостоятельно на основе того, чем эта переменная инициализируется.Следует заметить, что auto-переменная не сможет хранить значения разных типов в течениеодного запуска программы. Си++ по прежнему остается статически типизированным языком, иуказание auto лишь говорит компилятору самостоятельно позаботиться об определении типа:после инициализации сменить тип переменной будет уже нельзя.Теперь итератор может быть объявлен следующим образом:for (auto itr = myvec.begin(); itr != myvec.end(); ++itr)Помимо удобства в написании кода и его упрощения, ключевое слово auto поможет сделать кодболее безопасным. Рассмотрим пример, где auto сделает код безопасным с точки зрениясоздания 64-битных приложений:bool Find_Incorrect(const string *arrStr, size_t n)
  • 3. { for (size_t i = 0; i != n; ++i) { unsigned n = arrStr[i].find("ABC"); if (n != string::npos) return true; } return false;};Данный код содержит 64-битную ошибку. Функция корректно ведет себя при компиляции Win32версии и дает сбой при сборке в режиме Win64. Ошибка заключается в использовании типаunsigned для переменной "n", хотя должен использоваться тип string::size_type, которыйвозвращает функция find(). В 32-битной программе тип string::size_type и unsigned совпадают, имы получаем корректные результаты. В 64-битной программе string::size_type и unsignedперестают совпадать. Когда подстрока не находится, функция find() возвращает значениеstring::npos, равное 0xFFFFFFFFFFFFFFFFui64. Это значение урезается до величины 0xFFFFFFFFu ипомещается в 32-битную переменную. В результате условие 0xFFFFFFFFu !=0xFFFFFFFFFFFFFFFFui64 истинно и получается, что функция Find_Incorrect всегда возвращает true.В данном примере ошибка не так страшна, так обнаруживается даже компилятором и тем болееспециализированным анализатором Viva64 (входящим в состав PVS-Studio).Компилятор:warning C4267: initializing :conversion from size_t to unsigned int, possible loss of dataViva64:V103: Implicit type conversion from memsize to 32-bit type.Важнее то, что данная ошибка возможна и часто встречается в коде из-за неаккуратности привыборе типа для хранения возвращаемого значения. Возможно даже, что ошибка возникла из-занежелания использовать громоздкую конструкцию вида string::size_type.Теперь подобных ошибок легко избежать, при этом не загромождая код. Используя тип "auto" мыможем написать следующий простой и надежный код:auto n = arrStr[i].find("ABC");if (n != string::npos) return true;
  • 4. Ошибка исчезла сама собой. Код не стал сложнее или менее эффективным. Вывод -использование "auto" рационально во многих случаях.Ключевое слово "auto" сократит количество 64-битных ошибок или позволит исправить ошибкиболее изящно. Но само по себе использование "auto" вовсе не избавляет от всех 64-битныхошибок! Это всего лишь еще один инструмент языка, облегчающий жизнь программиста, но неделающий за него всю работу по контролю над типами. Рассмотрим пример:void *AllocArray3D(int x, int y, int z, size_t objectSize){ int size = x * y * z * objectSize; return malloc(size);}Функция должна вычислить размер массива и выделить необходимое количество памяти.Логично ожидать, что в 64-битной среде эта функция сможет выделить память для работы смассивом размером 2000*2000*2000 типа "double". Однако вызов вида "AllocArray3D(2000, 2000,2000, sizeof(double));" всегда будет возвращать NULL, как будто выделение такого объема памятиневозможно. Настоящей же причиной, по которой функция возвращает NULL, является ошибкапереполнения в выражении "int size = x * y * z * sizeof(double)". Переменная "size" приметзначение -424509440 и дальнейший вызов функции malloc не имеет смысла. Кстати, об опасностиданного выражения предупредит и компилятор:warning C4267: initializing :conversion from size_t to int, possible loss of dataНадеясь на "auto", неаккуратный программист может модифицировать код следующим образом:void *AllocArray3D(int x, int y, int z, size_t objectSize){ auto size = x * y * z * objectSize; return (double *)malloc(size);}Однако это вовсе не устранит, а только замаскирует ошибку. Компилятор больше не выдастпредупреждение, но функция AllocArray3D по-прежнему будет возвращать NULL.Тип переменной "size" автоматически станет "size_t". Но переполнение возникает при вычислениивыражения "x * y * z". Это подвыражение имеет тип "int" и только затем тип будет расширен до"size_t" при умножении на переменную "objectSize".Теперь эту спрятавшуюся ошибку можно будет обнаружить, только используя анализатор Viva64:
  • 5. V104: Implicit type conversion to memsize type in anarithmetic expression.Вывод - используя "auto", все-равно следует быть внимательным.Теперь кратко рассмотрим, как новое ключевое слово было поддержано в библиотеке VivaCore,на которой и построен статический анализатор Viva64. Итак, анализатор должен уметь понять, чтопеременная AA имеет тип "int", чтобы, предупредить (см. V101) о расширении переменной АА дотипа "size_t":void Foo(int X, int Y){ auto AA = X * Y; size_t BB = AA; //V101}Прежде всего, была составлена новая таблица лексем, которая включила новые ключевые словаC++0x. Эта таблица находится в файле Lex.cc и имеет имя tableC0xx. Для того чтобы немодифицировать старый код по обработке лексемы "auto" (tkAUTO), лексема "auto" в этойтаблице имеет имя tkAUTOcpp0x.В связи с появлением новой лексемы модификации подверглись следующие функции:isTypeToken, optIntegralTypeOrClassSpec. Появился новый класс LeafAUTOc0xx. В TypeInfoIdпоявился новый класс объектов - AutoDecltypeType.Для кодирования типа "auto" выбрана литера x, что нашло отражение в функциях классовTypeInfo и Encoding. Это, например, такие функции как IsAutoCpp0x, MakePtree.Эти исправления позволяют разбирать код с ключевым "auto", имеющим новый смысл исохранять тип объектов в закодированном виде (литера x). Однако это не позволяет узнать,какой тип в действительности представляет переменная. То есть в VivaCore отсутствуетфункциональность, позволяющая узнать, что в выражении "auto AA = X * Y" переменная AA будетиметь тип "int".Данная функциональность содержится в исходном коде Viva64 и не включается в состав кодабиблиотеки VivaCore. Принцип заключается в дополнительной работе по вычислению типа вметоде TranslateAssignInitializer. После того, как вычислена правая часть выражения происходитподмена связи (Bind) имени переменной с типом.2. decltypeВ ряде случаев полезно "скопировать" тип некоторого объекта. Ключевое слово "auto" выводиттип, основываясь на выражении, используемом для инициализации переменной. Еслиинициализация отсутствует, то для определения типа выражения во время компиляции можетбыть использовано ключевое слово "decltype". Пример кода, где переменная "value" будет иметьтип, возвращаемый функцией "Calc()":decltype(Calc()) value;
  • 6. try { value = Calc();}catch(...) { throw;}Можно использовать "decltype" для объявления типа:void f(const vector<int>& a, vector<float>& b){ typedef decltype(a[0]*b[0]) Tmp; for (int i=0; i<b.size(); ++i) { Tmp* p = new Tmp(a[i]*b[i]); // ... }}Учтите, что тип, взятый с использованием decltype, может отличаться от типа, выведенного спомощью auto.const std::vector<int> v(1);auto a = v[0];decltype(v[0]) b = 1;// тип a - int// тип b - const int& (возвращаемое значение// std::vector<int>::operator[](size_type) const)Перейдем к примеру, где "decltype" может быть полезен с точки зрения 64-битности. ФункцияIsPresent ищет элемент в последовательности и возвращает "true" если он найден:bool IsPresent(char *array, size_t arraySize, char key){ for (unsigned i = 0; i < arraySize; i++)
  • 7. if (array[i] == key) return true; return false;}Данная функция неспособна работать в 64-битной системе с большими массивами. Еслипеременная arraySize будет иметь значение больше UINT_MAX, то условие "i < arraySize" никогдане выполнится и возникнет вечный цикл.Если мы воспользуемся ключевым словом "auto", то это ничего не изменит:for (auto i = 0; i < arraySize; i++) if (array[i] == key) return true;Переменная "i" будет иметь тип "int", так как 0 имеет тип "int". Правильным исправлением можетстать использование "decltype":for (decltype(arraySize) i = 0; i < arraySize; i++) if (array[i] == key) return true;Теперь счетчик "i" имеет тип "size_t" как и переменная "arraySize".Поддержка "decltype" в библиотеке VivaCore во многом схожа с поддержкой "auto". Добавленановая лексема tkDECLTYPE. Добавлена функция парсинга rDecltype в файле Parser.cc. В связи споявлением новой лексемы модификации подверглась функция optIntegralTypeOrClassSpec.Появился новый класс LeafDECLTYPE.Для кодирования типа возвращаемого оператором "decltype" выбрана литера X (заглавная букваX, в отличие от прописной x, используемой для auto). В связи с этим измениласьфункциональность классов TypeInfo и Encoding. Например, функции WhatIs, IsDecltype, MakePtree.Функциональность по вычислению типов для оператора "decltype" реализована в классеEnvironment и входит в состав библиотеки VivaCore. Вычисление типа осуществляется в моментзаписи новой переменной/типа в Environment (функции RecordTypedefName, RecordDeclarator,RecordConstantDeclarator). За вычисление типа отвечает функция FixIfDecltype.3. Ссылка на временный объект (R-value reference)В стандарте C++98 временные объекты можно передавать в функции, но только как константнуюссылку (const &). Следовательно, функция не в состоянии определить, временный это объект илинормальный, который тоже передали как const &.В C++0x будет добавлен новый тип ссылки — ссылка на временный объект (R-value reference). Егообъявление следующее: "ИМЯ_ТИПА &&". Оно может быть использовано как не константный,легально модифицируемый объект. Данное нововведение позволяет учитывать временные
  • 8. объекты и реализовывать семантику переноса (Move semantics). Например, если std::vectorсоздается как временный объект или возвращается из функции — можно, создавая новый объект,просто перенести все внутренние данные из ссылки нового типа. Конструктор переноса std::vectorчерез полученную ссылку на временный объект просто копирует указатель массива, находящийсяв ссылке, которая по окончании устанавливается в пустое состояние.Конструктор переноса или оператор переноса может быть объявлен следующим образом:template<class T> class vector { // ... vector(const vector&); // copy constructor vector(vector&&); // move constructor vector& operator=(const vector&); // copy assignment vector& operator=(vector&&); // move assignment};С точки зрения анализа 64-битных ошибок в коде для нас не имеет значения, обрабатывается приобъявлении типа & или &&. Соответственно поддержка данного нововведения в VivaCoreвесьма проста. Изменения затронули только функцию optPtrOperator класса Parser. В ней мыравнозначно воспринимаем как &, так и &&.4. Правые угловые скобкиС точки зрения стандарта C++98 следующая конструкция содержит синтаксическую ошибку:list<vector<string>> lvs;Для ее предотвращения необходимо вставить пробел между двумя правыми закрывающимисяугловыми скобками:list<vector<string> > lvs;Стандарт С++0x узаконил использование двойных закрывающихся скобок при объявлениишаблонных типов, без необходимости вставлять между ними пробел. В результате становитсявозможным написание чуть более элегантного кода.Поддержку данного нововведения важно реализовать в статическом анализаторе, так какразработчики с удовольствием перестанут писать лишние пробелы.На данный момент, разбор объявлений шаблонных типов с ">>" пока реализован в VivaCore нелучшим образом. В ряде случаев анализатор ошибается и видимо со временем частианализатора, связанные с разбором шаблонов будут нами существенно переработаны. Пока вкоде можно увидеть следующие некрасивые функции, которые эвристическими методамипытаются определить, имеем мы дело с оператором сдвига ">>" или с частью объявленияшаблонного типа "A<B<C>> D": IsTemplateAngleBrackets, isTemplateArgs. Тем, кому интересно, каккорректно подойти к решению данной задачи, будет полезен следующий документ: "Right AngleBrackets (N1757)". Со временем мы улучшим обработку правых угловых скобок в VivaCore.
  • 9. 5. Лямбда-функции (Lambdas)Лямбда-выражения в Си++ — это краткая форма записи анонимных функторов (объектов, которыеможно использовать как функцию). Рассмотрим немного историю. В Си для создания функторовиспользуются указатели на функцию:/* callback-функция */int compare_function(int A, int B) { return A < B;}/* объявление функции сортировки */void mysort(int* begin_items, int num_items, int (*cmpfunc)(int, int));int main(void) { int items[] = {4, 3, 1, 2}; mysort(items, sizeof(items)/sizeof(int), compare_function); return 0;}Ранее в Си++ функтор создавали с помощью класса, у которого перегружен operator():class compare_class { public: bool operator()(int A, int B) { return (A < B); }};// объявление функции сортировкиtemplate <class ComparisonFunctor>
  • 10. void mysort (int* begin_items, int num_items, ComparisonFunctor c);int main() { int items[] = {4, 3, 1, 2}; compare_class functor; mysort(items, sizeof(items)/sizeof(int), functor);}В C++0x мы получаем возможность объявить функтор еще более элегантно:auto compare_function = [](char a, char b) { return a < b; };char Str[] = "cwgaopzq";std::sort(Str, Str + strlen(Str), compare_function);cout << Str << endl;Мы заводим переменную compare_function которая является функтором и тип которойопределяется компилятором автоматически. Затем мы может передать эту переменную вstd::sort. Мы можем еще более сократить код:char Str[] = "cwgaopzq";std::sort( Str, Str + strlen(Str), [](char a, char b) {return a < b;});cout << Str << endl;Здесь "[](char a, char b) {return a < b;}" и есть не что иное, как лямбда-функция.
  • 11. Лямбда-выражение всегда начинается со скобок [], в которых может быть указан список захвата.Затем идет необязательный список параметров и необязательный тип возвращаемого значения.Завершает объявление непосредственно тело функции. В целом формат написания лямбдафункций следующий:[ [<список_захвата>] ][ ( <список_параметров> ) [mutable ] ][ throw ( [<типы_исключений>] ) ][ -> <тип_возвращаемого_значения> ]{ [<тело_функции>] }Примечание. Спецификацию исключений в простых и лямбда-функциях теперь принятосчитать устаревшей. Вводится новое ключевое слово noexcept, но данное нововведение пока ненашло отражение в Visual C++.Список захвата указывает, к каким объектам из внешней области видимости имеет доступ лямбда-функция: • [] - без захвата переменных из внешней области видимости; • [=] - все переменные захватываются по значению; • [&] - все переменные захватываются по ссылке; • [x, y] - захват x и y по значению; • [&x, &y] - захват x и y по ссылке; • [in, &out] - захват in по значению, а out — по ссылке; • [=, &out1, &out2] - захват всех переменных по значению, кроме out1 и out2, которые захватываются по ссылке; • [&, x, &y] - захват всех переменных по ссылке, кроме x.К сожалению, в рамках данной статьи не представляется возможным уделить лямбда-функциямбольшего внимания. Вы сможете подробнее познакомиться с лямбда-функциям посетив ресурсы,перечисленные в библиографическом списке в конце статьи. В качестве демонстрациииспользования лямбда-функций рассмотрим код программы, выводящей строки в порядкеувеличения их длинны.Программа создает массив строк и массив индексов. Затем программа сортирует индексы строктаким образом, что бы строки были расположены по увеличению их длины:int _tmain(int, _TCHAR*[]){ vector<string> strings; strings.push_back("lambdas"); strings.push_back("decltype"); strings.push_back("auto");
  • 12. strings.push_back("static_assert"); strings.push_back("nullptr"); vector<size_t> indices; size_t k = 0; generate_n(back_inserter(indices), strings.size(), [&k]() { return k++; }); sort(indices.begin(), indices.end(), [&](ptrdiff_t i1, ptrdiff_t i2) { return strings[i1].length() < strings[i2].length(); }); for_each(indices.begin(), indices.end(), [&strings](const size_t i) { cout << strings[i] << endl; }); return 0;}Примечание.Согласно С++0x можно инициализировать массивы std::vector следующим образом:vector<size_t> indices = {0,1,2,3,4};Но пока Visual Studio 2010 не поддерживает подобные конструкции.Качество анализа лямбда-функций в статических анализаторах должно соответствовать качествуанализа простых функций. В целом анализ лямбда-функций схож с анализом простых функций, заисключением того, что лямбда-функции имеют другую область видимости.В PVS-Studio реализована полноценная диагностика ошибок в лямбда-функциях. Рассмотримпример кода, содержащий 64-битную ошибку:int a = -1;unsigned b = 0;const char str[] = "Viva64";const char *p = str + 1;auto lambdaFoo = [&]() -> char
  • 13. { return p[a+b]; };cout << lambdaFoo() << endl;Данный код работает при компиляции в режиме Win32 и печатает на экран букву V. В режимеWin64 программа аварийно завершается из-за попытки обращению к элементу с номером0xFFFFFFFF. Подробнее о данном виде ошибок рассказано в уроках по разработке 64-битныхприложений на языке Си/Си++ - " Урок 13. Паттерн 5. Адресная арифметика".При проверке приведенного выше кода, PVS-Studio выводит диагностическое сообщение:error V108: Incorrect index type: p[not a memsize-type]. Use memsizetype instead.Соответственно анализатор должен был для этого разобрать лямбда-функцию и разобраться собластью видимости переменных. Непростая, но необходимая функциональность.С поддержкой лямбда-функций связаны самые значительные изменения в VivaCore. В процессепостроения дерева разбора теперь участвует новая функция rLambdas. Функция находится вклассе Parser и вызывается из таких функций как rInitializeExpr, rFunctionArguments,rCommaExpression. Функция rLambdas разбирает лямбда-функции и добавляет в дерево новый типобъекта - PtreeLambda. Класс PtreeLambda объвлен и реализован в файлах PtreeLambda.h иPtreeLambda.Обработку PtreeLambda в построенном дереве осуществляет функция TranslateLambda. Вся логикапо работе с лямда-функциями сосредоточена в VivaCore. Внутри TranslateLambda вы встретитевызов функции GetReturnLambdaFunctionTypeForReturn, реализованную в коде PVS-Studio. Ноданная функция служит для внутренних целей PVS-Studio и пустая заглушка-функцияGetReturnLambdaFunctionTypeForReturn в VivaCore никак не скажется на разборе кода.6. Suffix return type syntaxБывают случаи, когда затруднительно указать тип, возвращаемый функцией. Рассмотрим примершаблонной функции, перемножающей два значения:template<class T, class U>??? mul(T x, U y){ return x*y;}Возвращаемый тип должен является типом выражения "x*y". Но, непонятно, что можно написатьвместо "???". Первой идеей может быть использование "decltype":template<class T, class U>
  • 14. decltype(x*y) mul(T x, U y) //Scope problem!{ return x*y;}Переменные "x" и "y" объявлены после "decltype(x*y)" и такой код, к сожалению, не может бытьскомпилирован.Решение данной проблемы заключается в использовании нового синтаксиса возвращаемыхзначений:template<class T, class U>[] mul(T x, U y) -> decltype(x*y){ return x*y;}Используя скобки [] мы порождаем здесь лямда-функцию и при этом говорим "возвращаемый типбудет выведен или задан позже". К сожалению, хотя приведенный пример корректен, он намомент написания статьи не компилируется в Visual C++. Однако, мы можем использоватьальтернативный вариант (где также используется Suffix return type syntax):template<class T, class U>auto mul(T x, U y) -> decltype(x*y){ return x*y;}Этот код будет успешно собран Visual C++ и мы получим желаемый результат.В версии PVS-Studio 3.50 поддержка нового формата функций реализована только частично.Конструкции полностью разбираются библиотекой VivaCore, но PVS-Studio при анализе неучитывает типы данных, возвращаемые этими функциями. Познакомиться с поддержкойальтернативной записи функций в библиотеке VivaCore можно в функцииParser::rIntegralDeclaration.7. static_assertВ стандарте C++0x появилось новое ключевое слово static_assert. Синтаксис:static_assert(выражение, "сообщение об ошибке");В случае если выражение ложно, то выводится указанное сообщение об ошибке и компиляцияпрекращается. Рассмотрим пример использования static_assert:
  • 15. template <unsigned n>struct MyStruct{ static_assert(n > 5, "N must be more 5");};MyStruct<3> obj;При компиляции данного кода компилятор Visual C++ выдаст сообщение:error C2338: N must be more 5 xx.cpp(33) : see reference to class template instantiation MyStruct<n> being compiled with [ n=3 ]С точки зрения анализа кода, осуществляемого PVS-Studio, конструкция static_assert непредставляет интереса и поэтому игнорируется. В VivaCore добавлена новая лексемаtkSTATIC_ASSERT. Встречая эту лексему, лексер игнорирует ее и все параметры, относящиеся кконструкции static_assert (реализация в функции Lex::ReadToken).8. nullptrДо стандарта C++0x в Си++ не было ключевого слова для обозначения нулевого указателя. Для егообозначения использовалось число 0. Однако хорошим стилем считалось использование макросаNULL. При раскрытии макрос NULL превращается в 0, и между ними нет практической разницы.Вот как объявлен макрос NULL в Visual Studio:#define NULL 0В ряде случаев отсутствие специального ключевого слова для обозначения нулевого указателябыло неудобно и даже провоцировало возникновение ошибок. Рассмотрим пример:void Foo(int a){ cout << "Foo(int a)" << endl; }void Foo(char *a){ cout << "Foo(char *a)" << endl; }int _tmain(int, _TCHAR*[]){
  • 16. Foo(0); Foo(NULL); return 0;}Хотя программист может ожидать, что в данном коде будут вызваны разные функции Foo, это нетак. Вместо NULL будет подставлен 0, имеющий тип "int" и при запуске программы на экранебудет распечатано:Foo(int a)Foo(int a)Для устранения подобных ситуаций в C++0x введено ключевое слово nullptr. Константа nullptrимеет тип nullptr_t и неявно приводится к любому типу указателя или к указателю на членыкласса. Константа nullptr неявно не приводится к целочисленным типам данных за исключениемтипа "bool".Вернемся к нашему примеру и добавим вызов функции "Foo" с аргументом nullptr:void Foo(int a){ cout << "Foo(int a)" << endl; }void Foo(char *a){ cout << "Foo(char *a)" << endl; }int _tmain(int, _TCHAR*[]){ Foo(0); Foo(NULL); Foo(nullptr); return 0;}Теперь на экране будет распечатано:Foo(int a)Foo(int a)Foo(char *a)Хотя ключевое слово nullptr не представляет интереса с точки зрения поиска 64-битных ошибок,необходима его поддержка при разборе кода. Для этого в VivaCore был добавлена новая лексемаtkNULLPTR, а также класс LeafNULLPTR. Создание объектов типа LeafNULLPTR происходит вфункции rPrimaryExpr. При вызове функции LeafNULLPTR::Typeof тип "nullptr" кодируется как "Pv",
  • 17. то-есть "void *". С точки зрения существующих задач по анализу кода в PVS-Studio этогодостаточно.9. Новые стандартные классыСтандарт C++0x вводит новые стандартные классы, относящиеся к namespace std. Ряд из этихклассов уже поддерживаются в Visaul Studio 2010. В качестве примера можно привести: • std::array; • std::shared_ptr; • std::regex.Поскольку перечисленные сущности являются обыкновенными шаблонными классами, то ихпоявление не потребовало какой либо модификации PVS-Studio или библиотеки VivaCore.10. Новые направления в развитии статических анализаторов кодаВ конце хочется отметить один интересный момент, связанный с использованием C++0x. Новыевозможности языка с одной стороны, исправляя старые недочеты, делают код безопаснее иэффективнее, но при этом также создают новые, пока неизвестные ловушки, в которые можетпопасть программист. Правда, о них рассказать я пока ничего не могу.Но можно попасть и в уже известные ловушки из-за того, что их диагностика в новых конструкцияхC++0x реализована намного хуже или вообще не реализована. Рассмотрим небольшой пример,демонстрирующий использование неинициализированной переменной:{ int x; std::vector<int> A(10); A[0] = x; // Warning C4700}{ int x; std::vector<int> A(10); std::for_each(A.begin(), A.end(), [x](int &y) { y = x; } // No Warning );}Программист может надеяться получить предупреждение от компилятора как в первом, так и вовтором случае. Но в примере с лямбда-функцией никакого предупреждения выдано не будет
  • 18. (испытано на Visual Studio 2010 RC, /W4). Как не было ранее и многих других предупреждений наразличные опасные ситуации. Требуется время на реализацию подробной диагностики.Можно ожидать новый виток развития статических анализаторов, с точки зрения поискапотенциально опасных конструкций, которые возникают при использовании конструкций C++0x.Мы позиционируем наш продукт PVS-Studio как средство для проверки современных программ. Внастоящий момент мы понимаем под этим 64-битные и параллельные технологии. В будущем мыпланируем провести исследования вопроса о том, какие потенциальные проблемы можноожидать при использовании C++0x. Если подводных камней будет достаточно много, то возможномы приступим к созданию нового инструмента для их диагностики.ЗаключениеНа наш взгляд C++0x привносит много положительных моментов. Старый код не требуетнемедленной модернизации, хотя может быть со временем модифицирован в ходерефакторинга. Новый же код может уже писаться с использованием новых конструкций. Такимобразом, начало использования C++0x выглядит рациональным уже сейчас.Библиографический список 1. Bjarne Stroustrup. C++0x - the next ISO C++ standard. http://www.viva64.com/go.php?url=304 2. Visual C++ Team Blog. Rvalue References: C++0x Features in VC10, Part 2. http://www.viva64.com/go.php?url=305 3. Сергей Олендаренко. C++0x. Лямбда-выражения. http://www.viva64.com/go.php?url=306 4. Максим. С++0x и решение проблем инициализации. http://www.viva64.com/go.php?url=307 5. Wikipedia. C++0x. http://www.viva64.com/go.php?url=301 6. Herb Sutter. Trip Report: March 2010 ISO C++ Standards Meeting. http://www.viva64.com/go.php?url=308