Семь шагов по переносу программы в 64-битную систему

  1. Вебинар: По запросу, по требованию Настольный компьютер как услуга, разработанный для любого облака?...
  2. Вступление
  3. 1. Первый шаг. 64-битный режим может быть разным. Давайте разберемся
  4. 2. Второй шаг. Узнайте, нужна ли вам 64-битная версия вашего продукта
  5. 2.1. Продолжительность жизненного цикла приложений
  6. 2.2. Ресурсоемкость приложения
  7. 2,3. Разработка библиотек
  8. 2,4. Зависимость вашего продукта от сторонних библиотек
  9. 2.5. Использование 16-битных приложений
  10. 2.6. Код ассемблера
  11. 3. Третий шаг. Инструментарий
  12. 3.1. 64-битный компилятор
  13. 3.2. 64-битные компьютеры под управлением 64-битной операционной системы
  14. 3.3. 64-битные версии всех используемых библиотек
  15. 3.4. Отсутствие встроенного кода на ассемблере
  16. 3.5. Обновление методологии тестирования
  17. 3,6. Новые данные для тестирования
  18. 3,7. 64-битные системы безопасности
  19. 3,8. монтажник
  20. 4. Четвертый шаг. Настройка проекта в Visual Studio 2005/2008
  21. 5. Пятый шаг. Составление заявки
  22. 6. Диагностика скрытых ошибок
  23. 6.1. Явное приведение типов
  24. 6.2. Неявное преобразование типов
  25. 6.3. Биты и смены
  26. 6.4. Магические числа
  27. 6,5. Ошибки, связанные с использованием 32-битных переменных в качестве индексов
  28. 6.6. Ошибки, связанные с изменением типов используемых функций
  29. 6.7. Диагностика скрытых ошибок
  30. 7. Седьмой шаг. Обновление процесса тестирования

Вебинар:
По запросу, по требованию

Настольный компьютер как услуга, разработанный для любого облака? Рамка Нутаникс


Андрей Карпов
ООО "Системы программной верификации"
Апрель 2009

Аннотация

В статье описываются основные шаги, которые необходимо выполнить, чтобы правильно портировать 32-разрядные приложения Windows в 64-разрядных системах Windows. Хотя эта статья предназначена для разработчиков, использующих C / C ++ в среде Visual Studio 2005/2008, она также будет полезна для других разработчиков, которые планируют портировать свои приложения на 64-разрядные системы.

Вступление

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

1. Первый шаг. 64-битный режим может быть разным. Давайте разберемся

В рамках компьютерной архитектуры под термином " 64-битный Под "64-разрядными целыми числами и другими типами данных 64-разрядного размера" понимаются 64-разрядные системы. 64-разрядные микропроцессорные архитектуры (например, EM64T, IA-64) или 64-разрядные операционные системы (например, Windows). XP Professional x64 Edition) можно понять [ 1 ].

AMD64 (или x86-64, Intel 64, EM64T, x64) - это 64-разрядная микропроцессорная архитектура и соответствующий набор инструкций, разработанный компанией AMD [ 2 ]. Этот набор инструкций был лицензирован компанией Intel под названием EM64T (Intel64). Архитектура AMD64 является расширением архитектуры x86 с полной обратной совместимостью. Архитектура получила широкое распространение как основа персональных компьютеров и рабочих станций.

IA-64 - это 64-битная микропроцессорная архитектура, разработанная компаниями Intel и Hewlett Packard [ 3 ]. Он реализован в микропроцессорах Itanium и Itanium 2 [ 4 ]. Архитектура используется в основном в многопроцессорных серверах и кластерных системах.

AMD64 и IA-64 - это две разные 64-битные архитектуры, несовместимые друг с другом. Вот почему разработчики должны решить, нужна ли им поддержка обеих архитектур или только одна из них. В большинстве случаев, если вы не разрабатываете специализированное программное обеспечение для кластерных систем или не внедряете собственные высокопроизводительные СУБД, вам, скорее всего, придется реализовать поддержку только архитектуры AMD64, которая гораздо более популярна, чем IA-64. Особенно это касается программного обеспечения для рынка ПК, которое почти на 100% занято архитектурой AMD64.

Далее в статье речь пойдет только об архитектуре AMD64 (EM64T, x64), поскольку в настоящее время она наиболее актуальна для разработчиков прикладного программного обеспечения.

