SlideShare a Scribd company logo
1 of 20
Download to read offline
Построение систем автоматического
протоколирования Си/Си++ кода
Автор: Андрей Карпов

Дата: 13.05.2008


Аннотация
Иногда единственным методом отладки является использование протоколирования событий
приложения. К недостаткам протоколирования (логирования) можно отнести большой объем
кода, который приходится писать вручную для сохранения всей необходимой информации. В
статье рассматривается методика, позволяющая построить систему автоматического
протоколирования кода на языке Си/Си++.


Введение
Несмотря на время, языки Си и Си++ продолжают занимать лидирующие позиции во многих
областях программирования. И в ближайшие 10 лет вряд ли что-то существенно изменится.
Появилось много красивых и интересных языков, однако на практике часто предпочтение все-таки
отдается Си и Си++. Это одни из лучших существующих языков, универсальные,
высокоэффективные и с широкой поддержкой. Если вы хотите лучше понять, чем же
привлекательны эти языки, то я предлагаю вам познакомиться со статьей "Инструменты и
производительность" [1] и интересной подборкой сообщений из форума comp.lang.c++.moderated
"Why do you program in c++?" [2]. Все это значит, что разработка инструментария для языков
Си/Си++ по прежнему актуальна.

Во всех языках разработчики допускают ошибки, и языки Си/Си++ здесь не исключение. Более
того, как и многие профессиональные инструменты, они требуют большего внимания и
аккуратности при их использовании. В результате одной из важнейших задач при разработке
приложений является отладка кода, то есть поиск и устранение ошибок.

Способы отладки приложений можно разделить на следующие основные группы:

   •   Интерактивные средства отладки;
   •   Runtime-диагностика;
   •   Визуальные (графические) средства отладки;
   •   Модульные тесты (Unit Testing);
   •   Функциональные тесты;
   •   Протоколирование;
   •   Отладка по крэш-дампам;
   •   Визуальный просмотр кода (Code Review);
   •   Статический анализ кода.
У каждого из перечисленных методов есть свои достоинства и недостатки, с которыми можно
познакомиться в статье "Способы отладки приложений" [3]. Но в рамках нашей статьи мы
поговорим о протоколировании (логировании) и методах его автоматизации.


1. Почему протоколирование?
На первый взгляд протоколирование работы приложений может показаться неактуальным.
Возможно это атавизм того времени, когда еще было принято выводить результаты работы
программы сразу на принтер? Нет, это очень эффективная и часто незаменимая методика,
позволяющая отлаживать сложные, параллельные или специфические приложения.

Рассмотрим области, где протоколирование является незаменимым по удобству и эффективности
решением:

   1. Отладка оптимизированных (Release) сборок приложения. Иногда Release версия ведет
      себя не так как Debug, что может быть связано с ошибками неинициализированной памяти
      и т.д. Но работать с Release версией в отладчике бывает часто неудобно. А еще, хотя это
      бывает крайне редко, встречаются ошибки компилятора, которые тоже проявляются
      только в Release версиях. В таких ситуациях протоколирование представляет собой
      хорошую замену отладчику.
   2. Отладка механизмов защиты. Часто разработка приложений с аппаратной защитой
      (например, на основе ключей Hasp) крайне затруднена, так как отладка невозможна.
      Протоколирование в этом случае, является, чуть ли не единственным способом поиска
      ошибок.
   3. Протоколирование - это единственно возможный вариант отладки для приложения,
      которое запускается на компьютере у конечного пользователя. Грамотное использование
      файла протокола позволит разработчикам получить полную информацию, необходимую
      для диагностики возникающих у пользователя проблем.
   4. Протоколирование позволяет отлаживать драйверы устройств и программы для
      встроенных систем.
   5. Протоколирование позволяет быстрее обнаруживать ошибки после пакетного запуска
      функциональных или нагрузочных тестов.
   6. Еще одним интересным способом использования логов является просмотр отличий (diff)
      двух различных версий. Читателю предлагается самому подумать, где такая возможность
      была бы ему полезна в его проектах.
   7. Протоколирование дает возможность удаленной отладки, когда интерактивные средства
      невозможны или труднодоступны. Это удобно для высокопроизводительных
      многопользовательских систем, где пользователь ставит свои задачи в очередь, а затем
      дожидается их выполнения. Такой подход используется и в наши дни в институтах и других
      организациях, работающих с вычислительными кластерами.
   8. Возможность отладки параллельных приложений. В таких приложениях ошибки часто
      происходят при создании большого количества потоков или при наличии проблем с
      синхронизацией. Ошибки в параллельных программах бывает достаточно сложно
      отладить. Надежным методом отлова таких ошибок является периодическое
      протоколирование состояния систем, которые имеют отношение к ошибке, и
      исследование данных лога после некорректного завершения работы [1].
Основательный список, чтобы не списывать метод протоколирования на пенсию. Хочется
надеяться, что статья натолкнет и на другие мысли, как можно с пользой использовать
описываемую методологию протоколирования.


2. Создание системы протоколирования
Начнем с требований, которыми должна обладать современная система протоколирования:

   •    Код, обеспечивающий протоколирование данных в отладочной версии, должен
        отсутствовать в конечной версии программного продукта. Во-первых, это связано с
        увеличением быстродействия и уменьшением размера программного продукта. Во-
        вторых, не позволяет использовать отладочную информацию для взлома приложения или
        иных несанкционированных действий. Заметим, что имеется в виду именно конечная
        версия программы, так как лог могут создавать и Debug и Release версии.
   •    Интерфейсы системы протоколирования должны быть лаконичны, чтобы не загромождать
        основной код программы.
   •    Сохранение данных должно осуществляться как можно быстрее, чтобы вносить
        минимальное изменение во временные характеристики параллельных алгоритмов.
   •    Полученный лог должен быть наглядным и легко поддаваться анализу. Должна
        существовать возможность разделить информацию, полученную от различных потоков, а
        также варьировать ее уровень подробности.
   •    Кроме протоколирования непосредственных событий приложения полезно выполнить
        сбор сведений о компьютере.
   •    Желательно, чтобы система сохраняла имя модуля, имя файла и номер строки, в которой
        произошла запись данных. В ряде случаев полезно хранить время наступления события.

Система протоколирования, отвечающая таким качествам, позволяет универсально решать
задачи, начиная от разработки механизмов защиты до поиска ошибок в параллельных алгоритмах
[4].

Данная статья, хотя и посвящена системе протоколирования данных, в ней не будет
рассматриваться какой-то завершенный вариант такой системы. Универсальный вариант
невозможен, так как он сильно зависит от среды разработки, особенностей проекта,
предпочтений разработчика и многого другого. Кстати отметим, что существует ряд статей, в
которых рассматриваются эти вопросы. Например, "Logging In C++" [5] и "Способы отладки
приложений: Протоколирование" [6].

Сейчас мы остановимся на рассмотрении ряда технических решений, которые помогут вам
создать удобную и эффективную систему протоколирования, если в том возникнет
необходимость.

Самым простым способом осуществить протоколирование является использование функции,
аналогичной printf, как показано в примере:

  int x = 5, y = 10;

  ...

  printf("Coordinate = (%d, %d)n", x, y);
Естественным недостатком является то, что информация будет выводиться как в отладочном
режиме, так и в конечном продукте. Поэтому, следует модернизировать код следующим образом:

#ifdef DEBUG_MODE

  #define WriteLog printf

#else

  #define WriteLog(a)

#endif

  WriteLog("Coordinate = (%d, %d)n", x, y);

Это уже лучше. Причем обратите внимание, что мы используем для выбора реализации функции
WriteLog не стандартный макрос _DEBUG, а собственный макрос DEBUG_MODE. Это позволяет
включать отладочную информацию в Release-версии, что важно при отладке на большом объеме
данных.

К сожалению, теперь при компиляции не отладочной версии, например, в среде Visual C++
возникает предупреждение: "warning C4002: too many actual parameters for macro 'WriteLog'".
Можно отключить это предупреждение, но это является плохим стилем. Можно переписать код,
как показано ниже:

#ifdef DEBUG_MODE

  #define WriteLog(a) printf a

#else

  #define WriteLog(a)

#endif

  WriteLog(("Coordinate = (%d, %d)n", x, y));

