SlideShare a Scribd company logo
1 of 6
Download to read offline
Урок 13. Паттерн 5. Адресная
арифметика
Мы специально выбрали номер "тринадцать" для этого урока, поскольку ошибки, связанные с
адресной арифметикой в 64-битных системах, являются наиболее коварными. Надеемся, число 13
заставит вас быть внимательнее.

Суть паттерна: чтобы избежать ошибок в 64-битном коде используйте для адресной арифметики
только memsize-типы.

Рассмотрим код:

unsigned short a16, b16, c16;

char *pointer;

...

pointer += a16 * b16 * c16;

Данный пример корректно работает с указателями, если значение выражения "a16 * b16 * c16" не
превышает INT_MAX (2147483647). Такой код мог всегда корректно работать на 32-битной
платформе. В рамках 32-битной архитектуры программе недоступен объем памяти для создания
массива подобного размеров. На 64-битной архитектуре это ограничение снято, и размер массива
легко может превысить INT_MAX элементов. Допустим, мы хотим сдвинуть значение указателя на
6.000.000.000 байт, и поэтому переменные a16, b16 и c16 имеют значения 3000, 2000 и 1000
соответственно. При вычислении выражения "a16 * b16 * c16" все переменные, согласно
правилам языка Си++, будут приведены к типу int, а уже затем будет произведено их умножение.
В ходе выполнения умножения произойдет переполнение. Некорректный результат выражения
будет расширен до типа ptrdiff_t, и произойдет некорректное вычисление указателя.

Следует старательно избегать возможных переполнений в арифметике с указателями. Для этого
лучше всего использовать memsize-типы или явное приведение типов в выражениях, где
присутствуют указатели. Используя явное приведение типов, мы можем переписать код
следующим образом:

short a16, b16, c16;

char *pointer;

...

pointer += static_cast<ptrdiff_t>(a16) *

              static_cast<ptrdiff_t>(b16) *

              static_cast<ptrdiff_t>(c16);

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

int A = -2;

unsigned B = 1;

int array[5] = { 1, 2, 3, 4, 5 };

int *ptr = array + 3;

ptr = ptr + (A + B); //Invalid pointer value on 64-bit platform

printf("%in", *ptr); //Access violation on 64-bit platform

Давайте проследим, как происходит вычисление выражения "ptr + (A + B)":

   •   Согласно правилам языка Си++ переменная A типа int приводится к типу unsigned.
   •   Происходит сложение A и B. В результате мы получаем значение 0xFFFFFFFF типа unsigned.
   •   Вычисляется выражение "ptr + 0xFFFFFFFFu".

Что из этого выйдет, будет зависеть от размера указателя на данной архитектуре. Если сложение
будет происходить в 32-битной программе, то данное выражение будет эквивалентно "ptr - 1", и
мы успешно распечатаем число "3". В 64-битной программе к указателю честным образом
прибавится значение 0xFFFFFFFFu, в результате чего указатель окажется далеко за пределами
массива. И при доступе к элементу по данному указателю нас ждут неприятности.

Для предотвращения подобной ситуации, как и в первом случае, рекомендуем использовать в
арифметике с указателями только memsize-типы. Два варианта исправления кода:

ptr = ptr + (ptrdiff_t(A) + ptrdiff_t(B));

ptrdiff_t A = -2;

size_t B = 1;

...

ptr = ptr + (A + B);

Вы можете возразить и предложить следующий вариант исправления:

int A = -2;

int B = 1;

...

ptr = ptr + (A + B);

Да, такой код будет работать, но он плох по ряду причин:

   •   Он будет приучать к неаккуратной работе с указателями. Через некоторое время вы
       можете забыть нюансы и по ошибке вновь сделать одну из переменных типа unsigned.
•   Использование не memsize-типов совместно с указателями потенциально опасно само по
        себе. Допустим, что в выражении с указателем участвует переменная Delta типа int, и это
        выражение совершенно корректно. Ошибка может крыться в вычислении самой
        переменной Delta, так как 32 бит может не хватить для необходимых вычислений, при
        работе с большими массивами данных. Использование memsize-типа для переменной
        Delta автоматически устраняет такую опасность.
    •   Код, использующий при работе с указателями типы size_t, ptrdiff_t и другие memsize-типы,
        приводит к генерации более оптимального двоичного кода, о чем будет рассказано
        подробнее в одном из следующих уроков.


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

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

const size_t size = ...;

char *array = ...;