Говоря о разных архитектурах, следует упомянуть понятие " Модель данных «. Под моделью данных мы понимаем корреляции между размерами типов, принятыми в рамках среды разработки. Для одной операционной системы может быть несколько инструментов разработки, придерживающихся разных типов данных. Но обычно доминирует только одна модель, которая соответствует аппаратному и программному обеспечению. среды наиболее. Такой пример - 64-битная Windows, чья исходная модель данных LLP64 , Но в целях совместимости 64-битная Windows поддерживает выполнение 32-битных программ, которые работают в ILP32LL режим модели данных. Таблица 1 дает информацию об основных моделях данных.


Таблица 1. Модели данных.

Используемая модель данных влияет на процесс разработки 64-битных приложений, так как вам необходимо учитывать размеры данных, используемых в коде программ [ 5 ].

2. Второй шаг. Узнайте, нужна ли вам 64-битная версия вашего продукта

Вы должны начать осваивать 64-битные системы с вопроса: «Мне действительно нужно перестраивать свой проект для 64-битной системы?» Вы даете ответ на этот вопрос, но только после того, как продумали его, не спеша. С одной стороны, вы можете отставать от своих конкурентов, если не предлагаете 64-битные решения. С другой стороны, вы можете просто тратить свое время на разработку 64-битного приложения, которое не даст никаких конкурентных преимуществ.

Давайте перечислим основные факторы, которые помогут вам определиться.

2.1. Продолжительность жизненного цикла приложений

Не следует создавать 64-битную версию приложения с коротким жизненным циклом. Благодаря WOW64 старые 32-битные приложения подсистемы довольно хорошо работают в 64-битных системах Windows, и поэтому нет смысла делать 64-битные программы, так как они не будут поддерживаться через 2 года [ 6 ]. Более того, практика показывает, что перенос на 64-битные версии Windows был отложен, и, возможно, большинство ваших пользователей будут использовать только 32-битную версию вашего программного решения в ближайшей перспективе.

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

2.2. Ресурсоемкость приложения

Перекомпиляция программы для 64-битной системы позволит использовать большие объемы основной памяти, а также ускорит ее работу на 5-15%. Увеличение на 5-10% будет достигнуто за счет использования архитектурных возможностей 64-битного процессора, например, большего количества регистров. Увеличение скорости покоя на 1-5% объясняется отсутствием уровня WOW64, который переводит вызовы API между 32-разрядными приложениями и 64-разрядной операционной системой.

Если ваша программа не работает с большими размерами данных (более 2 ГБ) и скорость ее работы не является критичной, в ближайшем будущем портирование на 64-битную систему не столь актуально.

Кстати, даже простые 32-битные приложения могут получить преимущество от запуска в 64-битной среде. Возможно, вы знаете, что программа, созданная с ключом / LARGEADDRESSAWARE: YES, может выделить до 3 ГБ памяти, если 32-разрядная версия Windows запускается с ключом / 3gb. Эта очень 32-битная программа, запущенная в 64-битной системе, может выделить почти 4 ГБ памяти (на практике около 3,5 ГБ).

2,3. Разработка библиотек

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

Дополнительным преимуществом выпуска 64-битной версии библиотеки является то, что вы можете продавать ее как отдельный продукт. Таким образом, ваши клиенты, желающие создавать как 32-битные, так и 64-битные приложения, должны будут купить 2 разные лицензии. Например, эта политика используется Spatial Corporation при продаже библиотеки Spatial ACIS.

2,4. Зависимость вашего продукта от сторонних библиотек

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

2.5. Использование 16-битных приложений

Если в ваших решениях все еще используются 16-битные блоки, самое время избавиться от них. 16-разрядные приложения в 64-разрядных версиях Windows не поддерживаются.

Здесь следует объяснить одну вещь, касающуюся использования 16-битных установщиков. Они все еще используются для установки некоторых 32-битных приложений. Существует специальный механизм, который заменяет некоторые из самых популярных 16-битных инсталляторов их более новыми версиями. Это может вызвать ложное мнение о том, что 16-разрядные программы все еще работают в 64-разрядной среде. Помните: это не так.

2.6. Код ассемблера

Не забывайте, что использование большого размера кода на ассемблере может значительно увеличить стоимость создания 64-битной версии приложения.

Обдумав все перечисленные факторы и взвесив все за и против, решите, нужно ли переносить проект на 64-разрядные системы. Если да, мы идем дальше.

3. Третий шаг. Инструментарий