Приведенный код не является элегантным, так как приходится использовать двойные пары
скобок, что часто забывается. Поэтому внесем новое усовершенствование:

#ifdef DEBUG_MODE

  #define WriteLog printf

#else

  inline int StubElepsisFunctionForLog(...) { return 0; }

  static class StubClassForLog {

  public:

     inline void operator =(size_t) {}

  private:

     inline StubClassForLog &operator =(const StubClassForLog &)
{ return *this; }

  } StubForLogObject;



  #define WriteLog 

      StubForLogObject = sizeof StubElepsisFunctionForLog

#endif

  WriteLog("Coordinate = (%d, %d)n", x, y);

Этот код выглядит сложным, но он позволяет писать одинарные скобки. При выключенном
DEBUG_MODE этот код превращается в ничто, и его можно смело использовать в критических
участках кода.

Следующим усовершенствованием может стать добавление к функции протоколирования таких
параметров как уровень детализации и тип выводимой информации. Уровень детализации
можно задать как параметр, например:

enum E_LogVerbose {

  Main,

  Full

};

#ifdef DEBUG_MODE

  void WriteLog(E_LogVerbose,

                    const char *strFormat, ...)

  {

      ...

  }

#else

  ...

#endif

WriteLog (Full, "Coordinate = (%d, %d)n", x, y);

Этот способ удобен тем, что решение, отфильтровать или не отфильтровать маловажные
сообщения можно принять уже после завершения работы программы, используя специальную
утилиту. Недостаток такого метода в том, что всегда происходит вывод всей информации, как
важной, так и второстепенной, что может снижать производительность. Поэтому можно создать
несколько функций вида WriteLogMain, WriteLogFull и подобные им, реализация которых будет
зависеть от режима сборки программы.
Мы упоминали о том, что запись отладочной информации должна как можно меньше влиять на
скорость работы алгоритма. Этого можно достичь, создав систему накопления сообщений, запись
которых происходит в параллельно выполняемом потоке. Сейчас это получило дополнительные
преимущества в связи с широким распространением многоядерных (многопроцессорных) систем.
Схематично этот механизм представлен на рисунке N1.
Рисунок N1. Система протоколирования с отложенной записью данных.

Как можно видеть на рисунке, запись очередной порции данных происходит в промежуточный
массив строк фиксированной длины. Фиксированный размер массива и строк в нем позволяет
исключить дорогостоящие операции выделения памяти. Это нисколько не снижает возможности
такой системы. Достаточно выбрать длину строк и размер массива с запасом. Например, 5000
строк длиной в 4000 символов будет достаточно для отладки практически любой системы. А
объем памяти в 20 мегабайт необходимый для этого, согласитесь, не критичен для современных
систем. Если же массив все равно будет переполнен, то несложно предусмотреть механизм
досрочной записи информации в файл.

Приведенный механизм обеспечивает практически моментальное выполнение функции WriteLog.
Если в системе присутствуют ненагруженные процессорные ядра, то и запись в файл будет
прозрачна для основных потоков протоколируемой программы.

Преимущество описываемой системы в том, что она практически без изменений способна
функционировать при отладке параллельной программы, когда в лог пишут сразу несколько
потоков. Следует только добавить сохранение идентификатора потока (thread), чтобы потом
можно было узнать, от каких потоков были получены сообщения (смотри рисунок N2).
Рисунок N2. Система протоколирования при отладке многопоточных приложений.

Такая схема может привести к изменению временных характеристик, так как несколько
протоколируемых потоков вынуждены ждать друг друга, чтобы осуществить запись информации.
Если это является критичным, то можно создать отдельные хранилища сообщений для каждого из
потоков, как показано на рисунке N3. Дополнительно, в этом случае следует записывать время
событий, чтобы было возможно слить два лога в один.
Рисунок N3. Улучшенная система протоколирования при отладке многопоточных приложений.

Последнее усовершенствование, которое хочется предложить, это организация показа уровня
вложенности сообщений при вызове функций или начале логического блока. Это можно легко
организовать, используя специальный класс, который в конструкторе записывает в лог
идентификатор начала блока, а в деструкторе - идентификатор конца блока. Написав небольшую
утилиту, можно трансформировать лог, опираясь на информацию об идентификаторах.
Попробуем показать это на примере.
Код программы:

class NewLevel {

public:

    NewLevel() { WriteLog("__BEGIN_LEVEL__n"); }

    ~NewLevel() { WriteLog("__END_LEVEL__n"); }

};

#define NEW_LEVEL NewLevel tempLevelObject;

void MyFoo() {

    WriteLog("Begin MyFoo()n");

    NEW_LEVEL;

    int x = 5, y = 10;

    printf("Coordinate = (%d, %d)n", x, y);

    WriteLog("Begin Loop:n");

    for (unsigned i = 0; i != 3; ++i)

    {

        NEW_LEVEL;

        WriteLog("i=%un", i);

    }

}

Содержимое лога:

Begin MyFoo()

__BEGIN_LEVEL__

Coordinate = (5, 10)

Begin Loop:

__BEGIN_LEVEL__

i=0

__END_LEVEL__

__BEGIN_LEVEL__

i=1

__END_LEVEL__
__BEGIN_LEVEL__

i=2

__END_LEVEL__

Coordinate = (5, 10)

__END_LEVEL__

Лог после трансформации:

Begin MyFoo()

      Coordinate = (5, 10)

      Begin Loop:

          i=0

          i=1

          i=2

      Coordinate = (5, 10)


3. Автоматизация системы протоколирования
Мы рассмотрели принципы, на которых может быть реализована система протоколирования. В
такой системе можно реализовать все требования, описанные в первой части статьи. Но
серьезным недостатком в применении такой системы становится необходимость написания
большого количества кода для записи всех необходимых данных. Есть и другие недостатки:

   1. Невозможность красиво реализовать функции протоколирования для языка Си, так как там
      отсутствуют классы и шаблоны. В результате код для протоколирования должен выглядеть
      по разному в Си и Си++ файлах.
   2. Необходимость помнить о необходимости написания NEW_LEVEL в каждом блоке или
      функции, если есть желание, чтобы лог выглядел красиво.
   3. Невозможно автоматически сохранять имена всех вызываемых функций. Необходимость
      вручную записывать входные аргументы функций.
   4. Загромождение исходных текстов дополнительными конструкциями, такими как,
      например NEW_LEVEL.
   5. Приходится прикладывать усилия, чтобы контролировать, что все конструкции
      протоколирования будут исключены из конечной версии программы.
   6. Приходится прописывать функции для инициализации системы протоколирования,
      прописывать необходимые "#include" и делать иные вспомогательные действия.

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

Реализовать такую систему возможно на основе методологии метапрограммирования, введя в
язык Си/Си++ новые конструкции для записи данных.
Метапрограммирование - это создание программ, которые создают другие программы [7]. В
метапрограммировании можно выделить два основных направления: генерация кода и
самомодифицирующийся код. Нас интересует генерация кода. В этом случае код программы с
встроенными механизмами протоколирования не пишется вручную, а создается автоматически
программой-генератором на основе другой, более простой программы. Это позволяет получить
программу при меньших затратах времени и усилий, чем если бы программист самостоятельно
реализовывал весь код.

Существуют языки, для которых метапрограммирование является естественно составной частью.
Примером является язык Nemerle, с которым можно познакомиться в статье
"Метапрограммирование в Nemerle" [8]. Но в случае с Си/Си++ все сложнее, и в них
метапрограммирование реализуется следующими двумя путями:

   1. Шаблоны в Си++ и препроцессор в Си. К сожалению, как было показано выше, этого
      недостаточно.
   2. Внешние языковые средства. Язык генератора составляется так, чтобы автоматически или
      с минимальными усилиями со стороны программиста реализовывать правила парадигмы
      или необходимые специальные функции. Фактически создается более высокоуровневый
      язык программирования. Именно этот подход может быть использован для создания
      системы автоматизации протоколирования программ.

Введя в язык Си/Си++ новые ключевые слова, можно получить гибкую и удобную систему
программирования с мощнейшими возможностями протоколирования. Использование метаязыка
открывает широкие возможности в выборе оформления записи необходимой информации.
Можно протоколировать данные, введя привычный формат функций:

EnableLogFunctionCallForFhisFile(true);

...

ObjectsArray &objects = getObjects();

WriteLog("objects.size = %u", objects.size());

for (size_t i = 0; i != objects.size(); ++i) {

      WriteLog("object type = %i", int(objects [i]->getType()));

      if (objects[i]->getType() != TYPE_1)

            ...

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

ObjectsArray &objects = getObjects();

MyLog "objects.size = ", objects.size()

for (size_t i = 0; i != objects.size(); ++i) {

      MyLog "object type = ", int(objects [i]->getType())

      if (objects[i]->getType() != TYPE_1)
...

Можно шагнуть дальше, так как здесь все зависит от фантазии автора:

thisfile.log.FunctionCall = ON

...

ObjectsArray &objects = getObjects();

for (size_t i = 0;

      i != <log>"objects.size = "objects.size()<log>;

      ++i) {

      LOG: "object type = " {as int} objects [i]->getType();

      if (objects[i]->getType() != TYPE_1)

             ...

Новый метаязык можно реализовать в качестве промежуточного звена между препроцессором и
компилятором. Что именно должен представлять этот промежуточный компонент для трансляции
метапрограммы зависит от среды разработки. В общем случае схему функционирования можно
представить, как показано на рисунке N4.
Рисунок 4. Участие транслятора метаязыка в процессе компиляции.

Теперь рассмотрим сам транслятор. Его принципиальная схема представлена на рисунке N5.
Рисунок 5. Принципиальная схема транслятора языка для генерации кода с поддержкой
                                    протоколирования.

Остановимся на генераторе кода более подробно. Генератор представляет собой алгоритм
обхода дерева и выполнения над ним трех операций. Во-первых, получение типов всех функций и
объектов, определение области их видимости. Это позволит автоматически генерировать код для
корректной записи аргументов функций и иных объектов. Во-вторых, происходит раскрытие
конструкций метаязыка в новые ветви дерева. И последнее - запись обработанных ветвей кода
обратно в файл.

Реализовать самостоятельно такой генератор дело достаточное непростое, впрочем, как и
синтаксический анализатор. Но существуют соответствующие библиотеки, о которых будет
сказано ниже.

Посмотрим, как генерация кода программы решает перечисленные ранее недостатки системы,
построенной исключительно на основе макросов и шаблонов:

   1. Конструкции протоколирования в языке Си могут выглядеть также элегантно, как и в Си++.
      Генератор берет на себя заботу о превращении зарезервированных слов в код для записи
      сообщений. То, что этот код будет выглядеть по разному, для Си и Си++ файлов скрыто от
      пользователя.
   2. Генератор может автоматически вставлять код для выделения начала и конца функций
      или блоков. Отпадает необходимость использования макроса NEW_LEVEL.
   3. Можно автоматически сохранять имена всех или части вызываемых функций. Можно
      автоматически сохранять и значения входных параметров для базовых типов данных.
   4. Исчезает загромождение текста вспомогательными конструкциями.
   5. Можно гарантировать полное исчезновение всех функций, ресурсов и объектов из
      конечной версии программного продукта. Генератору достаточно пропустить все
      специальные элементы, относящиеся к протоколированию в тексте программы.
   6. Исчезает необходимость прописывать функции инициализации, вставлять "#include" и
      совершать другие вспомогательные действия, так как они могут быть реализованы на
      этапе преобразования кода.

К подводным камням предложенной схемы следует отнести проблему с отладкой таких
модифицированных программ с помощью отладчика. С одной стороны, если имеется система
протоколирования, то использование отладчика не столь важный компонент разработки
приложения. Но все-таки часто отладчик бывает крайне полезным инструментом, отказываться от
которого не хочется.

Проблема в том, что после преобразования кода (раскрытия операторов для протоколирования)
возникнет проблема навигации по номерам строк. Здесь могут помочь специфические
программные меры, которые будут индивидуальны для каждой среды разработки. Можно пойти
более простым путем. И применить подход, аналогичный подходу в OpenMP, то есть использовать
"#pragma". В этом случае код протоколирования будет выглядеть так:

ObjectsArray &objects = getObjects();

#pragma Log("objects.size = ", objects.size())

for (size_t i = 0; i != objects.size(); ++i) {
#pragma Log("object type = ", objects [i]->getType())

     if (objects[i]->getType() != TYPE_1)

             ...

Это несколько менее красиво из-за лишнего слова "#pragma", но у такого текста программы
имеются большие преимущества. Такой код может быть безболезненно скомпилирован на другой
системе, где не используется система автоматического протоколирования. Такой код легко может
быть перенесен на другую систему или отдан сторонним разработчикам. И конечно нет никаких
препятствий для работы с этим кодом в отладчике.


4. Инструментарий
Автору, к сожалению, не известны инструменты автоматизированного протоколирования на
основе метаязыка, близкие по функциональности к описываемой в статье системе. Если
дальнейшие исследования покажут отсутствие подобных разработок, то автор, возможно, примет
участие в новом проекте, который сейчас находится на этапе проектирования. Можно сказать, что
эта статья является своего рода исследованием актуальности создания универсальной системы
протоколирования для языков Си/Си++.

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

Если вы остановитесь на создании метаязыка, то мы подскажем на основе чего можно его
создать.

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

Первая из них OpenC++ (OpenCxx) [9]. Эта открытая бесплатная библиотека представляющий собой
"source-to-source" транслятор. Библиотека поддерживает метапрограммирование и позволяет
создавать на своей основе расширения языка Си++. На основе этой библиотеки созданы такие
решения как среда исполнения OpenTS для языка программирования T++ (разработка Института
программных систем РАН) и инструмент Synopsis для подготовки документации по исходному
коду.

Второй библиотекой является VivaCore, которая представляет собой развитие библиотеки
OpenC++, ориентированное на среду разработки Microsoft Visual Studio 2005/2008 [10]. Эта
библиотека также открыта и бесплатна. К основным отличиям можно отнести реализованную
поддержку языка Си и поддержку некоторых современных элементов языка Си++. Примером
использования библиотеки являются статические анализаторы Viva64 и VivaMP.

Если вы захотите использовать эти библиотеки для создания своих расширений, то вам
понадобится препроцессор, который должен быть запущен до начала их работы. Скорее всего,
встроенные в компилятор средства препроцессирования будут достаточны. Если нет, то вы
можете попробовать воспользоваться библиотекой The Wave C++ preprocessor library.
Заключение
Статья носит теоретический характер, но хочется надеяться, что разработчики найдут в ней много
интересных и полезных для себя идей в области протоколирования кода своих программ.
Успехов!


Библиографический список
1.     Casual Games Association. Инструменты и производительность.
       http://www.viva64.com/go.php?url=34
2.     Over at comp.lang.c++.moderated, there is a thread created by the c++ guru Scott Meyers on
       the subject: "Why do you program in c++?" http://www.viva64.com/go.php?url=35
3.     Дмитрий Долгов. Способы отладки приложений. http://www.viva64.com/go.php?url=36
4.     Андрей Карпов, Евгений Рыжков. Разработка ресурсоемких приложений в среде Visual C++.
       http://www.viva64.com/art-1-1-640027853.html
5.     Petru Marginean. Logging In C++. http://www.viva64.com/go.php?url=37
6.     Дмитрий Долгов. Способы отладки приложений: Протоколирование.
       http://www.viva64.com/go.php?url=38
7.     Джонатан Бартлет. Искусство метапрограммирования, Часть 1: Введение в
       метапрограммирование. http://www.viva64.com/go.php?url=39
8.     Kamil Skalski. Метапрограммирование в Nemerle. http://www.viva64.com/go.php?url=40
9.     Grzegorz Jakack. OpenC++ - A C++ Metacompiler and Introspection Library.
       http://www.viva64.com/go.php?url=41
10.    Андрей Карпов, Евгений Рыжков. Сущность библиотеки анализа кода VivaCore.
       http://www.viva64.com/art-2-1-696420215.html


Об авторе
Карпов Андрей Николаевич, к.ф.-м.н., научный консультант компании "СиПроВер",
специализирующейся на вопросах повышения качества программных систем. Занимается
теоретическими и практическими вопросами статического анализа кода. Участвует в проектах по
созданию библиотеки VivaCore и анализаторов кода Viva64, VivaMP.

Страница на сайте Мой Круг: http://a-karpov.moikrug.ru

E-Mail: karpov@viva64.com

More Related Content

What's hot

Трудности сравнения анализаторов кода или не забывайте об удобстве использования
Трудности сравнения анализаторов кода или не забывайте об удобстве использованияТрудности сравнения анализаторов кода или не забывайте об удобстве использования
Трудности сравнения анализаторов кода или не забывайте об удобстве использованияTatyanazaxarova
 
Установка PC-Lint и его использование в Visual Studio 2005
Установка PC-Lint и его использование в Visual Studio 2005Установка PC-Lint и его использование в Visual Studio 2005
Установка PC-Lint и его использование в Visual Studio 2005Tatyanazaxarova
 
Programmers' Mistakes for Dummies
Programmers' Mistakes for DummiesProgrammers' Mistakes for Dummies
Programmers' Mistakes for DummiesCOTOHA
 
Спецкурс-2015. Занятие 03. Абстракции, именование, документирование.
Спецкурс-2015. Занятие 03. Абстракции, именование, документирование.Спецкурс-2015. Занятие 03. Абстракции, именование, документирование.
Спецкурс-2015. Занятие 03. Абстракции, именование, документирование.7bits
 
Технология OpenMP
Технология OpenMPТехнология OpenMP
Технология OpenMPTatyanazaxarova
 
Майстер-клас "Автоматизоване тестування. З чого почати?" (частина 1)
Майстер-клас "Автоматизоване тестування. З чого почати?" (частина 1)Майстер-клас "Автоматизоване тестування. З чого почати?" (частина 1)
Майстер-клас "Автоматизоване тестування. З чого почати?" (частина 1)DataArt
 
Регулярное использование статического анализа кода в командной разработке
Регулярное использование статического анализа кода в командной разработкеРегулярное использование статического анализа кода в командной разработке
Регулярное использование статического анализа кода в командной разработкеTatyanazaxarova
 
метод организации репозитория исходного кода
метод организации репозитория исходного кодаметод организации репозитория исходного кода
метод организации репозитория исходного кодаSergii Shmarkatiuk
 
Programming Concepts
Programming ConceptsProgramming Concepts
Programming ConceptsMister_Lee
 
32 подводных камня OpenMP при программировании на Си++
32 подводных камня OpenMP при программировании на Си++32 подводных камня OpenMP при программировании на Си++
32 подводных камня OpenMP при программировании на Си++Tatyanazaxarova
 
Урок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибокУрок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибокTatyanazaxarova
 
Custom Language Plugin for JetBrains IDEA
Custom Language Plugin for JetBrains IDEACustom Language Plugin for JetBrains IDEA
Custom Language Plugin for JetBrains IDEAAlexander Zastashkov
 
Интервью с Дмитрием Вьюковым – автором верификатора Relacy Race Detector (RRD)
Интервью с Дмитрием Вьюковым – автором верификатора Relacy Race Detector (RRD)Интервью с Дмитрием Вьюковым – автором верификатора Relacy Race Detector (RRD)
Интервью с Дмитрием Вьюковым – автором верификатора Relacy Race Detector (RRD)Tatyanazaxarova
 
Константин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороныКонстантин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороныTatyanazaxarova
 
Модифицируемость программных систем
Модифицируемость программных системМодифицируемость программных систем
Модифицируемость программных системDima Dzuba
 
VivaMP, система выявления ошибок в коде параллельных программ на языке С++, и...
VivaMP, система выявления ошибок в коде параллельных программ на языке С++, и...VivaMP, система выявления ошибок в коде параллельных программ на языке С++, и...
VivaMP, система выявления ошибок в коде параллельных программ на языке С++, и...Tatyanazaxarova
 

What's hot (20)

Трудности сравнения анализаторов кода или не забывайте об удобстве использования
Трудности сравнения анализаторов кода или не забывайте об удобстве использованияТрудности сравнения анализаторов кода или не забывайте об удобстве использования
Трудности сравнения анализаторов кода или не забывайте об удобстве использования
 
МиСПИСиТ (разработка программного модуля)
МиСПИСиТ (разработка программного модуля)МиСПИСиТ (разработка программного модуля)
МиСПИСиТ (разработка программного модуля)
 
Установка PC-Lint и его использование в Visual Studio 2005
Установка PC-Lint и его использование в Visual Studio 2005Установка PC-Lint и его использование в Visual Studio 2005
Установка PC-Lint и его использование в Visual Studio 2005
 
Programmers' Mistakes for Dummies
Programmers' Mistakes for DummiesProgrammers' Mistakes for Dummies
Programmers' Mistakes for Dummies
 
Спецкурс-2015. Занятие 03. Абстракции, именование, документирование.
Спецкурс-2015. Занятие 03. Абстракции, именование, документирование.Спецкурс-2015. Занятие 03. Абстракции, именование, документирование.
Спецкурс-2015. Занятие 03. Абстракции, именование, документирование.
 
Технология OpenMP
Технология OpenMPТехнология OpenMP
Технология OpenMP
 
Майстер-клас "Автоматизоване тестування. З чого почати?" (частина 1)
Майстер-клас "Автоматизоване тестування. З чого почати?" (частина 1)Майстер-клас "Автоматизоване тестування. З чого почати?" (частина 1)
Майстер-клас "Автоматизоване тестування. З чого почати?" (частина 1)
 
Регулярное использование статического анализа кода в командной разработке
Регулярное использование статического анализа кода в командной разработкеРегулярное использование статического анализа кода в командной разработке
Регулярное использование статического анализа кода в командной разработке
 
метод организации репозитория исходного кода
метод организации репозитория исходного кодаметод организации репозитория исходного кода
метод организации репозитория исходного кода
 
Programming Concepts
Programming ConceptsProgramming Concepts
Programming Concepts
 
32 подводных камня OpenMP при программировании на Си++
32 подводных камня OpenMP при программировании на Си++32 подводных камня OpenMP при программировании на Си++
32 подводных камня OpenMP при программировании на Си++
 
TAP
TAPTAP
TAP
 
Java 9 - кратко о новом
Java 9 -  кратко о новомJava 9 -  кратко о новом
Java 9 - кратко о новом
 
Урок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибокУрок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибок
 
Custom Language Plugin for JetBrains IDEA
Custom Language Plugin for JetBrains IDEACustom Language Plugin for JetBrains IDEA
Custom Language Plugin for JetBrains IDEA
 
Netpeak Talks #3: Масштабируемое приложение на PHP
Netpeak Talks #3: Масштабируемое приложение на PHPNetpeak Talks #3: Масштабируемое приложение на PHP
Netpeak Talks #3: Масштабируемое приложение на PHP
 
Интервью с Дмитрием Вьюковым – автором верификатора Relacy Race Detector (RRD)
Интервью с Дмитрием Вьюковым – автором верификатора Relacy Race Detector (RRD)Интервью с Дмитрием Вьюковым – автором верификатора Relacy Race Detector (RRD)
Интервью с Дмитрием Вьюковым – автором верификатора Relacy Race Detector (RRD)
 
Константин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороныКонстантин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороны
 
Модифицируемость программных систем
Модифицируемость программных системМодифицируемость программных систем
Модифицируемость программных систем
 
VivaMP, система выявления ошибок в коде параллельных программ на языке С++, и...
VivaMP, система выявления ошибок в коде параллельных программ на языке С++, и...VivaMP, система выявления ошибок в коде параллельных программ на языке С++, и...
VivaMP, система выявления ошибок в коде параллельных программ на языке С++, и...
 

Similar to Построение систем автоматического протоколирования Си/Си++ кода

Soft labs. достижима ли в c++ эффективность языка среднего уровня
Soft labs. достижима ли в c++ эффективность языка среднего уровняSoft labs. достижима ли в c++ эффективность языка среднего уровня
Soft labs. достижима ли в c++ эффективность языка среднего уровняLuxoftTraining
 
Тестирование параллельных программ
Тестирование параллельных программТестирование параллельных программ
Тестирование параллельных программTatyanazaxarova
 
Mva stf module 1 - rus
Mva stf module 1 - rusMva stf module 1 - rus
Mva stf module 1 - rusMaxim Shaptala
 
Mobile Monday Kiev#1 - How to save time in Mobile Apps Development
Mobile Monday Kiev#1 - How to save time in Mobile Apps DevelopmentMobile Monday Kiev#1 - How to save time in Mobile Apps Development
Mobile Monday Kiev#1 - How to save time in Mobile Apps DevelopmentIntersog
 
Presentation for students about Laravel.pptx
Presentation for students about Laravel.pptxPresentation for students about Laravel.pptx
Presentation for students about Laravel.pptxGetOverHere
 
Проектирование программных систем. Занятие 4
Проектирование программных систем. Занятие 4Проектирование программных систем. Занятие 4
Проектирование программных систем. Занятие 4Dima Dzuba
 
Inroducing SAP ABAP - Presentation with basics SAP ABAP
Inroducing SAP ABAP - Presentation with basics SAP ABAPInroducing SAP ABAP - Presentation with basics SAP ABAP
Inroducing SAP ABAP - Presentation with basics SAP ABAPmikhailshurgulaya
 
Автоматизация design patterns и компактный код вместе с PostSharp
Автоматизация design patterns и компактный код вместе с PostSharpАвтоматизация design patterns и компактный код вместе с PostSharp
Автоматизация design patterns и компактный код вместе с PostSharpgeekfamilyrussia
 
Руководство по формату событий для разработчиков
Руководство по формату событий для разработчиковРуководство по формату событий для разработчиков
Руководство по формату событий для разработчиковOlesya Shelestova
 
10 компонентные и офисные приложения на платформе microsoft
10 компонентные и офисные приложения на платформе microsoft10 компонентные и офисные приложения на платформе microsoft
10 компонентные и офисные приложения на платформе microsoftKewpaN
 
История одной трансформации: как Magento 1 разработчику быстро переориентиров...
История одной трансформации: как Magento 1 разработчику быстро переориентиров...История одной трансформации: как Magento 1 разработчику быстро переориентиров...
История одной трансформации: как Magento 1 разработчику быстро переориентиров...Magecom UK Limited
 
Open source infrastructure
Open source infrastructureOpen source infrastructure
Open source infrastructureAlexei Fedotov
 
Frontend: Путешествие в мир модульных загрузчиков
Frontend: Путешествие в мир модульных загрузчиковFrontend: Путешествие в мир модульных загрузчиков
Frontend: Путешествие в мир модульных загрузчиковCodeFest
 
Юрий Василевский "Автоматизация в XCode"
Юрий Василевский "Автоматизация в XCode"Юрий Василевский "Автоматизация в XCode"
Юрий Василевский "Автоматизация в XCode"Yandex
 
Юрий Василевский «Автоматизация в XCode»
Юрий Василевский «Автоматизация в XCode»Юрий Василевский «Автоматизация в XCode»
Юрий Василевский «Автоматизация в XCode»Yandex
 
Отладка веб-приложений на Javascript
Отладка веб-приложений на JavascriptОтладка веб-приложений на Javascript
Отладка веб-приложений на JavascriptDenis Latushkin
 

Similar to Построение систем автоматического протоколирования Си/Си++ кода (20)

Soft labs. достижима ли в c++ эффективность языка среднего уровня
Soft labs. достижима ли в c++ эффективность языка среднего уровняSoft labs. достижима ли в c++ эффективность языка среднего уровня
Soft labs. достижима ли в c++ эффективность языка среднего уровня
 
Тестирование параллельных программ
Тестирование параллельных программТестирование параллельных программ
Тестирование параллельных программ
 
Lekcia3
Lekcia3Lekcia3
Lekcia3
 
Mva stf module 1 - rus
Mva stf module 1 - rusMva stf module 1 - rus
Mva stf module 1 - rus
 
СУП
СУПСУП
СУП
 
190
190190
190
 
Mobile Monday Kiev#1 - How to save time in Mobile Apps Development
Mobile Monday Kiev#1 - How to save time in Mobile Apps DevelopmentMobile Monday Kiev#1 - How to save time in Mobile Apps Development
Mobile Monday Kiev#1 - How to save time in Mobile Apps Development
 
Presentation for students about Laravel.pptx
Presentation for students about Laravel.pptxPresentation for students about Laravel.pptx
Presentation for students about Laravel.pptx
 
Deep storm presentation
Deep storm presentationDeep storm presentation
Deep storm presentation
 
Проектирование программных систем. Занятие 4
Проектирование программных систем. Занятие 4Проектирование программных систем. Занятие 4
Проектирование программных систем. Занятие 4
 
Inroducing SAP ABAP - Presentation with basics SAP ABAP
Inroducing SAP ABAP - Presentation with basics SAP ABAPInroducing SAP ABAP - Presentation with basics SAP ABAP
Inroducing SAP ABAP - Presentation with basics SAP ABAP
 
Автоматизация design patterns и компактный код вместе с PostSharp
Автоматизация design patterns и компактный код вместе с PostSharpАвтоматизация design patterns и компактный код вместе с PostSharp
Автоматизация design patterns и компактный код вместе с PostSharp
 
Руководство по формату событий для разработчиков
Руководство по формату событий для разработчиковРуководство по формату событий для разработчиков
Руководство по формату событий для разработчиков
 
10 компонентные и офисные приложения на платформе microsoft
10 компонентные и офисные приложения на платформе microsoft10 компонентные и офисные приложения на платформе microsoft
10 компонентные и офисные приложения на платформе microsoft
 
История одной трансформации: как Magento 1 разработчику быстро переориентиров...
История одной трансформации: как Magento 1 разработчику быстро переориентиров...История одной трансформации: как Magento 1 разработчику быстро переориентиров...
История одной трансформации: как Magento 1 разработчику быстро переориентиров...
 
Open source infrastructure
Open source infrastructureOpen source infrastructure
Open source infrastructure
 
Frontend: Путешествие в мир модульных загрузчиков
Frontend: Путешествие в мир модульных загрузчиковFrontend: Путешествие в мир модульных загрузчиков
Frontend: Путешествие в мир модульных загрузчиков
 
Юрий Василевский "Автоматизация в XCode"
Юрий Василевский "Автоматизация в XCode"Юрий Василевский "Автоматизация в XCode"
Юрий Василевский "Автоматизация в XCode"
 
Юрий Василевский «Автоматизация в XCode»
Юрий Василевский «Автоматизация в XCode»Юрий Василевский «Автоматизация в XCode»
Юрий Василевский «Автоматизация в XCode»
 
Отладка веб-приложений на Javascript
Отладка веб-приложений на JavascriptОтладка веб-приложений на Javascript
Отладка веб-приложений на Javascript
 

More from Tatyanazaxarova

Урок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияУрок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияTatyanazaxarova
 
Урок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программУрок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программTatyanazaxarova
 
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокУрок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокTatyanazaxarova
 
Урок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиУрок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиTatyanazaxarova
 
Урок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурУрок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурTatyanazaxarova
 
Урок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхУрок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхTatyanazaxarova
 
Урок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияУрок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияTatyanazaxarova
 
Урок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиУрок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиTatyanazaxarova
 
Урок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметикаУрок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметикаTatyanazaxarova
 
Урок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхУрок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхTatyanazaxarova
 
Урок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейУрок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейTatyanazaxarova
 
Урок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметикаУрок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметикаTatyanazaxarova
 
Урок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаУрок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаTatyanazaxarova
 
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовУрок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовTatyanazaxarova
 
Урок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаУрок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаTatyanazaxarova
 
Урок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокУрок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокTatyanazaxarova
 
Урок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеУрок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеTatyanazaxarova
 
Урок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложенияУрок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложенияTatyanazaxarova
 
Урок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииУрок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииTatyanazaxarova
 
PVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийPVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийTatyanazaxarova
 

More from Tatyanazaxarova (20)

Урок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияУрок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окружения
 
Урок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программУрок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программ
 
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокУрок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
 
Урок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиУрок 24. Фантомные ошибки
Урок 24. Фантомные ошибки
 
Урок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурУрок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структур
 
Урок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхУрок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данных
 
Урок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияУрок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. Исключения
 
Урок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиУрок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен данными
 
Урок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметикаУрок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметика
 
Урок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхУрок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединениях
 
Урок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейУрок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателей
 
Урок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметикаУрок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметика
 
Урок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаУрок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвига
 
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовУрок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
 
Урок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаУрок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числа
 
Урок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокУрок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибок
 
Урок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеУрок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном коде
 
Урок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложенияУрок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложения
 
Урок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииУрок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурации
 
PVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийPVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложений
 

Построение систем автоматического протоколирования Си/Си++ кода

  • 1. Построение систем автоматического протоколирования Си/Си++ кода Автор: Андрей Карпов Дата: 13.05.2008 Аннотация Иногда единственным методом отладки является использование протоколирования событий приложения. К недостаткам протоколирования (логирования) можно отнести большой объем кода, который приходится писать вручную для сохранения всей необходимой информации. В статье рассматривается методика, позволяющая построить систему автоматического протоколирования кода на языке Си/Си++. Введение Несмотря на время, языки Си и Си++ продолжают занимать лидирующие позиции во многих областях программирования. И в ближайшие 10 лет вряд ли что-то существенно изменится. Появилось много красивых и интересных языков, однако на практике часто предпочтение все-таки отдается Си и Си++. Это одни из лучших существующих языков, универсальные, высокоэффективные и с широкой поддержкой. Если вы хотите лучше понять, чем же привлекательны эти языки, то я предлагаю вам познакомиться со статьей "Инструменты и производительность" [1] и интересной подборкой сообщений из форума comp.lang.c++.moderated "Why do you program in c++?" [2]. Все это значит, что разработка инструментария для языков Си/Си++ по прежнему актуальна. Во всех языках разработчики допускают ошибки, и языки Си/Си++ здесь не исключение. Более того, как и многие профессиональные инструменты, они требуют большего внимания и аккуратности при их использовании. В результате одной из важнейших задач при разработке приложений является отладка кода, то есть поиск и устранение ошибок. Способы отладки приложений можно разделить на следующие основные группы: • Интерактивные средства отладки; • Runtime-диагностика; • Визуальные (графические) средства отладки; • Модульные тесты (Unit Testing); • Функциональные тесты; • Протоколирование; • Отладка по крэш-дампам; • Визуальный просмотр кода (Code Review); • Статический анализ кода.
  • 2. У каждого из перечисленных методов есть свои достоинства и недостатки, с которыми можно познакомиться в статье "Способы отладки приложений" [3]. Но в рамках нашей статьи мы поговорим о протоколировании (логировании) и методах его автоматизации. 1. Почему протоколирование? На первый взгляд протоколирование работы приложений может показаться неактуальным. Возможно это атавизм того времени, когда еще было принято выводить результаты работы программы сразу на принтер? Нет, это очень эффективная и часто незаменимая методика, позволяющая отлаживать сложные, параллельные или специфические приложения. Рассмотрим области, где протоколирование является незаменимым по удобству и эффективности решением: 1. Отладка оптимизированных (Release) сборок приложения. Иногда Release версия ведет себя не так как Debug, что может быть связано с ошибками неинициализированной памяти и т.д. Но работать с Release версией в отладчике бывает часто неудобно. А еще, хотя это бывает крайне редко, встречаются ошибки компилятора, которые тоже проявляются только в Release версиях. В таких ситуациях протоколирование представляет собой хорошую замену отладчику. 2. Отладка механизмов защиты. Часто разработка приложений с аппаратной защитой (например, на основе ключей Hasp) крайне затруднена, так как отладка невозможна. Протоколирование в этом случае, является, чуть ли не единственным способом поиска ошибок. 3. Протоколирование - это единственно возможный вариант отладки для приложения, которое запускается на компьютере у конечного пользователя. Грамотное использование файла протокола позволит разработчикам получить полную информацию, необходимую для диагностики возникающих у пользователя проблем. 4. Протоколирование позволяет отлаживать драйверы устройств и программы для встроенных систем. 5. Протоколирование позволяет быстрее обнаруживать ошибки после пакетного запуска функциональных или нагрузочных тестов. 6. Еще одним интересным способом использования логов является просмотр отличий (diff) двух различных версий. Читателю предлагается самому подумать, где такая возможность была бы ему полезна в его проектах. 7. Протоколирование дает возможность удаленной отладки, когда интерактивные средства невозможны или труднодоступны. Это удобно для высокопроизводительных многопользовательских систем, где пользователь ставит свои задачи в очередь, а затем дожидается их выполнения. Такой подход используется и в наши дни в институтах и других организациях, работающих с вычислительными кластерами. 8. Возможность отладки параллельных приложений. В таких приложениях ошибки часто происходят при создании большого количества потоков или при наличии проблем с синхронизацией. Ошибки в параллельных программах бывает достаточно сложно отладить. Надежным методом отлова таких ошибок является периодическое протоколирование состояния систем, которые имеют отношение к ошибке, и исследование данных лога после некорректного завершения работы [1].
  • 3. Основательный список, чтобы не списывать метод протоколирования на пенсию. Хочется надеяться, что статья натолкнет и на другие мысли, как можно с пользой использовать описываемую методологию протоколирования. 2. Создание системы протоколирования Начнем с требований, которыми должна обладать современная система протоколирования: • Код, обеспечивающий протоколирование данных в отладочной версии, должен отсутствовать в конечной версии программного продукта. Во-первых, это связано с увеличением быстродействия и уменьшением размера программного продукта. Во- вторых, не позволяет использовать отладочную информацию для взлома приложения или иных несанкционированных действий. Заметим, что имеется в виду именно конечная версия программы, так как лог могут создавать и Debug и Release версии. • Интерфейсы системы протоколирования должны быть лаконичны, чтобы не загромождать основной код программы. • Сохранение данных должно осуществляться как можно быстрее, чтобы вносить минимальное изменение во временные характеристики параллельных алгоритмов. • Полученный лог должен быть наглядным и легко поддаваться анализу. Должна существовать возможность разделить информацию, полученную от различных потоков, а также варьировать ее уровень подробности. • Кроме протоколирования непосредственных событий приложения полезно выполнить сбор сведений о компьютере. • Желательно, чтобы система сохраняла имя модуля, имя файла и номер строки, в которой произошла запись данных. В ряде случаев полезно хранить время наступления события. Система протоколирования, отвечающая таким качествам, позволяет универсально решать задачи, начиная от разработки механизмов защиты до поиска ошибок в параллельных алгоритмах [4]. Данная статья, хотя и посвящена системе протоколирования данных, в ней не будет рассматриваться какой-то завершенный вариант такой системы. Универсальный вариант невозможен, так как он сильно зависит от среды разработки, особенностей проекта, предпочтений разработчика и многого другого. Кстати отметим, что существует ряд статей, в которых рассматриваются эти вопросы. Например, "Logging In C++" [5] и "Способы отладки приложений: Протоколирование" [6]. Сейчас мы остановимся на рассмотрении ряда технических решений, которые помогут вам создать удобную и эффективную систему протоколирования, если в том возникнет необходимость. Самым простым способом осуществить протоколирование является использование функции, аналогичной printf, как показано в примере: int x = 5, y = 10; ... printf("Coordinate = (%d, %d)n", x, y);
  • 4. Естественным недостатком является то, что информация будет выводиться как в отладочном режиме, так и в конечном продукте. Поэтому, следует модернизировать код следующим образом: #ifdef DEBUG_MODE #define WriteLog printf #else #define WriteLog(a) #endif WriteLog("Coordinate = (%d, %d)n", x, y); Это уже лучше. Причем обратите внимание, что мы используем для выбора реализации функции WriteLog не стандартный макрос _DEBUG, а собственный макрос DEBUG_MODE. Это позволяет включать отладочную информацию в Release-версии, что важно при отладке на большом объеме данных. К сожалению, теперь при компиляции не отладочной версии, например, в среде Visual C++ возникает предупреждение: "warning C4002: too many actual parameters for macro 'WriteLog'". Можно отключить это предупреждение, но это является плохим стилем. Можно переписать код, как показано ниже: #ifdef DEBUG_MODE #define WriteLog(a) printf a #else #define WriteLog(a) #endif WriteLog(("Coordinate = (%d, %d)n", x, y)); Приведенный код не является элегантным, так как приходится использовать двойные пары скобок, что часто забывается. Поэтому внесем новое усовершенствование: #ifdef DEBUG_MODE #define WriteLog printf #else inline int StubElepsisFunctionForLog(...) { return 0; } static class StubClassForLog { public: inline void operator =(size_t) {} private: inline StubClassForLog &operator =(const StubClassForLog &)
  • 5. { return *this; } } StubForLogObject; #define WriteLog StubForLogObject = sizeof StubElepsisFunctionForLog #endif WriteLog("Coordinate = (%d, %d)n", x, y); Этот код выглядит сложным, но он позволяет писать одинарные скобки. При выключенном DEBUG_MODE этот код превращается в ничто, и его можно смело использовать в критических участках кода. Следующим усовершенствованием может стать добавление к функции протоколирования таких параметров как уровень детализации и тип выводимой информации. Уровень детализации можно задать как параметр, например: enum E_LogVerbose { Main, Full }; #ifdef DEBUG_MODE void WriteLog(E_LogVerbose, const char *strFormat, ...) { ... } #else ... #endif WriteLog (Full, "Coordinate = (%d, %d)n", x, y); Этот способ удобен тем, что решение, отфильтровать или не отфильтровать маловажные сообщения можно принять уже после завершения работы программы, используя специальную утилиту. Недостаток такого метода в том, что всегда происходит вывод всей информации, как важной, так и второстепенной, что может снижать производительность. Поэтому можно создать несколько функций вида WriteLogMain, WriteLogFull и подобные им, реализация которых будет зависеть от режима сборки программы.
  • 6. Мы упоминали о том, что запись отладочной информации должна как можно меньше влиять на скорость работы алгоритма. Этого можно достичь, создав систему накопления сообщений, запись которых происходит в параллельно выполняемом потоке. Сейчас это получило дополнительные преимущества в связи с широким распространением многоядерных (многопроцессорных) систем. Схематично этот механизм представлен на рисунке N1.
  • 7. Рисунок N1. Система протоколирования с отложенной записью данных. Как можно видеть на рисунке, запись очередной порции данных происходит в промежуточный массив строк фиксированной длины. Фиксированный размер массива и строк в нем позволяет исключить дорогостоящие операции выделения памяти. Это нисколько не снижает возможности такой системы. Достаточно выбрать длину строк и размер массива с запасом. Например, 5000 строк длиной в 4000 символов будет достаточно для отладки практически любой системы. А объем памяти в 20 мегабайт необходимый для этого, согласитесь, не критичен для современных систем. Если же массив все равно будет переполнен, то несложно предусмотреть механизм досрочной записи информации в файл. Приведенный механизм обеспечивает практически моментальное выполнение функции WriteLog. Если в системе присутствуют ненагруженные процессорные ядра, то и запись в файл будет прозрачна для основных потоков протоколируемой программы. Преимущество описываемой системы в том, что она практически без изменений способна функционировать при отладке параллельной программы, когда в лог пишут сразу несколько потоков. Следует только добавить сохранение идентификатора потока (thread), чтобы потом можно было узнать, от каких потоков были получены сообщения (смотри рисунок N2).
  • 8.
  • 9. Рисунок N2. Система протоколирования при отладке многопоточных приложений. Такая схема может привести к изменению временных характеристик, так как несколько протоколируемых потоков вынуждены ждать друг друга, чтобы осуществить запись информации. Если это является критичным, то можно создать отдельные хранилища сообщений для каждого из потоков, как показано на рисунке N3. Дополнительно, в этом случае следует записывать время событий, чтобы было возможно слить два лога в один.
  • 10.
  • 11. Рисунок N3. Улучшенная система протоколирования при отладке многопоточных приложений. Последнее усовершенствование, которое хочется предложить, это организация показа уровня вложенности сообщений при вызове функций или начале логического блока. Это можно легко организовать, используя специальный класс, который в конструкторе записывает в лог идентификатор начала блока, а в деструкторе - идентификатор конца блока. Написав небольшую утилиту, можно трансформировать лог, опираясь на информацию об идентификаторах. Попробуем показать это на примере.
  • 12. Код программы: class NewLevel { public: NewLevel() { WriteLog("__BEGIN_LEVEL__n"); } ~NewLevel() { WriteLog("__END_LEVEL__n"); } }; #define NEW_LEVEL NewLevel tempLevelObject; void MyFoo() { WriteLog("Begin MyFoo()n"); NEW_LEVEL; int x = 5, y = 10; printf("Coordinate = (%d, %d)n", x, y); WriteLog("Begin Loop:n"); for (unsigned i = 0; i != 3; ++i) { NEW_LEVEL; WriteLog("i=%un", i); } } Содержимое лога: Begin MyFoo() __BEGIN_LEVEL__ Coordinate = (5, 10) Begin Loop: __BEGIN_LEVEL__ i=0 __END_LEVEL__ __BEGIN_LEVEL__ i=1 __END_LEVEL__
  • 13. __BEGIN_LEVEL__ i=2 __END_LEVEL__ Coordinate = (5, 10) __END_LEVEL__ Лог после трансформации: Begin MyFoo() Coordinate = (5, 10) Begin Loop: i=0 i=1 i=2 Coordinate = (5, 10) 3. Автоматизация системы протоколирования Мы рассмотрели принципы, на которых может быть реализована система протоколирования. В такой системе можно реализовать все требования, описанные в первой части статьи. Но серьезным недостатком в применении такой системы становится необходимость написания большого количества кода для записи всех необходимых данных. Есть и другие недостатки: 1. Невозможность красиво реализовать функции протоколирования для языка Си, так как там отсутствуют классы и шаблоны. В результате код для протоколирования должен выглядеть по разному в Си и Си++ файлах. 2. Необходимость помнить о необходимости написания NEW_LEVEL в каждом блоке или функции, если есть желание, чтобы лог выглядел красиво. 3. Невозможно автоматически сохранять имена всех вызываемых функций. Необходимость вручную записывать входные аргументы функций. 4. Загромождение исходных текстов дополнительными конструкциями, такими как, например NEW_LEVEL. 5. Приходится прикладывать усилия, чтобы контролировать, что все конструкции протоколирования будут исключены из конечной версии программы. 6. Приходится прописывать функции для инициализации системы протоколирования, прописывать необходимые "#include" и делать иные вспомогательные действия. Все эти и ряд других неудобств можно избежать, если построить систему автоматического протоколирования испытаний работы приложений. Реализовать такую систему возможно на основе методологии метапрограммирования, введя в язык Си/Си++ новые конструкции для записи данных.
  • 14. Метапрограммирование - это создание программ, которые создают другие программы [7]. В метапрограммировании можно выделить два основных направления: генерация кода и самомодифицирующийся код. Нас интересует генерация кода. В этом случае код программы с встроенными механизмами протоколирования не пишется вручную, а создается автоматически программой-генератором на основе другой, более простой программы. Это позволяет получить программу при меньших затратах времени и усилий, чем если бы программист самостоятельно реализовывал весь код. Существуют языки, для которых метапрограммирование является естественно составной частью. Примером является язык Nemerle, с которым можно познакомиться в статье "Метапрограммирование в Nemerle" [8]. Но в случае с Си/Си++ все сложнее, и в них метапрограммирование реализуется следующими двумя путями: 1. Шаблоны в Си++ и препроцессор в Си. К сожалению, как было показано выше, этого недостаточно. 2. Внешние языковые средства. Язык генератора составляется так, чтобы автоматически или с минимальными усилиями со стороны программиста реализовывать правила парадигмы или необходимые специальные функции. Фактически создается более высокоуровневый язык программирования. Именно этот подход может быть использован для создания системы автоматизации протоколирования программ. Введя в язык Си/Си++ новые ключевые слова, можно получить гибкую и удобную систему программирования с мощнейшими возможностями протоколирования. Использование метаязыка открывает широкие возможности в выборе оформления записи необходимой информации. Можно протоколировать данные, введя привычный формат функций: EnableLogFunctionCallForFhisFile(true); ... ObjectsArray &objects = getObjects(); WriteLog("objects.size = %u", objects.size()); for (size_t i = 0; i != objects.size(); ++i) { WriteLog("object type = %i", int(objects [i]->getType())); if (objects[i]->getType() != TYPE_1) ... Поскольку генератор кода может распознать используемые типы данных, то можно упростить протоколирование переменных и сократить запись. Например. ObjectsArray &objects = getObjects(); MyLog "objects.size = ", objects.size() for (size_t i = 0; i != objects.size(); ++i) { MyLog "object type = ", int(objects [i]->getType()) if (objects[i]->getType() != TYPE_1)
  • 15. ... Можно шагнуть дальше, так как здесь все зависит от фантазии автора: thisfile.log.FunctionCall = ON ... ObjectsArray &objects = getObjects(); for (size_t i = 0; i != <log>"objects.size = "objects.size()<log>; ++i) { LOG: "object type = " {as int} objects [i]->getType(); if (objects[i]->getType() != TYPE_1) ... Новый метаязык можно реализовать в качестве промежуточного звена между препроцессором и компилятором. Что именно должен представлять этот промежуточный компонент для трансляции метапрограммы зависит от среды разработки. В общем случае схему функционирования можно представить, как показано на рисунке N4.
  • 16. Рисунок 4. Участие транслятора метаязыка в процессе компиляции. Теперь рассмотрим сам транслятор. Его принципиальная схема представлена на рисунке N5.
  • 17.
  • 18. Рисунок 5. Принципиальная схема транслятора языка для генерации кода с поддержкой протоколирования. Остановимся на генераторе кода более подробно. Генератор представляет собой алгоритм обхода дерева и выполнения над ним трех операций. Во-первых, получение типов всех функций и объектов, определение области их видимости. Это позволит автоматически генерировать код для корректной записи аргументов функций и иных объектов. Во-вторых, происходит раскрытие конструкций метаязыка в новые ветви дерева. И последнее - запись обработанных ветвей кода обратно в файл. Реализовать самостоятельно такой генератор дело достаточное непростое, впрочем, как и синтаксический анализатор. Но существуют соответствующие библиотеки, о которых будет сказано ниже. Посмотрим, как генерация кода программы решает перечисленные ранее недостатки системы, построенной исключительно на основе макросов и шаблонов: 1. Конструкции протоколирования в языке Си могут выглядеть также элегантно, как и в Си++. Генератор берет на себя заботу о превращении зарезервированных слов в код для записи сообщений. То, что этот код будет выглядеть по разному, для Си и Си++ файлов скрыто от пользователя. 2. Генератор может автоматически вставлять код для выделения начала и конца функций или блоков. Отпадает необходимость использования макроса NEW_LEVEL. 3. Можно автоматически сохранять имена всех или части вызываемых функций. Можно автоматически сохранять и значения входных параметров для базовых типов данных. 4. Исчезает загромождение текста вспомогательными конструкциями. 5. Можно гарантировать полное исчезновение всех функций, ресурсов и объектов из конечной версии программного продукта. Генератору достаточно пропустить все специальные элементы, относящиеся к протоколированию в тексте программы. 6. Исчезает необходимость прописывать функции инициализации, вставлять "#include" и совершать другие вспомогательные действия, так как они могут быть реализованы на этапе преобразования кода. К подводным камням предложенной схемы следует отнести проблему с отладкой таких модифицированных программ с помощью отладчика. С одной стороны, если имеется система протоколирования, то использование отладчика не столь важный компонент разработки приложения. Но все-таки часто отладчик бывает крайне полезным инструментом, отказываться от которого не хочется. Проблема в том, что после преобразования кода (раскрытия операторов для протоколирования) возникнет проблема навигации по номерам строк. Здесь могут помочь специфические программные меры, которые будут индивидуальны для каждой среды разработки. Можно пойти более простым путем. И применить подход, аналогичный подходу в OpenMP, то есть использовать "#pragma". В этом случае код протоколирования будет выглядеть так: ObjectsArray &objects = getObjects(); #pragma Log("objects.size = ", objects.size()) for (size_t i = 0; i != objects.size(); ++i) {
  • 19. #pragma Log("object type = ", objects [i]->getType()) if (objects[i]->getType() != TYPE_1) ... Это несколько менее красиво из-за лишнего слова "#pragma", но у такого текста программы имеются большие преимущества. Такой код может быть безболезненно скомпилирован на другой системе, где не используется система автоматического протоколирования. Такой код легко может быть перенесен на другую систему или отдан сторонним разработчикам. И конечно нет никаких препятствий для работы с этим кодом в отладчике. 4. Инструментарий Автору, к сожалению, не известны инструменты автоматизированного протоколирования на основе метаязыка, близкие по функциональности к описываемой в статье системе. Если дальнейшие исследования покажут отсутствие подобных разработок, то автор, возможно, примет участие в новом проекте, который сейчас находится на этапе проектирования. Можно сказать, что эта статья является своего рода исследованием актуальности создания универсальной системы протоколирования для языков Си/Си++. Пока же заинтересовавшимся читателям предлагается самостоятельно создать систему протоколирования. Будет это просто набор шаблонов и макросов, или полноценная система генерации кода, зависит от востребованности такой системы и количества ресурсов для ее создания. Если вы остановитесь на создании метаязыка, то мы подскажем на основе чего можно его создать. Существует две наиболее подходящие бесплатные библиотеки, позволяющие представить программу в виде дерева, преобразовать ее и вновь сохранить в виде программы. Первая из них OpenC++ (OpenCxx) [9]. Эта открытая бесплатная библиотека представляющий собой "source-to-source" транслятор. Библиотека поддерживает метапрограммирование и позволяет создавать на своей основе расширения языка Си++. На основе этой библиотеки созданы такие решения как среда исполнения OpenTS для языка программирования T++ (разработка Института программных систем РАН) и инструмент Synopsis для подготовки документации по исходному коду. Второй библиотекой является VivaCore, которая представляет собой развитие библиотеки OpenC++, ориентированное на среду разработки Microsoft Visual Studio 2005/2008 [10]. Эта библиотека также открыта и бесплатна. К основным отличиям можно отнести реализованную поддержку языка Си и поддержку некоторых современных элементов языка Си++. Примером использования библиотеки являются статические анализаторы Viva64 и VivaMP. Если вы захотите использовать эти библиотеки для создания своих расширений, то вам понадобится препроцессор, который должен быть запущен до начала их работы. Скорее всего, встроенные в компилятор средства препроцессирования будут достаточны. Если нет, то вы можете попробовать воспользоваться библиотекой The Wave C++ preprocessor library.
  • 20. Заключение Статья носит теоретический характер, но хочется надеяться, что разработчики найдут в ней много интересных и полезных для себя идей в области протоколирования кода своих программ. Успехов! Библиографический список 1. Casual Games Association. Инструменты и производительность. http://www.viva64.com/go.php?url=34 2. Over at comp.lang.c++.moderated, there is a thread created by the c++ guru Scott Meyers on the subject: "Why do you program in c++?" http://www.viva64.com/go.php?url=35 3. Дмитрий Долгов. Способы отладки приложений. http://www.viva64.com/go.php?url=36 4. Андрей Карпов, Евгений Рыжков. Разработка ресурсоемких приложений в среде Visual C++. http://www.viva64.com/art-1-1-640027853.html 5. Petru Marginean. Logging In C++. http://www.viva64.com/go.php?url=37 6. Дмитрий Долгов. Способы отладки приложений: Протоколирование. http://www.viva64.com/go.php?url=38 7. Джонатан Бартлет. Искусство метапрограммирования, Часть 1: Введение в метапрограммирование. http://www.viva64.com/go.php?url=39 8. Kamil Skalski. Метапрограммирование в Nemerle. http://www.viva64.com/go.php?url=40 9. Grzegorz Jakack. OpenC++ - A C++ Metacompiler and Introspection Library. http://www.viva64.com/go.php?url=41 10. Андрей Карпов, Евгений Рыжков. Сущность библиотеки анализа кода VivaCore. http://www.viva64.com/art-2-1-696420215.html Об авторе Карпов Андрей Николаевич, к.ф.-м.н., научный консультант компании "СиПроВер", специализирующейся на вопросах повышения качества программных систем. Занимается теоретическими и практическими вопросами статического анализа кода. Участвует в проектах по созданию библиотеки VivaCore и анализаторов кода Viva64, VivaMP. Страница на сайте Мой Круг: http://a-karpov.moikrug.ru E-Mail: karpov@viva64.com