char *end = array + size;

for (unsigned i = 0; i != size; ++i)

{

    const int one = 1;

    end[-i - one] = 0;

}

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

Вторая ошибка связана с проходом по массиву от конца к началу, для чего используются
отрицательные значения индексов. Приведенный код работоспособен в 32-битном режиме, но
при его запуске на 64-битной машине на первой же итерации цикла произойдет доступ за
границы массива, и программа аварийно завершится. Рассмотрим причину такого поведения.
Хотя то, что написано ниже, аналогично примеру с "ptr = ptr + (A + B);", это повторение делается
сознательно. Важно показать, что опасность скрывается даже в простых конструкциях и может
выглядеть по-разному.

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

     1. Выражение "-i" имеет тип unsigned и имеет значение 0x00000000u.
     2. Переменная 'one' будет расширена от типа 'int' до типа unsigned и будет равна
        0x00000001u. Примечание: Тип int расширяется (согласно стандарту языка Си++) до типа
        'unsigned', если он участвует в операции, где второй аргумент имеет тип unsigned.
     3. Происходит операция вычитания, в котором участвуют два значения типа unsigned и
        результат выполнения операции равен 0x00000000u - 0x00000001u = 0xFFFFFFFFu.
        Обратите внимание, что результат имеет беззнаковый тип.

На 32-битной системе обращение к массиву по индексу 0xFFFFFFFFu эквивалентно использованию
индекса -1. То есть end[0xFFFFFFFFu] является аналогом end[-1]. В результате происходит
корректная обработка элемента массива. В 64-битной системе в последнем пункте картина будет
иной. Произойдет расширение типа unsigned до знакового ptrdiff_t и индекс массива будет равен
0x00000000FFFFFFFFi64. В результате произойдет выход за рамки массива.

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

Чтобы окончательно убедить вас в необходимости использования только memsize-типов для
индексации и в выражениях адресной арифметики, приведем еще один пример.

class Region {

    float *array;

    int Width, Height, Depth;

    float Region::GetCell(int x, int y, int z) const;

    ...

};

float Region::GetCell(int x, int y, int z) const {

    return array[x + y * Width + z * Width * Height];

}

Данный код взят из реальной программы математического моделирования, в которой важным
ресурсом является объем оперативной памяти, и возможность на 64-битной архитектуре
использовать более 4 гигабайт памяти существенно увеличивает вычислительные возможности. В
программах данного класса для экономии памяти часто используют одномерные массивы,
осуществляя работу с ними как с трехмерными массивами. Для этого существуют функции,
аналогичные GetCell, обеспечивающие доступ к необходимым элементам. Но приведенный код
будет корректно работать только с массивами, содержащими менее INT_MAX элементов. Причина
- использование 32-битных типов int для вычисления индекса элемента.
Программисты часто допускают ошибку, пытаясь исправить код следующим образом:

float Region::GetCell(int x, int y, int z) const {

    return array[static_cast<ptrdiff_t>(x) + y * Width +

                   z * Width * Height];

}

Они знают, что по правилам языка Си++ выражение для вычисления индекса будет иметь тип
ptrdiff_t и надеются за счет этого избежать переполнения. Но переполнение может произойти
внутри подвыражения "y * Width" или "z * Width * Height", так как для их вычисления по-
прежнему используется тип int.

Если вы хотите исправить код, не изменяя типов переменных, участвующих в выражении, то вы
можете явно привести каждую переменную к memsize-типу:

float Region::GetCell(int x, int y, int z) const {

    return array[ptrdiff_t(x) +

                   ptrdiff_t(y) * ptrdiff_t(Width) +

                   ptrdiff_t(z) * ptrdiff_t(Width) *

                   ptrdiff_t(Height)];

}

Другое, более верное решение - изменить типы переменных на memsize-тип:

typedef ptrdiff_t TCoord;

class Region {

    float *array;

    TCoord Width, Height, Depth;

    float Region::GetCell(TCoord x, TCoord y, TCoord z) const;

    ...

};

float Region::GetCell(TCoord x, TCoord y, TCoord z) const {

    return array[x + y * Width + z * Width * Height];

}


Диагностика
Ошибки адресной арифметики хорошо диагностируются инструментом PVS-Studio. Анализатор
предупреждает о потенциально опасных выражениях с помощью диагностических сообщений
V102 и V108.
По возможности анализатор пытается понять, когда использование не memsize-типа в адресной
арифметике безопасно и не выдавать в этом месте предупреждения. В результате, поведение
анализатора иногда может показаться странным. В таких случаях мы просим не спешить и
постараться разобраться. Рассмотрим следующий код:

char Arr[] = { '0', '1', '2', '3', '4' };

char *p = Arr + 2;

cout << p[0u + 1] << endl;

cout << p[0u - 1] << endl; //V108

Данный код исправно работает в 32-битном режиме и печатает на экране числа 3 и 1. При
проверке этого кода мы получим предупреждение только на одну строку с выражением "p[0u -
1]". И это совершенно верно! Если вы скомпилируете и запустите данный пример в 64-битном
режиме, то увидите, как на экране будет распечатано значение 3, после чего произойдет
аварийное завершение программы.

Если вы уверены в корректности индексации, то вы можете изменить соответствующую настройку
анализатора на вкладке настроек Settings: General или использовать фильтры. Также можно
использовать явное приведение типов.

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

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

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

More Related Content

What's hot

Сравнение диагностических возможностей анализаторов при проверке 64-битного кода
Сравнение диагностических возможностей анализаторов при проверке 64-битного кодаСравнение диагностических возможностей анализаторов при проверке 64-битного кода
Сравнение диагностических возможностей анализаторов при проверке 64-битного кодаTatyanazaxarova
 
Урок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программУрок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программTatyanazaxarova
 
Правила статического анализа кода для диагностики потенциально опасных констр...
Правила статического анализа кода для диагностики потенциально опасных констр...Правила статического анализа кода для диагностики потенциально опасных констр...
Правила статического анализа кода для диагностики потенциально опасных констр...Sergey Vasilyev
 
20 ловушек переноса Си++ - кода на 64-битную платформу
20 ловушек переноса Си++ - кода на 64-битную платформу20 ловушек переноса Си++ - кода на 64-битную платформу
20 ловушек переноса Си++ - кода на 64-битную платформуTatyanazaxarova
 
автоматическая обработка информации
автоматическая обработка информацииавтоматическая обработка информации
автоматическая обработка информацииЕлена Ключева
 
курсовой проект
курсовой проекткурсовой проект
курсовой проектGulnaz Shakirova
 
машина поста
машина постамашина поста
машина постаchikisheva90
 
Урок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурУрок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурTatyanazaxarova
 
программирование на Maple. Лекция 2
программирование на Maple. Лекция 2программирование на Maple. Лекция 2
программирование на Maple. Лекция 2Andrei V, Zhuravlev
 
программирование на Maple. Лекция 1
программирование на Maple. Лекция 1программирование на Maple. Лекция 1
программирование на Maple. Лекция 1Andrei V, Zhuravlev
 
Лекция 9 Жадные алгоритмы
Лекция 9 Жадные алгоритмыЛекция 9 Жадные алгоритмы
Лекция 9 Жадные алгоритмыsimple_people
 
Большой брат помогает тебе
Большой брат помогает тебеБольшой брат помогает тебе
Большой брат помогает тебеTatyanazaxarova
 
Arithmetical Research
Arithmetical ResearchArithmetical Research
Arithmetical ResearchNick Papenko
 
Урок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеУрок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеTatyanazaxarova
 
Урок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхУрок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхTatyanazaxarova
 
для чего нужны десятичные
для чего нужны десятичныедля чего нужны десятичные
для чего нужны десятичныеco1858
 
тема 1.введение в pascal
тема 1.введение в pascalтема 1.введение в pascal
тема 1.введение в pascaldasha2012
 
лабораторная работа 3
лабораторная работа 3лабораторная работа 3
лабораторная работа 3Gulnaz Shakirova
 
Урок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиУрок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиTatyanazaxarova
 

What's hot (20)

Сравнение диагностических возможностей анализаторов при проверке 64-битного кода
Сравнение диагностических возможностей анализаторов при проверке 64-битного кодаСравнение диагностических возможностей анализаторов при проверке 64-битного кода
Сравнение диагностических возможностей анализаторов при проверке 64-битного кода
 
Урок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программУрок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программ
 
Правила статического анализа кода для диагностики потенциально опасных констр...
Правила статического анализа кода для диагностики потенциально опасных констр...Правила статического анализа кода для диагностики потенциально опасных констр...
Правила статического анализа кода для диагностики потенциально опасных констр...
 
20 ловушек переноса Си++ - кода на 64-битную платформу
20 ловушек переноса Си++ - кода на 64-битную платформу20 ловушек переноса Си++ - кода на 64-битную платформу
20 ловушек переноса Си++ - кода на 64-битную платформу
 
