Программисты видят в стандарте C++0x возможность использовать лямбда-функции и прочие мало понятные для меня сущности :). Я увидел в нем удобные средства, позволяющие исключить многие 64-битные ошибки.
Как стандарт C++0x поможет в борьбе с 64-битными ошибками
1. Как стандарт C++0x поможет в борьбе
с 64-битными ошибками
Автор: Андрей Карпов
Дата: 28.02.2010
Программисты видят в стандарте C++0x возможность использовать лямбда-функции и прочие
мало понятные для меня сущности :). Я увидел в нем удобные средства, позволяющие исключить
многие 64-битные ошибки.
Рассмотрим функцию, которая возвращает true, если хотя бы в одной из строк встречается
последовательность "ABC".
typedef vector<string> ArrayOfStrings;
bool Find_Incorrect(const ArrayOfStrings &arrStr)
{
ArrayOfStrings::const_iterator it;
for (it = arrStr.begin(); it != arrStr.end(); ++it)
{
unsigned n = it->find("ABC");
if (n != string::npos)
return true;
}
return false;
};
Эта функция корректно ведет себя при компиляции Win32 версии и дает сбой при сборке в
режиме Win64. Рассмотрим пример использования функции:
#ifdef IS_64
const char WinXX[] = "Win64";
#else
const char WinXX[] = "Win32";
#endif
int _tmain(int argc, _TCHAR* argv[])
{
2. ArrayOfStrings array;
array.push_back(string("123456"));
array.push_back(string("QWERTY"));
if (Find_Incorrect(array))
printf("Find_Incorrect (%s): ERROR!n", WinXX);
else
printf("Find_Incorrect (%s): OK!n", WinXX);
return 0;
}
Find_Incorrect (Win32): OK!
Find_Incorrect (Win64): ERROR!
Ошибка заключается в использовании типа unsigned для переменной "n", хотя функция find()
возвращает значение типа string::size_type. В 32-битной программе тип string::size_type и unsigned
совпадают, и мы получаем верный результат.
В 64-битной программе string::size_type и unsigned перестают совпадать. Так как подстрока не
находится, функция find() возвращает значение string::npos, которое равно
0xFFFFFFFFFFFFFFFFui64. Это значение урезается до величины 0xFFFFFFFFu и помещается в 32-
битную переменную. В результате условие 0xFFFFFFFFu == 0xFFFFFFFFFFFFFFFFui64 всегда false и
мы получаем сообщение "Find_Incorrect (Win64): ERROR!".
Исправим код, используя тип string::size_type.
bool Find_Correct(const ArrayOfStrings &arrStr)
{
ArrayOfStrings::const_iterator it;
for (it = arrStr.begin(); it != arrStr.end(); ++it)
{
string::size_type n = it->find("ABC");
if (n != string::npos)
return true;
}
return false;
};
3. Теперь код работает корректно, хотя постоянно писать тип string::size_type длинно и не красиво.
Можно переопределить его через typedef, но все равно это выглядит как-то сложно. Используя
C++0x, мы можем сделать код гораздо изящней надежней.
Для этого мы воспользуемся ключевым словом auto. Ранее auto означало, что переменная
создается на стеке, и подразумевалось неявно в случае, если вы не указали что-либо другое,
например register. Теперь тип переменной, объявленной как auto, определяется компилятором
самостоятельно на основе того, чем эта переменная инициализируется.
Следует заметить, что auto-переменная не сможет хранить значения разных типов в течение
одного запуска программы. Си++ остается статически типизированным языком, и указание auto
лишь говорит компилятору самостоятельно позаботиться об определении типа: после
инициализации сменить тип переменной будет уже нельзя.
Воспользуемся ключевым словом auto в нашем коде. Проект был создан в Visual Studio 2005, а
поддержка C++0x реализована только начиная с версии Visual Studio 2010. Поэтому для
компиляции я воспользовался компилятором Intel C++, входящим в состав Intel Parallel Studio 11.1
и поддерживающим стандарт C++0x. Поддержка C++0x включается в разделе Language и носит
название "Enable C++0x Support". Как видно из рисунка 1, эта опция является Intel Specific.
Рисунок 1 - Поддержка стандарта C++0x
Модифицированный код выглядит следующим образом:
bool Find_Cpp0X(const ArrayOfStrings &arrStr)
{
for (auto it = arrStr.begin(); it != arrStr.end(); ++it)
4. {
auto n = it->find("ABC");
if (n != string::npos)
return true;
}
return false;
};
Обратите внимание, как теперь объявлена переменная "n". Неправда ли, элегантно и устраняет от
ряд ошибок, в том числе и 64-битных. Переменная "n" будет иметь ровно тот тип, который
возвращает функция find(), то-есть string::size_type. Также обратите внимание, что исчезла строчка
объявления итератора:
ArrayOfStrings::const_iterator it;
Объявлять переменную it внутри цикла было некрасиво (длинно). И поэтому объявление было
вынесено отдельно. Теперь проблемы длины нет:
for (auto it = arrStr.begin(); ......)
Рассмотрим еще одно ключевое слово - decltype. Оно позволяет задать тип на основании типа
другой переменной. Если бы в нашем коде, мы должны были объявить все переменные заранее,
то можно было бы написать так:
bool Find_Cpp0X_2(const ArrayOfStrings &arrStr)
{
decltype(arrStr.begin()) it;
decltype(it->find("")) n;
for (it = arrStr.begin(); it != arrStr.end(); ++it)
{
n = it->find("ABC");
if (n != string::npos)
return true;
}
return false;
};
Конечно, в данном случае это бессмысленно, но может быть весьма удобно во многих ситуациях.
5. К сожалению (или к счастью для нас :-), хотя новый стандарт облегчает написание безопасного 64-
битного кода, он не помогает в устранении уже существующих дефектов. Для того чтобы
использовать memsize-тип или auto для устранений ошибки, эту ошибку нужно в начале
обнаружить. Следовательно, актуальность использования инструмента Viva64 с приходом
стандарта C++0x не изменится.
P.S.
Проект с кодом можно скачать здесь.