Если вы решили разработать 64-битную версию своего продукта и готовы потратить на него время, этого все равно недостаточно, чтобы гарантировать успех. Дело в том, что вы должны обладать всем необходимым инструментарием, и здесь вы можете столкнуться с некоторыми трудностями.

Отсутствие 64-битного компилятора может быть самой простой, но самой непреодолимой проблемой. Статья написана в 2009 году, но до сих пор нет 64-битного компилятора C ++ Builder от Codegear [ 7 ]. Его выпуск ожидается к концу этого года. Невозможно избежать этой проблемы, если только переписать весь проект с помощью, например, Visual Studio. Но если все понятно об отсутствии 64-битного компилятора, другие подобные проблемы могут оказаться менее прозрачными и возникать только на этапе переноса проекта на новую архитектуру. Вот почему мы хотели бы посоветовать вам заранее выяснить, есть ли все необходимые компоненты, которые вам понадобятся для реализации 64-битной версии вашего продукта. Вы можете столкнуться с неприятными сюрпризами.

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

3.1. 64-битный компилятор

Вряд ли можно сказать о важности наличия 64-битного компилятора. Это просто должно быть.

Если вы планируете разрабатывать 64-разрядные приложения, используя самую последнюю (на момент написания статьи) версию Visual Studio 2008, следующая таблица 2 поможет вам понять, какой из выпусков Visual Studio вам нужен.


Таблица 2. Возможности разных выпусков Visual Studio 2008.

3.2. 64-битные компьютеры под управлением 64-битной операционной системы

Конечно, вы можете использовать виртуальные машины для запуска 64-битных приложений на 32-битных компьютерах, но это слишком неудобно и не обеспечит необходимый уровень тестов. Желательно, чтобы на машинах было не менее 4-8 ГБ оперативной памяти.

3.3. 64-битные версии всех используемых библиотек

Если библиотеки представлены в исходных кодах, должна быть 64-битная конфигурация проекта. Самостоятельное обновление библиотеки для 64-битной системы может быть неблагодарным и трудным делом, а результат может быть ненадежным и содержать ошибки. Кроме того, вы можете нарушать лицензионные соглашения этими действиями. Если вы используете библиотеки в виде двоичных модулей, вы также должны выяснить, существуют ли 64-разрядные модули. Вы не можете использовать 32-битную DLL внутри 64-битного приложения. Вы можете создать специальную связь через COM, но это будет отдельной большой и сложной задачей [ 8 ]. Также имейте в виду, что вам может потребоваться потратить дополнительные деньги на покупку 64-битной версии библиотеки.

3.4. Отсутствие встроенного кода на ассемблере

Visual C ++ не поддерживает 64-битный встроенный ассемблер. Вы должны либо использовать внешний 64-битный ассемблер (например, MASM), либо обладать реализацией с той же функциональностью в C / C ++ [ 9 ].

3.5. Обновление методологии тестирования

Это означает значительную переработку методологии тестирования, обновление юнит-тестов и использование новых инструментов. Мы поговорим об этом более подробно далее, но не забудьте учесть это на этапе оценки затрат времени на перенос приложения на новую систему [ 10 ].

3,6. Новые данные для тестирования

Если вы разрабатываете ресурсоемкие приложения, использующие большой объем оперативной памяти, вам необходимо обеспечить пополнение базы входных данных тестирования. При нагрузочном тестировании 64-битных приложений желательно превышать пределы 4 ГБ используемой памяти. Многие ошибки могут возникнуть только в этих условиях.

3,7. 64-битные системы безопасности

Используемая система безопасности должна обеспечивать полную поддержку 64-битных систем. Например, компания Aladdin довольно быстро выпустила 64-битные драйверы для поддержки аппаратных ключей Hasp. Но уже давно не было системы автоматической защиты 64-битных бинарных файлов (программа Hasp Envelop). Таким образом, механизм безопасности должен был быть реализован вручную внутри программного кода, и это было еще одной сложной задачей, требующей профессионализма и времени. Не забывайте о таких вещах, как безопасность, обновление системы и т. Д.

3,8. монтажник

