Урок 12. Паттерн 4. Виртуальные функции

24.01.2012

Бывают ошибки, в которых, в общем-то, никто не виноват, но они от этого не перестают быть ошибками. Представьте, что давным-давно (в Visual Studio 6.0) был разработан проект, в котором присутствует класс CSampleApp, являющийся наследником от CWinApp. В базовом классе есть виртуальная функция WinHelp. Наследник перекрывает эту функцию и выполняет необходимые действия. Визуально это представлено на рисунке 1.

Рисунок 1 - Работоспособный корректный код, который создан в Visual Studio 6.0

Рисунок 1 - Работоспособный корректный код, который создан в Visual Studio 6.0

Затем проект переносится на Visual Studio 2005, где прототип функции WinHelp изменился, но этого никто не замечает, так как в 32-битном режиме типы DWORD и DWORD_PTR совпадают и программа продолжает корректно работать (рисунок 2).

Рисунок 2 - Не корректный, но работоспособный 32-битный код

Рисунок 2 - Не корректный, но работоспособный 32-битный код

Ошибка ждет, чтобы проявить себя в 64-битной системе, где размер типов DWORD и DWORD_PTR различен (рисунок 3). Получается, что в 64-битном режиме классы содержат две РАЗНЫЕ функции WinHelp, что естественно некорректно. Учтите, что подобные ловушки могут скрываться не только в MFC, где часть функций изменили типы своих аргументов, но и в коде ваших приложений и сторонних библиотек.

Рисунок 3 - Ошибка проявляет себя в 64-битном коде

Рисунок 3 - Ошибка проявляет себя в 64-битном коде

Рассмотрим данную ошибку на реальном примере. Есть прекрасная библиотека компонентов BCGControlBar. Наверняка вы про нее слышали, поскольку компоненты компании BCGSoft Ltd включены в Microsoft Visual Studio 2008 Feature Pack. Так вот, если скачать ознакомительную версию этой библиотеки, установить ее и выполнить поиск слова "WinHelp" по .h-файлам... то мы увидим, что везде, где якобы перекрыта эта функция, используется параметр DWORD, вместо DWORD_PTR. А это означает, что справка в 64-битной системе в этих классах будет вести себя некорректно.

Почему подобная ошибка может до сих пор быть в коде такой известной библиотеки? Мы думаем, дело в том, что клиентам компании доступны исходные коды этой библиотеки и клиенты всегда могут поправить эти коды. Кроме того, в настоящее время функция WinHelp используется очень редко. Намного чаще используется HtmlHelp. А она-то в BCGControlBar имеет правильный параметр DWORD_PTR. Однако факт остается фактом. Ошибка есть в реальном коде и компилятор ее не обнаружит. Причем такие ошибки могут оставаться не выявленными годами.

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

Диагностика

Ошибки, связанные с виртуальными функциями в 64-битном коде, могут быть обнаружены статическим анализатором PVS-Studio. Анализатор предупредит об опасных виртуальных функциях диагностическим сообщением V301.

Виртуальная функция считается опасной, если:

  • Функция объявлена в базовом классе и в классе-потомке.
  • Типы аргументов функций не совпадают, но эквивалентны на 32-битной системе (например: unsigned, size_t) и не эквивалентны на 64-битной.

Авторы курса: Андрей Карпов (karpov@viva64.com), Евгений Рыжков (evg@viva64.com).

Правообладателем курса "Уроки разработки 64-битных приложений на языке Си/Си++" является ООО "Системы программной верификации". Компания занимается разработкой программного обеспечения в области анализа исходного кода программ. Сайт компании: http://www.viva64.com.

Контактная информация: e-mail: support@viva64.com, 300027, г. Тула, а/я 1800.