﻿# Забытые проблемы разработки 64\-битных программ

Хотя история развития 64\-битных систем составляет более десятилетия, появление 64\-битных версий операционной системы Windows поставило перед разработчиками новые задачи в области разработки и тестирования программных решений\. В статье рассмотрены некоторые ошибки, связанные с разработкой 64\-битного Си/Си\+\+ кода под операционную систему Windows\. Объяснены причины, по которым данные ошибки не нашли отражения в статьях, посвященных задачам миграции, и неудовлетворительно выявляются большинством статических анализаторов\.

## Введение

История развития [64\-битных](https://pvs-studio.ru/ru/blog/terms/0001/) программных систем не нова и составляет уже более десятилетия \[[1](https://queue.acm.org/detail.cfm?id=1165766)\]\. В 1991 году был выпущен первый 64\-битный микропроцессор MIPS R4000 \[[2](https://en.wikipedia.org/wiki/MIPS_instruction_set), [3](https://yarchive.net/comp/64bit.html)\]\. С тех пор в форумах и статьях возникали дискуссии, посвященные переносу программ на 64\-битные системы\. Началось обсуждение проблем, связанных с разработкой 64\-битных программ на языке Си\. Обсуждались вопросы о том, какая модель данных лучше, что такое **long long** и многое другое\. Вот, например, интересная подборка сообщений \[[4](https://yarchive.net/comp/longlong.html)\] из новостной группы comp\.lang\.c, посвященная использованию типа **long long** в языке Си, которая, в свою очередь, была связана с появлением 64\-битных систем\.

Одним из наиболее распространенных и чувствительных к изменению размерности типов данных является язык Си\. Из\-за его низкоуровневых свойств следует постоянно контролировать корректность программы на этом языке, переносимой на новую платформу\. Естественно, что при появлении 64\-битных систем разработчики по всему миру вновь столкнулись с задачами обеспечения совместимости старого исходного кода с новыми системами\. Одним из косвенных свидетельств сложности проблем миграции является большое количество моделей данных, которые постоянно следует учитывать\. Модель данных \- это соотношение размеров базовых типов в языке программирования\. На рисунке 1 показаны размерность типов в различных моделях данных, на которые мы в дальнейшем будем ссылаться\.

![a0010_Forgotten_problems_ru/image2.png](https://import.viva64.com/docx/blog/a0010_Forgotten_problems_ru/image2.png)

_Рисунок 1\. Модели данных\._

## Существующие публикации и инструменты в сфере верификации 64\-битных приложений

Конечно, это был не первый этап смены разрядности\. Достаточно вспомнить переход с 16\-битных систем на 32\-битные\. Естественно, накопленный опыт оказал свое положительное воздействие на этапе перехода на 64\-битные системы\.

Но переход на 64\-битные системы имел свои нюансы, в результате чего появилась серия исследований и публикаций по данным вопросам, например \[[5](http://www.unix.org/whitepapers/64bit.html), [6](http://www.unix.org/version2/whatsnew/lp64_wp.html), 7\]\.

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

1. Упаковка указателей в типы меньшей размерности\. Например, помещение указателя в тип **int** на системе с моделью данных [LP64](https://pvs-studio.ru/ru/blog/terms/0028/) приведет к обрезанию значения указателя и невозможности его использования в дальнейшем\.
1. Использование магических констант\. Опасность заключается в использовании таких чисел как 4, 32, 0x80000000 и ряда других вместо специализированных констант или использования оператора sizeof\(\)\.
1. Некорректные операции сдвига, не учитывающие увеличение размерности ряда типов\.
1. Использование некорректных объединений или структур без учета выравнивания на системах с различной разрядностью\.
1. Ошибки работы с битовыми полями\.
1. Некорректные арифметические выражения\. Пример:

```cpp
int x = 100000, y = 100000, z = 100000;
long long s = x * y * x;
```

Кроме ошибок, перечисленных в списке, также рассматривались и некоторые другие, более редкие ошибки\.

На основе проведенных исследований вопроса верификации 64\-битного кода были предложены решения, обеспечивающие диагностику опасных конструкций\. Например, такую проверку реализовали в статических анализаторах Gimpel Software PC\-Lint \([http://www\.gimpel\.com](https://gimpel.com/)\) и Parasoft C\+\+test \([http://www\.parasoft\.com](https://www.parasoft.com/)\)\.

Возникает вопрос\. Если 64\-битные системы существуют так давно, существуют статьи, посвященные данной тематике, и даже программные инструменты, обеспечивающие контроль опасных конструкций в коде, так стоит ли возвращаться к этому вопросу?

К сожалению да \- стоит\! Причиной тому служит прогресс, произошедший за эти годы в области информационных технологий\. А актуальность данного вопроса связана с быстрым распространением 64\-битных версий операционной системы Windows\.

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

## Неосвещенные проблемы разработки 64\-битных программ

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

```cpp
for (int x = 0; x != width; ++x)
  for (int y = 0; y != height; ++y)
    for (int z = 0; z != depth; ++z)
      BigArray[z * width * height + y * width + x] = InitValue;
```

В этом примере, выражение "z \* width \* height \+ y \* width \+ x", используемое для адресации, имеет тип **int**, а, следовательно, данный код будет некорректен на массивах, содержащих более 2 GB элементов\. На 64\-битных системах для безопасной индексации к большим массивам следует использовать типы ptrdiff\_t, size\_t или производные от них\. Отсутствие описания такого вида ошибки в статьях объясняется очень просто\. Во времена их написания машины с объемом памяти, позволяющим хранить такие массивы, были практически не доступны\. Сейчас же это становится рядовой задачей в программировании, и с большим удивлением можно наблюдать, как код, верой и правдой служивший многие годы, вдруг перестает корректно работать при использовании больших массивов данных на 64\-битных системах\.

Другой пласт практически неосвещенных проблем, представлен ошибками, связанными с возможностями и особенностями языка Си\+\+\. Почему так произошло, тоже достаточно объяснимо\. Во время внедрения первых 64\-битных систем язык Си\+\+ для них не существовал или он был не распространен\. Поэтому, практически все статьи посвящены проблемам в области языка Си\. Современные авторы заменили название Си на Си/Си\+\+, но нового ничего не добавили\.

Но отсутствие в статьях описания ошибок, специфичных для Си\+\+, не означает, что их нет\. Существуют ошибки, проявляющие себя при переносе программ на 64\-битные системы\. Они связанны с виртуальными функциями, исключениями, перегруженными функциями и так далее\. Более подробно с такими ошибками можно ознакомиться в статье \[[8](https://pvs-studio.ru/ru/blog/posts/cpp/a0004/)\]\. Приведем простой пример, связанный с использованием виртуальных функций:

```cpp
class CWinApp {
  ...
  virtual void WinHelp(DWORD_PTR dwData, UINT nCmd);
};
class CSampleApp : public CWinApp {
  ...
  virtual void WinHelp(DWORD dwData, UINT nCmd);
};
```

Проследим жизненный цикл разработки некоторого приложения\. Пусть первоначально оно разрабатывалось под Microsoft Visual C\+\+ 6\.0, когда функция WinHelp в классе CWinApp имела следующий прототип:

```cpp
virtual void WinHelp(DWORD dwData, UINT nCmd = HELP_CONTEXT);
```

Совершенно верно было осуществить перекрытие виртуальной функции в классе CSampleApp, как показано в примере\. Затем проект был перенесен в Microsoft Visual C\+\+ 2005, где прототип функции в классе CWinApp претерпел изменения, заключающиеся в смене типа DWORD на тип DWORD\_PTR\. На 32\-битной системе программа продолжит совершенно корректно работать, так как здесь типы DWORD и DWORD\_PTR совпадают\. Неприятности проявят себя при компиляции данного кода под 64\-битную платформу\. Получатся две функции с одинаковыми именами, но с различными параметрами, в результате чего перестанет вызываться пользовательский код\.

Помимо особенностей разработки 64\-битных программ с точки зрения языка Си\+\+, существуют и другие тонкие моменты\. Например, особенности, связанные с архитектурой 64\-битной версии Windows\. Хочется заранее предупредить разработчиков о потенциальных проблемах и порекомендовать уделить большее внимание тестированию 64\-битного программного обеспечения \[[9](https://pvs-studio.ru/ru/blog/posts/cpp/a0006/)\]\.

Теперь вернемся к методам верификации исходного кода программы с использованием статических анализаторов\. Я думаю, вы уже угадали, что здесь тоже не все так хорошо, как кажется\. Несмотря на заявленную поддержку диагностирования особенностей 64\-битного кода, эта поддержка на данный момент не удовлетворяет необходимым требованиям\. Причина заключается в том, что диагностические правила были созданы по все тем же статьям, не учитывающим специфику языка Си\+\+ или обработку больших массивов данных, превышающих 2 GB\.

Для Windows\-разработчиков дело обстоит еще хуже\. Основные статические анализаторы рассчитаны на диагностику 64\-битных ошибок для модели данных LP64, в то время как в Windows используется модель данных [LLP64](https://pvs-studio.ru/ru/blog/terms/0026/) \[[10](https://devblogs.microsoft.com/oldnewthing/2005/01/31)\]\. Обусловлено это тем, что 64\-битные версии Windows молоды, а ранее 64\-битные системы были представлены Unix\-подобными системами с моделью данных LP64\.

В качестве примера рассмотрим диагностическое сообщение 3264bit\_IntToLongPointerCast \(port\-10\), генерируемое анализатором Parasoft C\+\+test:

```cpp
int *intPointer;   
long *longPointer;
longPointer = (long *)intPointer; //-ERR port-10
```

C\+\+test предполагает, что с точки зрения модели LP64 данная конструкция будет некорректна\. Но в рамках модели данных, принятой в Windows, данная конструкция будет безопасна\.

## Рекомендации по верификации 64\-битных программ

Хорошо, \- скажете Вы, \- проблемы разработки 64\-битных версий программ действительно актуальны\. Но как найти все эти ошибки?

Исчерпывающий ответ дать невозможно, но можно привести ряд рекомендаций, которые в сумме позволят обеспечить безопасную миграцию на 64\-битные системы и обеспечить необходимый уровень надежности:

* Ознакомьте Ваших коллег, связанных с разработкой 64\-битых приложений, со следующими статьями: \[7, [8](https://pvs-studio.ru/ru/blog/posts/cpp/a0004/), [9](https://pvs-studio.ru/ru/blog/posts/cpp/a0006/), [10](https://devblogs.microsoft.com/oldnewthing/2005/01/31), [11](https://www.drdobbs.com/parallel/multiplatform-porting-to-64-bits/184406427), [12](https://msdn.microsoft.com/en-us/library/ms973190.aspx), [13](https://msdn.microsoft.com/en-us/library/ee418798.aspx)\]\.
* Ознакомьте Ваших коллег с методологией статического анализа кода: \[[14](https://www.drdobbs.com/cpp/bulletproofing-c-code/196802351), [15](https://www.drdobbs.com/architecture-and-design/integrate-static-analysis-into-a-softwar/193500967?cid=rssfeed_ddj_debugging)\]\. Статическая верификация кода \- один из лучших способов поиска такого рода ошибок\. Она позволяет убедиться в работоспособности даже тех частей кода, работу которых на больших объемах данных сложно смоделировать в реальности, например, при использовании методологии юнит\-тестов\.
* Разработчикам будет полезно познакомиться со статическими анализаторами, такими как Parasoft C\+\+test \([www\.parasoft\.com](https://www.parasoft.com/)\), Gimpel Software PC\-lint \([www\.gimpel\.com](https://gimpel.com/)\), Abraxas Software CodeCheck \([www\.abxsoft\.com](http://www.abxsoft.com/)\)\.
* Для разработчиков Windows\-приложений особенно полезно будет знакомство со специализированным статическим анализатором [Viva64](https://pvs-studio.ru/ru/pvs-studio/), рассчитанным на модель данных LLP64 \[[16](https://pvs-studio.ru/ru/blog/posts/a0009/)\]\.
* Усовершенствуйте систему юнит\-тестирования, включив в набор тестов обработку больших массивов данных\. Более подробно с необходимостью тестирования на большом объеме данных можно познакомиться в статье \[[9](https://pvs-studio.ru/ru/blog/posts/cpp/a0006/)\], а также узнать, как лучше организовать такое тестирование\.
* Провести тщательно ручное тестирование перенесенного кода на реальных больших задачах, использующих возможности 64\-битных систем\. Смена архитектуры слишком существенное изменение, чтобы полностью положиться на автоматизированные системы тестирования\.

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

1. John R\. Mashey, [The Long Road to 64 Bits\.](https://queue.acm.org/detail.cfm?id=1165766)
1. Wikipedia: [MIPS architecture\.](https://en.wikipedia.org/wiki/MIPS_instruction_set)
1. John R\. Mashey, [64 bit processors: history and rationale\.](https://yarchive.net/comp/64bit.html)
1. John R\. Mashey, [The 64\-bit integer type "long long": arguments and history\.](https://yarchive.net/comp/longlong.html)
1. [64\-bit and Data Size Neutrality\.](http://www.unix.org/whitepapers/64bit.html) 
1. [64\-Bit Programming Models: Why LP64?](http://www.unix.org/version2/whatsnew/lp64_wp.html)
1. Transitioning C and C\+\+ programs to the 64\-bit data model\.
1. Andrey Karpov, Evgeniy Ryzhkov\. [20 issues of porting C\+\+ code on the 64\-bit platform\.](https://pvs-studio.ru/ru/blog/posts/cpp/a0004/)
1. Andrey Karpov\. Evgeniy Ryzhkov\. [Problems of testing 64\-bit applications\.](https://pvs-studio.ru/ru/blog/posts/cpp/a0006/) 
1. The Old New Thing: [Why did the Win64 team choose the LLP64 model?](https://devblogs.microsoft.com/oldnewthing/2005/01/31)
1. Brad Martin, Anita Rettinger, and Jasmit Singh\. [Multiplatform Porting to 64 Bits\.](https://www.drdobbs.com/parallel/multiplatform-porting-to-64-bits/184406427)
1. [Migrating 32\-bit Managed Code to 64\-bit\.](https://msdn.microsoft.com/en-us/library/ms973190.aspx)
1. Microsoft Game Technology Group\. [64\-bit programming for Game Developers\.](https://msdn.microsoft.com/en-us/library/ee418798.aspx)
1. Sergei Sokolov\. [Bulletproofing C\+\+ Code\.](https://www.drdobbs.com/cpp/bulletproofing-c-code/196802351)
1. Walter W\. Schilling, Jr\. and Mansoor Alam\. [Integrate Static Analysis Into a Software Development Process\.](https://www.drdobbs.com/architecture-and-design/integrate-static-analysis-into-a-softwar/193500967?cid=rssfeed_ddj_debugging)
1. Evgeniy Ryzhkov\. [Viva64: what is it and for whom is it meant?](https://pvs-studio.ru/ru/blog/posts/a0009/)