Вам нужен новый установщик, способный полностью установить 64-битные приложения. Мы хотели бы предупредить вас об одной очень типичной ошибке. Это создание 64-битных инсталляторов для установки 32/64-битных программных продуктов. При подготовке 64-битной версии приложения разработчики часто хотят сделать в ней «64-битный режим» абсолютным и создать 64-битный установщик, забыв, что те, кто использует 32-битную операционную систему, просто не смогут запустить такой установочный пакет. Обратите внимание, что это не 32-битное приложение, включенное в дистрибутив вместе с 64-битным, а сам установщик. Если дистрибутив является 64-битным приложением, конечно, он не будет работать в 32-битной операционной системе. Самое неприятное, что пользователь не сможет угадать, почему это происходит. Он просто увидит установочный пакет, который не может быть запущен.

4. Четвертый шаг. Настройка проекта в Visual Studio 2005/2008

Создание 64-битной конфигурации проекта в Visual Studio 2005/2008 выглядит довольно просто. Трудности начнутся на этапе создания новой конфигурации и поиска ошибок в ней. Для создания самой 64-битной конфигурации вам просто нужно выполнить следующие 4 шага:

Запустите менеджер конфигурации, как показано на рисунке 1:


Рисунок 1. Запуск менеджера конфигурации.

В диспетчере конфигурации выберите поддержку новой платформы (рисунок 2):

Рисунок 2
Рисунок 2. Создание новой конфигурации.

Выберите 64-битную платформу (x64) и в качестве основы - настройки из 32-битной версии (рисунок 3). Те настройки, которые влияют на режим строительства, будут исправлены Visual Studio автоматически.

Рисунок 3
Рисунок 3. Выберите x64 в качестве платформы и используйте конфигурацию Win32 в качестве основы.

Добавление новой конфигурации завершено, и теперь вы можете выбрать 64-разрядную версию конфигурации и начать компиляцию 64-разрядного приложения. Выбор 64-битной конфигурации для сборки показан на рисунке 4.

Рисунок 4
Рисунок 4. Теперь доступны как 32-битные, так и 64-битные конфигурации.

Если вам повезет, вам не нужно будет дополнительно устанавливать 64-битный проект. Но это сильно зависит от проекта, его сложности и количества используемых библиотек. Единственное, что вы должны изменить сразу, это размер стека. В случае, если размер стека в вашем проекте установлен по умолчанию, то есть 1 МБ, вы должны определить его как 2 МБ для 64-битной версии. В этом нет необходимости, но лучше заранее застраховать себя. Если вы используете размер, отличный от размера по умолчанию, есть смысл увеличить его вдвое для 64-битной версии. Для этого найдите и измените параметры Stack Reserve Size и Stack Commit Size в настройках проекта.

5. Пятый шаг. Составление заявки

Здесь мы должны рассказать вам о типичных проблемах, возникающих на этапе компиляции 64-битной конфигурации, обсудить, какие проблемы возникают в сторонних библиотеках, сказать, что в коде, относящемся к функциям WinAPI, компилятор не разрешит размещение указателя в тип LONG, и вам придется обновить код и использовать тип LONG_PTG. И еще много чего можно сказать. К сожалению, проблем так много, а ошибок так много, что мы не можем описать их все в одной статье и даже книге. Вам нужно будет просмотреть все ошибки, которые покажет вам компилятор, и все новые предупреждения, которых раньше не было ни у вас, ни в каждом конкретном случае, чтобы выяснить, как обновить код.

Следующий список ссылок на ресурсы, посвященные разработке 64-битных приложений, может частично вам помочь: http://www.viva64.com/links/64-bit-development/ , Список постоянно пополняется, и автор будет рад, если читатели отправят ему ссылки на ресурсы, которые, по их мнению, заслуживают внимания.

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