автоматическая обработка информации
автоматическая обработка информацииавтоматическая обработка информации
автоматическая обработка информации
 
курсовой проект
курсовой проекткурсовой проект
курсовой проект
 
машина поста
машина постамашина поста
машина поста
 
Урок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурУрок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структур
 
программирование на Maple. Лекция 2
программирование на Maple. Лекция 2программирование на Maple. Лекция 2
программирование на Maple. Лекция 2
 
программирование на Maple. Лекция 1
программирование на Maple. Лекция 1программирование на Maple. Лекция 1
программирование на Maple. Лекция 1
 
Лекция 9 Жадные алгоритмы
Лекция 9 Жадные алгоритмыЛекция 9 Жадные алгоритмы
Лекция 9 Жадные алгоритмы
 
Большой брат помогает тебе
Большой брат помогает тебеБольшой брат помогает тебе
Большой брат помогает тебе
 
Arithmetical Research
Arithmetical ResearchArithmetical Research
Arithmetical Research
 
Урок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеУрок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном коде
 
Урок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхУрок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данных
 
для чего нужны десятичные
для чего нужны десятичныедля чего нужны десятичные
для чего нужны десятичные
 
тема 1.введение в pascal
тема 1.введение в pascalтема 1.введение в pascal
тема 1.введение в pascal
 
Data types. Variables
Data types. VariablesData types. Variables
Data types. Variables
 
лабораторная работа 3
лабораторная работа 3лабораторная работа 3
лабораторная работа 3
 
Урок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиУрок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен данными
 

Similar to Урок 13. Паттерн 5. Адресная арифметика

Как стандарт C++0x поможет в борьбе с 64-битными ошибками
Как стандарт C++0x поможет в борьбе с 64-битными ошибкамиКак стандарт C++0x поможет в борьбе с 64-битными ошибками
Как стандарт C++0x поможет в борьбе с 64-битными ошибкамиTatyanazaxarova
 
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовУрок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовTatyanazaxarova
 
64-битный конь, который умеет считать
64-битный конь, который умеет считать64-битный конь, который умеет считать
64-битный конь, который умеет считатьTatyanazaxarova
 
Статический анализ Си++ кода и новый стандарт языка C++0x
Статический анализ Си++ кода и новый стандарт языка C++0xСтатический анализ Си++ кода и новый стандарт языка C++0x
Статический анализ Си++ кода и новый стандарт языка C++0xTatyanazaxarova
 
Лекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIЛекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIAlexey Paznikov
 
Оптимизация в мире 64-битных ошибок
Оптимизация  в мире 64-битных ошибокОптимизация  в мире 64-битных ошибок
Оптимизация в мире 64-битных ошибокTatyanazaxarova
 
Коллекция примеров 64-битных ошибок в реальных программах
Коллекция примеров 64-битных ошибок в реальных программахКоллекция примеров 64-битных ошибок в реальных программах
Коллекция примеров 64-битных ошибок в реальных программахTatyanazaxarova
 
Особенности разработки 64-битных приложений
Особенности разработки 64-битных приложенийОсобенности разработки 64-битных приложений
Особенности разработки 64-битных приложенийTatyanazaxarova
 
Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)Mikhail Kurnosov
 
Статический анализ кода для верификации 64-битных приложений
Статический анализ кода для верификации 64-битных приложенийСтатический анализ кода для верификации 64-битных приложений
Статический анализ кода для верификации 64-битных приложенийTatyanazaxarova
 
СИ++ УМЕР. ДА ЗДРАВСТВУЕТ СИ++
СИ++ УМЕР. ДА ЗДРАВСТВУЕТ СИ++СИ++ УМЕР. ДА ЗДРАВСТВУЕТ СИ++
СИ++ УМЕР. ДА ЗДРАВСТВУЕТ СИ++Pavel Tsukanov
 
Урок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиУрок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиTatyanazaxarova
 
C++ осень 2013 лекция 2
C++ осень 2013 лекция 2C++ осень 2013 лекция 2
C++ осень 2013 лекция 2Technopark
 
PVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийPVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийTatyanazaxarova
 
2010 егэ часть А
2010 егэ часть А2010 егэ часть А
2010 егэ часть Аfinatalya
 
