Как стандарт C++0x поможет в борьбе с 64-битными ошибками

Андрей Карпов
Статей: 372



Программисты видят в стандарте 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[])
{
  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;
};

Теперь код работает корректно, хотя постоянно писать тип 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

Рисунок 1 - Поддержка стандарта C++0x

Модифицированный код выглядит следующим образом:

bool Find_Cpp0X(const ArrayOfStrings &arrStr)
{
  for (auto it = arrStr.begin(); it != arrStr.end(); ++it)
  {
    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;
};

Конечно, в данном случае это бессмысленно, но может быть весьма удобно во многих ситуациях.

К сожалению (или к счастью для нас :-), хотя новый стандарт облегчает написание безопасного 64-битного кода, он не помогает в устранении уже существующих дефектов. Для того чтобы использовать memsize-тип или auto для устранений ошибки, эту ошибку нужно в начале обнаружить. Следовательно, актуальность использования инструмента Viva64 с приходом стандарта C++0x не изменится.

P.S.

Проект с кодом можно скачать здесь.



Найдите ошибки в своем C, C++, C# и Java коде

Предлагаем попробовать проверить код вашего проекта с помощью анализатора кода PVS-Studio. Одна найденная в нём ошибка скажет вам о пользе методологии статического анализа кода больше, чем десяток статей.

goto PVS-Studio;

Андрей Карпов
Статей: 372


Найденные ошибки

Проверено проектов
346
Собрано ошибок
13 188

А ты совершаешь ошибки в коде?

Проверь с помощью
PVS-Studio

Статический анализ
кода для C, C++, C#
и Java

goto PVS-Studio;