Тип Размер шрифта на платформе x32 / x64 Примечание int 32/32 Базовый тип. На 64-битных системах остается 32-битная. длинный 32/32 Базовый тип. В 64-битных системах Windows остается 32-битным. Имейте в виду, что в 64-битных системах Linux этот тип был расширен до 64-битных. Не забывайте об этом, если вы разрабатываете код, который должен быть скомпилирован для систем Windows и Linux. size_t 32/64 Основной тип без знака. Размер типа выбирается таким образом, чтобы вы могли записать в него максимальный размер теоретически возможного массива. Вы можете безопасно поместить указатель в тип size_t (за исключением указателей на функции класса, но это особый случай). ptrdiff_t 32/64 Аналогично типу size_t, но это тип со знаком. Результат выражения, в котором один указатель вычитается из другого (ptr1-ptr2), будет иметь тип ptrdiff_t. Указатель 32/64 Размер указателя напрямую зависит от размера платформы. Будьте осторожны при преобразовании указателей в другие типы. __int64 64/64 Подписанный 64-битный тип. DWORD 32/32 32-битный тип без знака. В WinDef.h определяется как: typedef unsigned long DWORD; DWORDLONG 64/64 64-битный тип без знака. В WinNT.h определяется как: typedef ULONGLONG DWORDLONG; DWORD_PTR 32/64 Беззнаковый тип, в котором можно разместить указатель. В BaseTsd.h определяется как: typedef ULONG_PTR DWORD_PTR; DWORD32 32/32 32-разрядный тип без знака. В BaseTsd.h определяется как: typedef unsigned int DWORD32; DWORD64 64/64 64-битный тип без знака. В BaseTsd.h определяется как: typedef unsigned __int64 DWORD64; HALF_PTR 16/32 Половина указателя. В Basetsd.h определяется как: #ifdef _WIN64 typedef int HALF_PTR; #else typedef short HALF_PTR; #endif INT_PTR 32/64 Подписанный тип, в котором можно разместить указатель. В BaseTsd.h определяется следующим образом: #if определенные (_WIN64) typedef __int64 INT_PTR; #else typedef int INT_PTR; #endif LONG 32/32 Тип со знаком, который остался 32-разрядным. Вот почему во многих случаях теперь следует использовать LONG_PTR. В WinNT.h определяется как: typedef long LONG; LONG_PTR 32/64 Тип со знаком, в котором можно разместить указатель. В BaseTsd.h определен как: #if определенные (_WIN64) typedef __int64 LONG_PTR; #else typedef long LONG_PTR; #endif LPARAM 32/64 Параметр для отправки сообщений. В WinNT.h определяется как: typedef LONG_PTR LPARAM; SIZE_T 32/64 Аналог типа size_t. В BaseTsd.h определяется как: typedef ULONG_PTR SIZE_T; SSIZE_T 32/64 Аналог типа ptrdiff_t. В BaseTsd.h определяется как: typedef LONG_PTR SSIZE_T; ULONG_PTR 32/64 Тип без знака, в котором можно разместить указатель. В BaseTsd.h определяется как: #if определенные (_WIN64) typedef без знака __int64 ULONG_PTR; #else typedef unsigned long ULONG_PTR; #endif WORD 16/16 Беззнаковый 16-битный тип. В WinDef.h определяется как: typedef unsigned short WORD; WPARAM 32/64 Параметр для отправки сообщений. В WinDef.h определяется как: typedef UINT_PTR WPARAM;

Таблица 3. Типы, которые следует учитывать при переносе 32-битных программ в 64-битных системах Windows.

6. Диагностика скрытых ошибок

Если вы считаете, что после исправления всех ошибок компиляции вы получите долгожданное 64-битное приложение, мы вас разочаруем. Самое сложное только впереди. На этапе компиляции вы исправите наиболее явные ошибки, которые удалось обнаружить компилятору и которые в основном связаны с невозможностью неявного преобразования типов. Но это только малая часть проблемы. Большинство ошибок скрыты. С точки зрения абстрактного языка C ++ эти ошибки выглядят безопасными и маскируются явными преобразованиями типов. Количество таких ошибок намного больше, чем количество ошибок, обнаруженных на этапе компиляции.

Вы не должны надеяться на / Wp64 ключ. Этот ключ часто представляется как прекрасное средство поиска 64-битных ошибок. На самом деле ключ Wp64 позволяет просто получить несколько предупреждающих сообщений о некорректности некоторых участков кода в 64-битном режиме при компиляции 32-битного кода. Во время компиляции 64-битного кода эти предупреждения будут показаны в любом случае. И именно поэтому ключ / Wp64 игнорируется при компиляции 64-битного приложения. И, конечно, этот ключ не поможет в поиске скрытых ошибок [ 11 ].

Давайте рассмотрим несколько примеров скрытых ошибок.

6.1. Явное приведение типов

Самый простой, но не самый простой для обнаружения класс ошибок относится к явные преобразования типов когда значимые биты вырезаны. Популярным примером является преобразование указателей в 32-битные типы при передаче их в функции, такие как SendMessage:

MyObj * pObj = ... :: SendMessage (hwnd, msg, (WORD) x, (DWORD) pObj);