Статический анализ исходного кода на примере WinMerge
Статический анализ исходного кода на примере WinMergeСтатический анализ исходного кода на примере WinMerge
Статический анализ исходного кода на примере WinMergeTatyanazaxarova
 
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.Tatyanazaxarova
 

Similar to Урок 13. Паттерн 5. Адресная арифметика (20)

Как стандарт C++0x поможет в борьбе с 64-битными ошибками
Как стандарт C++0x поможет в борьбе с 64-битными ошибкамиКак стандарт C++0x поможет в борьбе с 64-битными ошибками
Как стандарт C++0x поможет в борьбе с 64-битными ошибками
 
Step cpp0201
Step cpp0201Step cpp0201
Step cpp0201
 
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовУрок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
 
64-битный конь, который умеет считать
64-битный конь, который умеет считать64-битный конь, который умеет считать
64-битный конь, который умеет считать
 
Статический анализ Си++ кода и новый стандарт языка C++0x
Статический анализ Си++ кода и новый стандарт языка C++0xСтатический анализ Си++ кода и новый стандарт языка C++0x
Статический анализ Си++ кода и новый стандарт языка C++0x
 
Лекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIЛекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPI
 
Оптимизация в мире 64-битных ошибок
Оптимизация  в мире 64-битных ошибокОптимизация  в мире 64-битных ошибок
Оптимизация в мире 64-битных ошибок
 
Коллекция примеров 64-битных ошибок в реальных программах
Коллекция примеров 64-битных ошибок в реальных программахКоллекция примеров 64-битных ошибок в реальных программах
Коллекция примеров 64-битных ошибок в реальных программах
 
Особенности разработки 64-битных приложений
Особенности разработки 64-битных приложенийОсобенности разработки 64-битных приложений
Особенности разработки 64-битных приложений
 
Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)
 
Статический анализ кода для верификации 64-битных приложений
Статический анализ кода для верификации 64-битных приложенийСтатический анализ кода для верификации 64-битных приложений
Статический анализ кода для верификации 64-битных приложений
 
СИ++ УМЕР. ДА ЗДРАВСТВУЕТ СИ++
СИ++ УМЕР. ДА ЗДРАВСТВУЕТ СИ++СИ++ УМЕР. ДА ЗДРАВСТВУЕТ СИ++
СИ++ УМЕР. ДА ЗДРАВСТВУЕТ СИ++
 
паскаль 10кл 14
паскаль 10кл 14паскаль 10кл 14
паскаль 10кл 14
 
Урок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиУрок 24. Фантомные ошибки
Урок 24. Фантомные ошибки
 
C++ осень 2013 лекция 2
C++ осень 2013 лекция 2C++ осень 2013 лекция 2
C++ осень 2013 лекция 2
 
PVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийPVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложений
 
2010 егэ часть А
2010 егэ часть А2010 егэ часть А
2010 егэ часть А
 
Статический анализ исходного кода на примере WinMerge
Статический анализ исходного кода на примере WinMergeСтатический анализ исходного кода на примере WinMerge
Статический анализ исходного кода на примере WinMerge
 
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
 
C language. Introduction
C language. IntroductionC language. Introduction
C language. Introduction
 

More from Tatyanazaxarova

Урок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияУрок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияTatyanazaxarova
 
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокУрок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокTatyanazaxarova
 
Урок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияУрок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияTatyanazaxarova
 
Урок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхУрок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхTatyanazaxarova
 
Урок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибокУрок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибокTatyanazaxarova
 
Урок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокУрок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокTatyanazaxarova
 
Урок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииУрок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииTatyanazaxarova
 
Статический анализ Си++ кода
Статический анализ Си++ кодаСтатический анализ Си++ кода
Статический анализ Си++ кодаTatyanazaxarova
 
PVS-Studio научился следить за тем, как вы программируете
PVS-Studio научился следить за тем, как вы программируетеPVS-Studio научился следить за тем, как вы программируете
PVS-Studio научился следить за тем, как вы программируетеTatyanazaxarova
 
Пояснения к статье про Copy-Paste
Пояснения к статье про Copy-PasteПояснения к статье про Copy-Paste
Пояснения к статье про Copy-PasteTatyanazaxarova
 
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...Tatyanazaxarova
 
Статический анализ и ROI
Статический анализ и ROIСтатический анализ и ROI
Статический анализ и ROITatyanazaxarova
 
Вечный вопрос измерения времени
Вечный вопрос измерения времениВечный вопрос измерения времени
Вечный вопрос измерения времениTatyanazaxarova
 
