Урок 6. Ошибки в 64-битном коде

23.01.2012

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

Ключ /Wp64

Ключ /Wp64 позволяет программисту найти некоторые проблемы, которые могут возникнуть при компиляции кода для 64-битных систем. Проверка заключается в том, что типы, которые отмечены в 32-битном коде ключевым словом __w64 интерпретируются при проверке как 64-битные типы.

Например, пусть мы имеем следующий код:

typedef int MyInt32;
#ifdef _WIN64
  typedef __int64 MySSizet;
#else
  typedef int MySSizet;
#endif
void foo() {
  MyInt32 value32 = 10;
  MySSizet size = 20;
  value32 = size;
}

Выражение "value32 = size;" на 64-битной системе приведет к урезанию значения, а, следовательно, к потенциальной ошибке. Мы хотим это диагностировать. Но при компиляции 32-битного приложения, все корректно и мы не получим предупреждения.

Для того чтобы подготовиться к 64-битным системам, нам следует добавить ключ /Wp64 и вставить ключевое слово __w64 при описании типа MySSizet в 32-битном варианте. В результате код станет выглядеть так:

typedef int MyInt32;
#ifdef _WIN64
  typedef __int64 MySSizet;
#else
  typedef int __w64 MySSizet; // Add __w64 keyword
#endif
void foo() {
  MyInt32 value32 = 10;
  MySSizet size = 20;
  value32 = size; // C4244 64-bit int assigned to 32-bit int
}

Теперь мы получим предупреждение C4244, которое поможет подготовиться к переносу кода на 64-битную платформу.

Обратите внимание, что для 64-битного режима компиляции ключ /Wp64 игнорируется, так как все типы уже имеют необходимый размер, и компилятор произведет необходимые проверки. То есть при компиляции 64-битной версии даже с выключенным ключом /Wp64 мы получим предупреждение C4244.

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

Кстати, в Visual Studio 2008 ключ /Wp64 считается устаревшим, поскольку уже давно пора компилировать 64-битные приложения, а не продолжать готовиться к этому.

64-битные ошибки

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

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

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

Примеры 64-битных ошибок

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

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

size_t pointersCount = 100;
int **arrayOfPointers = (int **)malloc(pointersCount * 4);

Следующий пример демонстрирует ошибку в механизме чтения данных. Данный код корректно работает в 32-битном режиме и не обнаруживается компилятором. Однако такой код некорректно прочитает данные сохраненные 32-битной версией программы.

size_t PixelCount;
fread(&PixelCount, sizeof(PixelCount), 1, inFile);

Комментарий искушенным программистам

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

Да, это именно так! Мы помним про это. Но мы не ставим целью рассматривать переносимость кода вообще. В этих уроках мы хотим решить конкретную частную задачу, а именно помочь разработчикам в освоении 64-битных платформ, которые получают все большее распространение.

Говоря о паттернах 64-битных ошибках, мы будем рассматривать примеры кода, который корректно функционирует на 32-битных системах, но может приводить к сбою при его переносе на 64-битную архитектуру.

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

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

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