Здесь явное преобразование типов используется для превращения указателя в числовой тип. Для 32-битной архитектуры этот пример верен, так как последний параметр функции SendMessage имеет тип LPARAM, который совпадает с DWORD в 32-битной архитектуре. Для 64-битной архитектуры DWORD неверен и должен быть заменен на LPARAM. Тип LPARAM имеет размеры 32 или 64 бита в зависимости от архитектуры.

Это простой случай, но преобразование типов часто выглядит более сложным, и его невозможно обнаружить с помощью предупреждений компилятора или поиска по тексту программы. Явные преобразования типов подавляют диагностику компилятора, поскольку они предназначены именно для этой цели - сообщать компилятору, что преобразование типов является правильным, и за безопасность кода отвечает программист. Явный поиск также не поможет. Типы могут иметь нестандартные имена (определяемые программистом через typedef), и число методов для явного преобразования типов также велико. Для безопасной диагностики таких ошибок вы должны использовать только специальный инструментарий, такой как Вива64 или же PC-Lint анализаторы.

6.2. Неявное преобразование типов

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

size_t __fread (void * __restrict buf, size_t size, size_t count, FILE * __restrict fp); size_t fread (void * __restrict buf, size_t size, size_t count, FILE * __restrict fp) {int ret; FLOCKFILE (FP); ret = __fread (buf, size, count, fp); FUNLOCKFILE (FP); возврат (в отставку); }

Функция __fread возвращает тип size_t, но тип int используется для хранения количества прочитанных байтов. В результате при больших размерах считываемых данных функция может возвращать ложное число байтов.

Вы можете сказать, что это неграмотный код для начинающих, что компилятор объявит об этом преобразовании типов и что этот код на самом деле легко найти и исправить. Это в теории. И на практике все может быть совсем иначе в случае крупных проектов. Этот пример взят из исходного кода FreeBSD. Ошибка была исправлена ​​только в декабре 2008 года! Обратите внимание, что первая (экспериментальная) 64-битная версия FreeBSD была выпущена в июне 2003 года.

Это исходный код до того, как он был исправлен:

http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libc/stdio/fread.c?rev=1.14

И это исправленный вариант (декабрь 2008):

http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libc/stdio/fread.c?rev=1.15

6.3. Биты и смены

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

ptrdiff_t SetBitN (значение ptrdiff_t, unsigned bitNum) {ptrdiff_t mask = 1 << bitNum; возвращаемое значение | маска; }

Этот код хорошо работает на 32-битной архитектуре и позволяет устанавливать биты с номерами от 0 до 31 в единицу. После переноса программы на 64-битную платформу вам нужно будет установить биты от 0 до 63. Но этот код никогда не установит биты 32-63. Обратите внимание, что «1» имеет тип int, и когда происходит сдвиг на 32 позиции, произойдет переполнение, как показано на рисунке 5. Получим ли мы 0 (рисунок 5-B) или 1 (рисунок 5-C), как результат, зависит от реализации компилятора.

Рисунок 5
Рисунок 5. A - Правильная установка 32-го бита в 32-битном коде;B, C - ошибка установки 32-го бита в 64-битной системе (два способа поведения)

Чтобы исправить код, нам нужно сделать константу «1» того же типа, что и переменная маски:

ptrdiff_t mask = ptrdiff_t (1) << bitNum;

Также обратите внимание, что неверный код приводит к еще одной ошибке. При установке 31 бита в 64-битной системе результатом функции будет значение 0xffffffff80000000 (см. Рисунок 6). Результатом выражения 1 << 31 является отрицательное число -2147483648. В 64-разрядной целочисленной переменной это число представлено как 0xffffffff80000000.

Рисунок 6
Рисунок 6. Ошибка установки 31-го бита в 64-битной системе

6.4. Магические числа

Магические константы, то есть числа, с помощью которых определяется размер того или иного типа, могут вызвать массу неприятностей. Правильное решение - использовать операторы sizeof () для этих целей, но в большой программе старый раздел кода все еще может быть скрыт, где, как полагают программисты, размер указателя составляет 4 байта, а в size_t он всегда равен 32 битам. Обычно такие ошибки выглядят следующим образом:

size_t ArraySize = N * 4; size_t * Array = (size_t *) malloc (ArraySize);

На рисунке 4 показаны основные цифры, с которыми вам следует работать осторожно при переходе на 64-битную платформу.

Таблица 4
Таблица 4. Основные магические значения, которые опасны при переносе приложений с 32-битной платформы на 64-битную.

6,5. Ошибки, связанные с использованием 32-битных переменных в качестве индексов

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