По колено в Си++ г... коде
По колено в Си++ г... кодеПо колено в Си++ г... коде
По колено в Си++ г... кодеTatyanazaxarova
 
Статический анализ и регулярные выражения
Статический анализ и регулярные выраженияСтатический анализ и регулярные выражения
Статический анализ и регулярные выраженияTatyanazaxarova
 
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...Tatyanazaxarova
 

More from Tatyanazaxarova (17)

Урок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияУрок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окружения
 
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокУрок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
 
Урок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияУрок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. Исключения
 
Урок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхУрок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединениях
 
Урок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибокУрок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибок
 
Урок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокУрок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибок
 
Урок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииУрок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурации
 
Статический анализ Си++ кода
Статический анализ Си++ кодаСтатический анализ Си++ кода
Статический анализ Си++ кода
 
PVS-Studio
PVS-Studio PVS-Studio
PVS-Studio
 
PVS-Studio научился следить за тем, как вы программируете
PVS-Studio научился следить за тем, как вы программируетеPVS-Studio научился следить за тем, как вы программируете
PVS-Studio научился следить за тем, как вы программируете
 
Пояснения к статье про Copy-Paste
Пояснения к статье про Copy-PasteПояснения к статье про Copy-Paste
Пояснения к статье про Copy-Paste
 
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
 
Статический анализ и ROI
Статический анализ и ROIСтатический анализ и ROI
Статический анализ и ROI
 
Вечный вопрос измерения времени
Вечный вопрос измерения времениВечный вопрос измерения времени
Вечный вопрос измерения времени
 
По колено в Си++ г... коде
По колено в Си++ г... кодеПо колено в Си++ г... коде
По колено в Си++ г... коде
 
Статический анализ и регулярные выражения
Статический анализ и регулярные выраженияСтатический анализ и регулярные выражения
Статический анализ и регулярные выражения
 
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...
 

