Урок 6. Ошибки в 64-битном коде
Исправление всех ошибок компиляции и предупреждений не будет означать работоспособность 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.