const size_t size = ...; char * array = ...; символ * конец = массив + размер; for (без знака i = 0; i! = размер; ++ i) {const int one = 1; end [-i - one] = 0; }

Первая ошибка заключается в том, что если размер обрабатываемых данных превышает 4 ГБ (0xFFFFFFFF), может возникнуть вечный цикл, поскольку переменная 'i' имеет тип «unsigned» и никогда не достигнет значения 0xFFFFFFFF. Я специально пишу, что это может произойти, но не обязательно. Это зависит от того, какой код будет компилироваться. Например, в режиме отладки будет присутствовать вечный цикл, а в коде выпуска не будет цикла, так как компилятор решит оптимизировать код, используя 64-битный регистр для счетчика, и цикл будет корректным. Все это добавляет много путаницы, и код, который работал вчера, может не работать сегодня.

Вторая ошибка связана с анализом массива от начала до конца, для которого используются значения отрицательных индексов. Этот код будет хорошо работать в 32-битном режиме, но при выполнении на 64-битном компьютере доступ за пределами массива будет происходить на первой итерации цикла, и произойдет сбой программы. Давайте изучим причину такого поведения.

Согласно правилам C ++ выражение в 32-битной системе будет рассчитываться следующим образом: (на первом шаге i = 0):

  • Выражение '-i' имеет тип без знака и имеет значение 0x00000000u.
  • Переменная 'one' будет расширена с типа 'int' до типа без знака и будет равна 0x00000001u. Примечание: тип int расширяется (согласно стандарту C ++) до типа «без знака», если он участвует в операции, где второй аргумент имеет тип без знака.
  • Выполняется операция вычитания, в которой участвуют два значения беззнакового типа, и результат операции равен 0x00000000u - 0x00000001u = 0xFFFFFFFFu. Обратите внимание, что результат будет иметь тип без знака.
  • В 32-битной системе доступ к массиву по индексу 0xFFFFFFFFu такой же, как при использовании индекса -1. То есть end [0xFFFFFFFFu] является аналогом end [-1]. В результате элементы массива будут обработаны правильно.

В 64-битной системе ситуация будет совсем другой относительно последнего пункта. Тип без знака будет расширен до типа со знаком ptfdiff_t, а индекс массива будет равен 0x00000000FFFFFFFFi64. В результате произойдет переполнение.

Для исправления кода вы должны использовать типы ptrdiff_t и size_t.

6.6. Ошибки, связанные с изменением типов используемых функций

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

Рисунок 7
Рисунок 7. Эффективный правильный код, созданный в Visual Studio 6.0.

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

Рисунок 8
Рисунок 8. Неверный, но эффективный 32-битный код.

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

Рисунок 9
Рисунок 9. Ошибка возникает в 64-битном коде

6.7. Диагностика скрытых ошибок

Примеров таких 64-битных ошибок много. Те, кто интересуется этой темой и хотели бы узнать больше об этих ошибках, читают статью 12].

Как видите, этап поиска скрытых ошибок является нетривиальной задачей, и, кроме того, многие из них будут возникать нерегулярно и только при больших входных данных. Статические анализаторы кода хороши для диагностики таких ошибок, поскольку они могут проверять весь код приложения независимо от входных данных и частоты выполнения его разделов в реальных условиях. Использование статического анализа имеет смысл как на этапе переноса приложения на 64-битные платформы, чтобы найти большинство ошибок в самом начале, так и в дальнейшей разработке 64-битных решений. Статический анализ предупредит и научит программиста лучше понимать особенности ошибок, связанных с 64-битной архитектурой, и писать более эффективный код. Автор статьи - разработчик одного из таких специализированных анализаторов кода под названием Viva64 [ 13 ]. Чтобы узнать больше об инструменте и загрузить демо-версию, посетите сайт ООО "Системы программной верификации" Компания.

Справедливости ради следует сказать, что Gimpel PC-Lint а также Parasoft C ++ Test Анализаторы кода имеют набор правил для диагностики 64-битных ошибок. Но, во-первых, это анализаторы общего назначения, а правила диагностики 64-битных ошибок неполные. Во-вторых, они предназначены в основном для LP64 Модель данных используется в семействе операционных систем Linux, и поэтому они не так полезны для программ Windows, где LLP64 модель данных используется [ 14 ].

7. Седьмой шаг. Обновление процесса тестирования

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