Урок 13. Паттерн 5. Адресная арифметика

  • 1. Урок 13. Паттерн 5. Адресная арифметика Мы специально выбрали номер "тринадцать" для этого урока, поскольку ошибки, связанные с адресной арифметикой в 64-битных системах, являются наиболее коварными. Надеемся, число 13 заставит вас быть внимательнее. Суть паттерна: чтобы избежать ошибок в 64-битном коде используйте для адресной арифметики только memsize-типы. Рассмотрим код: unsigned short a16, b16, c16; char *pointer; ... pointer += a16 * b16 * c16; Данный пример корректно работает с указателями, если значение выражения "a16 * b16 * c16" не превышает INT_MAX (2147483647). Такой код мог всегда корректно работать на 32-битной платформе. В рамках 32-битной архитектуры программе недоступен объем памяти для создания массива подобного размеров. На 64-битной архитектуре это ограничение снято, и размер массива легко может превысить INT_MAX элементов. Допустим, мы хотим сдвинуть значение указателя на 6.000.000.000 байт, и поэтому переменные a16, b16 и c16 имеют значения 3000, 2000 и 1000 соответственно. При вычислении выражения "a16 * b16 * c16" все переменные, согласно правилам языка Си++, будут приведены к типу int, а уже затем будет произведено их умножение. В ходе выполнения умножения произойдет переполнение. Некорректный результат выражения будет расширен до типа ptrdiff_t, и произойдет некорректное вычисление указателя. Следует старательно избегать возможных переполнений в арифметике с указателями. Для этого лучше всего использовать memsize-типы или явное приведение типов в выражениях, где присутствуют указатели. Используя явное приведение типов, мы можем переписать код следующим образом: short a16, b16, c16; char *pointer; ... pointer += static_cast<ptrdiff_t>(a16) * static_cast<ptrdiff_t>(b16) * static_cast<ptrdiff_t>(c16); Если вы думаете, что злоключения ждут неаккуратные программы только на больших объемах данных, то мы вынуждены вас огорчить. Рассмотрим интересный код для работы с массивом,
  • 2. содержащим всего 5 элементов. Этот пример работоспособен в 32-битном варианте и не работоспособен в 64-битном: int A = -2; unsigned B = 1; int array[5] = { 1, 2, 3, 4, 5 }; int *ptr = array + 3; ptr = ptr + (A + B); //Invalid pointer value on 64-bit platform printf("%in", *ptr); //Access violation on 64-bit platform Давайте проследим, как происходит вычисление выражения "ptr + (A + B)": • Согласно правилам языка Си++ переменная A типа int приводится к типу unsigned. • Происходит сложение A и B. В результате мы получаем значение 0xFFFFFFFF типа unsigned. • Вычисляется выражение "ptr + 0xFFFFFFFFu". Что из этого выйдет, будет зависеть от размера указателя на данной архитектуре. Если сложение будет происходить в 32-битной программе, то данное выражение будет эквивалентно "ptr - 1", и мы успешно распечатаем число "3". В 64-битной программе к указателю честным образом прибавится значение 0xFFFFFFFFu, в результате чего указатель окажется далеко за пределами массива. И при доступе к элементу по данному указателю нас ждут неприятности. Для предотвращения подобной ситуации, как и в первом случае, рекомендуем использовать в арифметике с указателями только memsize-типы. Два варианта исправления кода: ptr = ptr + (ptrdiff_t(A) + ptrdiff_t(B)); ptrdiff_t A = -2; size_t B = 1; ... ptr = ptr + (A + B); Вы можете возразить и предложить следующий вариант исправления: int A = -2; int B = 1; ... ptr = ptr + (A + B); Да, такой код будет работать, но он плох по ряду причин: • Он будет приучать к неаккуратной работе с указателями. Через некоторое время вы можете забыть нюансы и по ошибке вновь сделать одну из переменных типа unsigned.
  • 3. Использование не memsize-типов совместно с указателями потенциально опасно само по себе. Допустим, что в выражении с указателем участвует переменная Delta типа int, и это выражение совершенно корректно. Ошибка может крыться в вычислении самой переменной Delta, так как 32 бит может не хватить для необходимых вычислений, при работе с большими массивами данных. Использование memsize-типа для переменной Delta автоматически устраняет такую опасность. • Код, использующий при работе с указателями типы size_t, ptrdiff_t и другие memsize-типы, приводит к генерации более оптимального двоичного кода, о чем будет рассказано подробнее в одном из следующих уроков. Индексация массивов Данная разновидность ошибок выделена для лучшей структуризации изложения, так как индексация в массивах с использованием квадратных скобок - это всего лишь иная запись адресной арифметики, рассмотренной выше. В программах, обрабатывающих большие объемы данных, могут встретиться ошибки связанные с индексацией больших массивов, или возникнуть вечные циклы. Следующий пример содержит сразу 2 ошибки: const size_t size = ...; char *array = ...; char *end = array + size; for (unsigned i = 0; i != size; ++i) { const int one = 1; end[-i - one] = 0; } Первая ошибка заключается в том, что если размер обрабатываемых данных превысит 4 гигабайта (0xFFFFFFFF), то возможно возникновение вечного цикла, поскольку переменная 'i' имеет тип 'unsigned' и никогда не достигнет значения больше чем 0xFFFFFFFF. Мы специально пишем, что возникновение возможно, но не обязательно оно произойдет. Это зависит от того, какой код построит компилятор. Например, в отладочном (debug) режиме вечный цикл будет присутствовать, а в release-коде зацикливание исчезнет, так компилятор примет решение оптимизировать код, используя для счетчика 64-битный регистр, и цикл будет корректным. Все это добавляет путаницы, и код, который работал вчера, неожиданно может перестать работать на следующий день. Вторая ошибка связана с проходом по массиву от конца к началу, для чего используются отрицательные значения индексов. Приведенный код работоспособен в 32-битном режиме, но при его запуске на 64-битной машине на первой же итерации цикла произойдет доступ за границы массива, и программа аварийно завершится. Рассмотрим причину такого поведения.
  • 4. Хотя то, что написано ниже, аналогично примеру с "ptr = ptr + (A + B);", это повторение делается сознательно. Важно показать, что опасность скрывается даже в простых конструкциях и может выглядеть по-разному. Согласно правилам языка Си++ на 32-битной системе выражение "-i - one" будет вычисляться следующим образом (на первом шаге i = 0): 1. Выражение "-i" имеет тип unsigned и имеет значение 0x00000000u. 2. Переменная 'one' будет расширена от типа 'int' до типа unsigned и будет равна 0x00000001u. Примечание: Тип int расширяется (согласно стандарту языка Си++) до типа 'unsigned', если он участвует в операции, где второй аргумент имеет тип unsigned. 3. Происходит операция вычитания, в котором участвуют два значения типа unsigned и результат выполнения операции равен 0x00000000u - 0x00000001u = 0xFFFFFFFFu. Обратите внимание, что результат имеет беззнаковый тип. На 32-битной системе обращение к массиву по индексу 0xFFFFFFFFu эквивалентно использованию индекса -1. То есть end[0xFFFFFFFFu] является аналогом end[-1]. В результате происходит корректная обработка элемента массива. В 64-битной системе в последнем пункте картина будет иной. Произойдет расширение типа unsigned до знакового ptrdiff_t и индекс массива будет равен 0x00000000FFFFFFFFi64. В результате произойдет выход за рамки массива. Для исправления кода необходимо использовать такие типы, как ptrdiff_t и size_t. Чтобы окончательно убедить вас в необходимости использования только memsize-типов для индексации и в выражениях адресной арифметики, приведем еще один пример. class Region { float *array; int Width, Height, Depth; float Region::GetCell(int x, int y, int z) const; ... }; float Region::GetCell(int x, int y, int z) const { return array[x + y * Width + z * Width * Height]; } Данный код взят из реальной программы математического моделирования, в которой важным ресурсом является объем оперативной памяти, и возможность на 64-битной архитектуре использовать более 4 гигабайт памяти существенно увеличивает вычислительные возможности. В программах данного класса для экономии памяти часто используют одномерные массивы, осуществляя работу с ними как с трехмерными массивами. Для этого существуют функции, аналогичные GetCell, обеспечивающие доступ к необходимым элементам. Но приведенный код будет корректно работать только с массивами, содержащими менее INT_MAX элементов. Причина - использование 32-битных типов int для вычисления индекса элемента.
  • 5. Программисты часто допускают ошибку, пытаясь исправить код следующим образом: float Region::GetCell(int x, int y, int z) const { return array[static_cast<ptrdiff_t>(x) + y * Width + z * Width * Height]; } Они знают, что по правилам языка Си++ выражение для вычисления индекса будет иметь тип ptrdiff_t и надеются за счет этого избежать переполнения. Но переполнение может произойти внутри подвыражения "y * Width" или "z * Width * Height", так как для их вычисления по- прежнему используется тип int. Если вы хотите исправить код, не изменяя типов переменных, участвующих в выражении, то вы можете явно привести каждую переменную к memsize-типу: float Region::GetCell(int x, int y, int z) const { return array[ptrdiff_t(x) + ptrdiff_t(y) * ptrdiff_t(Width) + ptrdiff_t(z) * ptrdiff_t(Width) * ptrdiff_t(Height)]; } Другое, более верное решение - изменить типы переменных на memsize-тип: typedef ptrdiff_t TCoord; class Region { float *array; TCoord Width, Height, Depth; float Region::GetCell(TCoord x, TCoord y, TCoord z) const; ... }; float Region::GetCell(TCoord x, TCoord y, TCoord z) const { return array[x + y * Width + z * Width * Height]; } Диагностика Ошибки адресной арифметики хорошо диагностируются инструментом PVS-Studio. Анализатор предупреждает о потенциально опасных выражениях с помощью диагностических сообщений V102 и V108.
  • 6. По возможности анализатор пытается понять, когда использование не memsize-типа в адресной арифметике безопасно и не выдавать в этом месте предупреждения. В результате, поведение анализатора иногда может показаться странным. В таких случаях мы просим не спешить и постараться разобраться. Рассмотрим следующий код: char Arr[] = { '0', '1', '2', '3', '4' }; char *p = Arr + 2; cout << p[0u + 1] << endl; cout << p[0u - 1] << endl; //V108 Данный код исправно работает в 32-битном режиме и печатает на экране числа 3 и 1. При проверке этого кода мы получим предупреждение только на одну строку с выражением "p[0u - 1]". И это совершенно верно! Если вы скомпилируете и запустите данный пример в 64-битном режиме, то увидите, как на экране будет распечатано значение 3, после чего произойдет аварийное завершение программы. Если вы уверены в корректности индексации, то вы можете изменить соответствующую настройку анализатора на вкладке настроек Settings: General или использовать фильтры. Также можно использовать явное приведение типов. Авторы курса: Андрей Карпов (karpov@viva64.com), Евгений Рыжков (evg@viva64.com). Правообладателем курса "Уроки разработки 64-битных приложений на языке Си/Си++" является ООО "Системы программной верификации". Компания занимается разработкой программного обеспечения в области анализа исходного кода программ. Сайт компании: http://www.viva64.com. Контактная информация: e-mail: support@viva64.com, 300027, г. Тула, а/я 1800.