Если ваша 64-разрядная программа обрабатывает данные большего размера, чем 32-разрядная версия, вам необходимо тестировать данные, включающие в себя обработку данных размером более 4 ГБ. Это 64-битные ошибки. Такие тесты могут занимать гораздо больше времени. Обычно тесты написаны таким образом, что каждый тест может обрабатывать небольшое количество элементов и, таким образом, позволяет выполнять все внутренние юнит-тесты за несколько минут при автоматических тестах (например, с использованием AutomatedQA TestComplete ) может быть выполнено в течение нескольких часов. Почти абсолютно очевидно, что функция сортировки 100 элементов будет вести себя корректно при 100 000 элементов в 32-разрядной системе. Но та же функция может не работать в 64-битной системе при попытке обработать 5 миллиардов элементов. Скорость выполнения юнит-теста может упасть в миллион раз. Не забывайте о стоимости адаптации тестов при освоении 64-битных систем. Хорошим решением является разделение юнит-тестов на быстрые (работающие с небольшим объемом памяти) и медленные, обрабатывающие гигабайты и выполняемые, например, в ночное время. Автоматизированное тестирование ресурсоемких 64-битных программ может быть организовано на основе распределенных вычислений.

Есть еще одна неприятная вещь. Вам вряд ли удастся использовать такие инструменты, как BoundsChecker для поиска ошибок в ресурсоемких 64-битных программах, занимающих большой объем памяти. Причина заключается в значительном замедлении тестируемых программ, что делает этот подход очень неудобным. В режиме диагностики все ошибки, связанные с работой памяти, Параллельный Инспектор инструмент входит в Intel Parallel Studio замедлит выполнение приложения в среднем в 100 раз (рисунок 10). Весьма вероятно, что вам придется оставить алгоритм, тестируемый на ночь, чтобы увидеть результаты только на следующий день, в то время как обычно этот алгоритм работает всего 10 минут. И все же я уверен, что Parallel Inspector - один из самых полезных и удобных инструментов при работе в режиме поиска ошибок работы памяти. Вы просто должны быть готовы изменить практику диагностики ошибок и помнить об этом при планировании освоения 64-битных систем.

Рисунок 10
Рисунок 10. Окно настроек программы Parallel Inspector перед запуском приложения.

И последнее. Не забудьте добавить тесты, проверяющие совместимость форматов данных между 32-битной и 64-битной версиями. Совместимость данных часто нарушается во время миграции из-за записи в файлы таких типов, как size_t или long (в системах Linux).

Рекомендации

  1. Wikipedia. 64-битный. http://www.viva64.com/go.php?url=203
  2. Wikipedia. AMD64. http://www.viva64.com/go.php?url=220
  3. Сверре Ярп. Архитектура IA-64. Подробный учебник. http://www.viva64.com/go.php?url=222
  4. Wikipedia. Itanium. http://www.viva64.com/go.php?url=221
  5. Андрей Карпов. Забытые проблемы разработки 64-битных программ http://www.viva64.com/art-1-2-16511733.html
  6. Wikipedia. WOW64. http://www.viva64.com/go.php?url=207
  7. Ник Ходжес Будущее Delphi-компилятора. http://www.viva64.com/go.php?url=208
  8. Майк Беккер Доступ к 32-битным DLL из 64-битного кода. http://www.viva64.com/go.php?url=209
  9. Эрик Палмер. Как использовать все CPUID для платформ x64 под Microsoft Visual Studio .NET 2005. http://www.viva64.com/go.php?url=210
  10. Андрей Карпов, Евгений Рыжков. Обнаружение ловушек при переносе кода C и C ++ в 64-битную Windows. http://www.viva64.com/art-1-2-2140958669.html
  11. Андрей Карпов. 64 бита, / Wp64, Visual Studio 2008, Viva64 и все остальное ... http://www.viva64.com/art-1-2-621693540.html
  12. Андрей Карпов, Евгений Рыжков. 20 вопросов портирования кода C ++ на 64-битную платформу. http://www.viva64.com/art-1-2-599168895.html
  13. Евгений Рыжков. Viva64: что это и для кого? http://www.viva64.com/art-1-2-903037923.html
  14. Андрей Карпов. Сравнение диагностических возможностей анализаторов при проверке 64-битного кода. http://www.viva64.com/art-1-2-914146540.html
Вебинар: По запросу, по требованию Настольный компьютер как услуга, разработанный для любого облака?
C?
C?
Php?
Php?
Php?
Php?
Php?
Php?
Php?