SlideShare a Scribd company logo
1 of 6
Download to read offline
Что такое size_t и ptrdiff_t
Автор: Андрей Карпов

Дата: 21.09.2009


Аннотация
Статья поможет читателю разобраться, что представляют собой типы size_t и ptrdiff_t, для чего они
нужны и когда целесообразно их использование. Статья будет интересна разработчикам,
начинающим создание 64-битных приложений, где использование типов size_t и ptrdiff_t
обеспечивает быстродействие, возможность работы с большими объемами данных и
переносимость между разными платформами.


Введение
Сразу заметим, что данные в статье определения и рекомендации относятся к наиболее
распространненым на данный момент архитектурам (IA-32, Intel 64, IA-64), и могут быть неточны
по отношению к экзотическим архитектурам.

Типы size_t и ptrdiff_t были созданы для того, чтобы осуществлять корректную адресную
арифметику. Долгое время было принято считать, что размер int совпадает с размером
машинного слова (разрядностью микропроцессора) и его можно использовать в качестве
индексов, для хранения размеров объектов или указателей. Соответственно адресная арифметика
также строилась с использованием типов int и unsigned. Тип int используется в большинстве
обучающих материалов по программированию на Си и Си++ в телах циклов и в качестве индексов.
Практически каноническим выглядит пример следующего вида:

for (int i = 0; i < n; i++)

  a[i] = 0;

С развитием микропроцессоров и ростом их разрядности стало нерационально дальнейшее
увеличение размерностей типа int. Причин для этого много: экономия используемой памяти,
максимальная совместимость и так далее. В результате появилось несколько моделей данных,
описывающих соотношение размеров базовых типов языка Си/Си++. В таблице N1 приведены
основные модели данных и перечислены наиболее популярные системы их использующие.
Таблица N1. Модели данных (data models)

Как видно из таблицы, не так просто выбрать тип переменной для хранения указателя или
размера объекта. Чтобы наиболее красиво решить эту проблему и появились типы size_t и
ptrdiff_t. Они гарантированно могут использоваться для адресной арифметики. Теперь
каноническим должен стать следующий код:

for (ptrdiff_t i = 0; i < n; i++)

  a[i] = 0;

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


Тип size_t
Тип size_t - базовый беззнаковый целочисленный тип языка Си/Си++. Является типом результата,
возвращаемого оператором sizeof. Размер типа выбирается таким образом, чтобы в него можно
было записать максимальный размер теоретически возможного массива любого типа. Например,
на 32-битной системе size_t будет занимать 32-бита, на 64-битной - 64-бита. Другими словами в
переменную типа size_t может быть безопасно помещен указатель. Исключение составляют
указатели на функции классов, но это особый случай. Хотя в size_t можно помещать указатель, для
этих целей лучше подходит другой беззнаковый целочисленный тип uintptr_t, само название
которого отражает эту возможность. Типы size_t и uintptr_t являются синонимами. Тип size_t
обычно применяется для счетчиков циклов, индексации массивов, хранения размеров, адресной
арифметики.

Максимально допустимым значением типа size_t является константа SIZE_MAX.


Тип ptrdiff_t
Тип ptrdiff_t - базовый знаковый целочисленный тип языка Си/Си++. Размер типа выбирается
таким образом, чтобы в него можно было записать максимальный размер теоретически
возможного массива любого типа. На 32-битной системе ptrdiff_t будет занимать 32-бита, на 64-
битной - 64-бита. Как и в size_t в переменную типа ptrdiff_t может быть безопасно помещен
указатель, за исключением указателя на функцию класса. Также ptrdiff_t является типом
результата выражения, где один указатель вычитается из другого (ptr1-ptr2). Тип ptrdiff_t обычно
применяется для счетчиков циклов, индексации массивов, хранения размеров, адресной
арифметики. У типа ptrdiff_t есть синоним intptr_t, название которого лучше отражает, что тип
может хранить в себе указатель.


Переносимость size_t и ptrdiff_t
Типы size_t и ptrdiff_t позволяют писать переносимый код. Размер size_t и ptrdiff_t всегда
совпадают с размером указателя. По этой причине именно эти типы следует использовать в
качестве индексов больших массивов, для хранения указателей и арифметики с указателями.

Разработчики Linux приложений часто используют для этих целей тип long. В рамках 32-битных и
64-битных моделей данных, принятых в Linux это действительно работает. Размер типа long
совпадает с размером указателя. Но такой код несовместим с моделью данных Windows и
соответственно его нельзя считать хорошо переносимым. Более правильным решением будет
использование типов size_t и ptrdiff_t.

Разработчики Windows в качестве альтернативы size_t и ptrdiff_t могут использовать типы
DWORD_PTR, SIZE_T, SSIZE_T и так далее. Но желательно также ограничиваться типами size_t и
ptrdiff_t.


Безопасность типов ptrdiff_t и size_t в адресной арифметике
Проблемы адресной арифметики стали активно проявлять себя с началом освоения 64-битных
систем. Наибольшее число проблем при переносе 32-битных приложений на 64-битные системы
связанно с использованием неподходящих для работы с указателями и массивами типов, таких
как int и long. Этим проблемы переноса приложений на 64-битные системы не ограничивается, но
большинство ошибок связанны именно с адресной арифметикой и работе с индексами.

Возьмем самый простой пример:

size_t n = ...;

for (unsigned i = 0; i < n; i++)

  a[i] = 0;

Если мы работаем с массивом, состоящим более чем из UINT_MAX элементов, то данный код
является некорректным. При этом выявить ошибку и предсказать поведение данного кода не так
просто. Отладочная (debug) версия зависнет, но редко кто в отладочной версии будет
обрабатывать гигабайты данных. А вот рабочая (release) версия в зависимости от настроек
оптимизации и особенностей кода, может как зависнуть, так и неожиданно корректно заполнить
все ячейки массива, создавая иллюзию корректной работы. В результате в программе появляются
плавающие ошибки, возникающие или пропадающие после малейшего изменения кода.
Подробнее о таких фантомных ошибках и их опасностях можно познакомиться в статье "64-
битный конь, который умеет считать" [1].

Пример еще одной дремлющей ошибки, которая проявит себя при определенном сочетании
входных данных (значении переменных A и B):
int A = -2;

unsigned B = 1;

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

int *ptr = array + 3;

ptr = ptr + (A + B); //Error

printf("%in", *ptr);

Данный код будет успешно выполняться в 32-битном варианте и печатать на экране число "3".
После компиляции 64-битном режиме при выполнении кода возникнет сбой. Рассмотрим
последовательность выполнения кода и причину ошибки:

   •   Переменная A типа int приводится к типу unsigned;
   •   Происходит сложение A и B. В результате мы получаем значение 0xFFFFFFFF типа unsigned;
   •   Вычисляется выражение "ptr + 0xFFFFFFFFu". Результат зависит от размерности указателя
       на данной платформе. В 32-битной программе, выражение будет эквивалентно "ptr - 1" и
       мы успешно распечатаем число 3. В 64-битной программе к указателю прибавится
       значение 0xFFFFFFFFu, в результате чего указатель окажется далеко за пределами массива.

Приведенные ошибки можно легко избежать, используя тип size_t или ptrdiff_t. В первом случае,
если тип переменной "i" будет size_t, то не возникнет зацикливания. Во втором, если мы
используем типы size_t или ptrdiff_t для переменных "A" и "B", то корректно распечатаем число
"3".

Сформулируем совет: везде, где присутствует работа с указателями или массивами следует
использовать типы size_t и ptrdiff_t.

Более подробно с тем, каких ошибок можно избежать, используя типы size_t и ptrdiff_t можно
познакомиться в следующих статьях:

   •   20 ловушек переноса Си++ - кода на 64-битную платформу [2];
   •   Безопасность 64-битного кода [3];
   •   Поиск ловушек в Си/Си++ коде при переносе приложений под 64-битную версию Windows
       [4].


Быстродействие кода, использующего типы ptrdiff_t и size_t
Использование типов ptrdiff_t и size_t в адресной арифметике помимо повышения надежности
кода может дать дополнительный выигрыш в производительности. Например, использование в
качестве индекса типа int, размерность которого отличается от размерности указателя приводит к
тому, что в двоичном коде будут присутствовать дополнительные команды преобразования
данных. Речь идет о 64-битном коде, в котором размер указателей стал равен 64-битам, а размер
типа int остался 32-битным.

Показать короткий пример преимущества size_t над unsigned не простая задача. Чтобы быть
объективным необходимо использовать оптимизирующие возможности компилятора. А два
варианта оптимизированного кода часто становятся слишком непохожим, чтобы легко было
продемонстрировать отличие. Попытка создать нечто близкое к простому примеру увенчалась
успехом только с шестой попытки. И все равно пример не идеален, так как показывает не лишние
преобразование типов данных, о которых сказано выше, а то, что компилятор смог построить
более эффективный код при использовании типа size_t. Рассмотрим код программы,
переставляющий элементы массива в обратном порядке:

unsigned arraySize;

...

for (unsigned i = 0; i < arraySize / 2; i++)

{

    float value = array[i];

    array[i] = array[arraySize - i - 1];

    array[arraySize - i - 1] = value;

}

В примере переменные "arraySize" и "i" имеют тип unsigned. Этот тип легко можно заменить на тип
size_t и сравнить небольшой участок ассемблерного кода, показанный на рисунке 1.




    Рисунок N1. Сравнение 64-битного ассемблерного кода при использовании типов unsigned и
                                            size_t

Компилятор смог построить более лаконичный код, когда использовал 64-битные регистры. Автор
не берется утверждать, что код, созданный при использовании типа unsigned (текст слева), будет
работать медленнее, чем код с использованием size_t (текст слева). Сравнить скорость
выполнения кода на современных процессорах крайне сложная задача. Но из примера видно, что
когда компилятор работает с массивами, используя 64-битные типы, он может строить более
короткий и быстрый код.

По личному опыту автора, грамотная замена типов int и unsigned на ptrdiff_t и size_t может дать на
64-битной системе дополнительный прирост производительности до 10%. С одним из примеров
увеличения скорости от использования типов ptrdiff_t и size_t можно познакомиться в четвертой
главе статьи "Разработка ресурсоемких приложений в среде Visual C++" [5].


Рефакторинг кода с целью перехода на типы ptrdiff_t и size_t
Как читатель уже убедился, использование типов ptrdiff_t и size_t имеет ряд преимуществ для 64-
битных программ. Но и заменить, скажем все unsigned на size_t - не является выходом. Во-первых,
это не гарантирует корректность программы на 64-битной системы. Во-вторых, скорее всего из-за
такой замены возникнут новые ошибки, нарушится совместимость форматов данных и так далее.
Не стоит забывать, что после такой замены существенно возрастет и объем потребляемой
программой памяти. Причем увеличение объема требуемой памяти может замедлить работу
приложения, так как в кэш будет помещаться меньше объектов, с которыми идет работа.

Следовательно, внедрение в старый код типов ptrdiff_t и size_t является задачей постепенного
рефакторинга, требующего большого количества времени. Фактически необходимо просмотреть
весь код и внести необходимые исправления. Такой подход практически является слишком
дорогостоящим и неэффективным. Можно предложить 2 варианта:

   1. Использовать специализированные инструменты, такие как Viva64, входящий в состав PVS-
      Studio. Viva64 это статический анализатор кода, обнаруживающий места, где рационально
      изменить типы данных, чтобы программа была корректна и эффективно работала на 64-
      битных системах. Подробнее смотрите "Учебное пособие по PVS-Studio" [6].
   2. Если 32-битную программу не планируется адаптировать для 64-битных систем, то и нет
      смысла заниматься рефакторингом типов данных. 32-битная программа не получит
      никаких преимуществ от использования типов ptrdiff_t и size_t.


Библиографический список
   1. Андрей Карпов. 64-битный конь, который умеет считать. http://www.viva64.com/art-1-1-
      1064884779.html
   2. Андрей Карпов, Евгений Рыжков. 20 ловушек переноса Си++ - кода на 64-битную
      платформу. http://www.viva64.com/art-1-1-1958348565.html
   3. Андрей Карпов. Безопасность 64-битного кода. http://www.viva64.com/art-1-1-
      38488545.html
   4. Андрей Карпов, Евгений Рыжков. Поиск ловушек в Си/Си++ коде при переносе
      приложений под 64-битную версию Windows. http://www.viva64.com/art-1-1-
      329725213.html
   5. Андрей Карпов, Евгений Рыжков. Разработка ресурсоемких приложений в среде Visual C++.
      http://www.viva64.com/art-1-1-640027853.html
   6. Евгений Рыжков. Учебное пособие по PVS-Studio. http://www.viva64.com/art-4-1-
      1796251700.html

More Related Content

Similar to Что такое size_t и ptrdiff_t

Урок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаУрок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаTatyanazaxarova
 
Урок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программУрок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программTatyanazaxarova
 
Урок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейУрок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейTatyanazaxarova
 
Коллекция примеров 64-битных ошибок в реальных программах
Коллекция примеров 64-битных ошибок в реальных программахКоллекция примеров 64-битных ошибок в реальных программах
Коллекция примеров 64-битных ошибок в реальных программахTatyanazaxarova
 
Большой брат помогает тебе
Большой брат помогает тебеБольшой брат помогает тебе
Большой брат помогает тебеTatyanazaxarova
 
Статический анализ кода для верификации 64-битных приложений
Статический анализ кода для верификации 64-битных приложенийСтатический анализ кода для верификации 64-битных приложений
Статический анализ кода для верификации 64-битных приложенийTatyanazaxarova
 
Статический анализ Си++ кода и новый стандарт языка C++0x
Статический анализ Си++ кода и новый стандарт языка C++0xСтатический анализ Си++ кода и новый стандарт языка C++0x
Статический анализ Си++ кода и новый стандарт языка C++0xTatyanazaxarova
 
Как стандарт C++0x поможет в борьбе с 64-битными ошибками
Как стандарт C++0x поможет в борьбе с 64-битными ошибкамиКак стандарт C++0x поможет в борьбе с 64-битными ошибками
Как стандарт C++0x поможет в борьбе с 64-битными ошибкамиTatyanazaxarova
 
Урок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаУрок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаTatyanazaxarova
 
Урок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхУрок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхTatyanazaxarova
 
Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)Mikhail Kurnosov
 
Урок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурУрок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурTatyanazaxarova
 
Урок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеУрок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеTatyanazaxarova
 
Особенности разработки 64-битных приложений
Особенности разработки 64-битных приложенийОсобенности разработки 64-битных приложений
Особенности разработки 64-битных приложенийTatyanazaxarova
 
Статический анализ исходного кода на примере WinMerge
Статический анализ исходного кода на примере WinMergeСтатический анализ исходного кода на примере WinMerge
Статический анализ исходного кода на примере WinMergeTatyanazaxarova
 
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовУрок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовTatyanazaxarova
 
PVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийPVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийTatyanazaxarova
 
Chislovye tipy dannykh_i_ikh_ispolzovanie_v_vba
Chislovye tipy dannykh_i_ikh_ispolzovanie_v_vbaChislovye tipy dannykh_i_ikh_ispolzovanie_v_vba
Chislovye tipy dannykh_i_ikh_ispolzovanie_v_vbagreg1496
 
64-битный конь, который умеет считать
64-битный конь, который умеет считать64-битный конь, который умеет считать
64-битный конь, который умеет считатьTatyanazaxarova
 

Similar to Что такое size_t и ptrdiff_t (20)

Урок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаУрок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числа
 
Урок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программУрок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программ
 
Урок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейУрок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателей
 
Коллекция примеров 64-битных ошибок в реальных программах
Коллекция примеров 64-битных ошибок в реальных программахКоллекция примеров 64-битных ошибок в реальных программах
Коллекция примеров 64-битных ошибок в реальных программах
 
Большой брат помогает тебе
Большой брат помогает тебеБольшой брат помогает тебе
Большой брат помогает тебе
 
Статический анализ кода для верификации 64-битных приложений
Статический анализ кода для верификации 64-битных приложенийСтатический анализ кода для верификации 64-битных приложений
Статический анализ кода для верификации 64-битных приложений
 
Статический анализ Си++ кода и новый стандарт языка C++0x
Статический анализ Си++ кода и новый стандарт языка C++0xСтатический анализ Си++ кода и новый стандарт языка C++0x
Статический анализ Си++ кода и новый стандарт языка C++0x
 
Как стандарт C++0x поможет в борьбе с 64-битными ошибками
Как стандарт C++0x поможет в борьбе с 64-битными ошибкамиКак стандарт C++0x поможет в борьбе с 64-битными ошибками
Как стандарт C++0x поможет в борьбе с 64-битными ошибками
 
Урок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаУрок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвига
 
Урок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхУрок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данных
 
Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)
 
Урок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурУрок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структур
 
Урок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеУрок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном коде
 
Особенности разработки 64-битных приложений
Особенности разработки 64-битных приложенийОсобенности разработки 64-битных приложений
Особенности разработки 64-битных приложений
 
Статический анализ исходного кода на примере WinMerge
Статический анализ исходного кода на примере WinMergeСтатический анализ исходного кода на примере WinMerge
Статический анализ исходного кода на примере WinMerge
 
Step cpp0201
Step cpp0201Step cpp0201
Step cpp0201
 
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовУрок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
 
PVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийPVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложений
 
Chislovye tipy dannykh_i_ikh_ispolzovanie_v_vba
Chislovye tipy dannykh_i_ikh_ispolzovanie_v_vbaChislovye tipy dannykh_i_ikh_ispolzovanie_v_vba
Chislovye tipy dannykh_i_ikh_ispolzovanie_v_vba
 
64-битный конь, который умеет считать
64-битный конь, который умеет считать64-битный конь, который умеет считать
64-битный конь, который умеет считать
 

More from Tatyanazaxarova

Урок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияУрок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияTatyanazaxarova
 
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокУрок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокTatyanazaxarova
 
Урок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиУрок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиTatyanazaxarova
 
Урок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияУрок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияTatyanazaxarova
 
Урок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиУрок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен данными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
 

More from Tatyanazaxarova (18)

Урок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияУрок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окружения
 
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокУрок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
 
Урок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиУрок 24. Фантомные ошибки
Урок 24. Фантомные ошибки
 
Урок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияУрок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. Исключения
 
Урок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиУрок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен данными
 
Урок 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
 
Вечный вопрос измерения времени
Вечный вопрос измерения времениВечный вопрос измерения времени
Вечный вопрос измерения времени
 
По колено в Си++ г... коде
По колено в Си++ г... кодеПо колено в Си++ г... коде
По колено в Си++ г... коде
 
Статический анализ и регулярные выражения
Статический анализ и регулярные выраженияСтатический анализ и регулярные выражения
Статический анализ и регулярные выражения
 

Что такое size_t и ptrdiff_t

  • 1. Что такое size_t и ptrdiff_t Автор: Андрей Карпов Дата: 21.09.2009 Аннотация Статья поможет читателю разобраться, что представляют собой типы size_t и ptrdiff_t, для чего они нужны и когда целесообразно их использование. Статья будет интересна разработчикам, начинающим создание 64-битных приложений, где использование типов size_t и ptrdiff_t обеспечивает быстродействие, возможность работы с большими объемами данных и переносимость между разными платформами. Введение Сразу заметим, что данные в статье определения и рекомендации относятся к наиболее распространненым на данный момент архитектурам (IA-32, Intel 64, IA-64), и могут быть неточны по отношению к экзотическим архитектурам. Типы size_t и ptrdiff_t были созданы для того, чтобы осуществлять корректную адресную арифметику. Долгое время было принято считать, что размер int совпадает с размером машинного слова (разрядностью микропроцессора) и его можно использовать в качестве индексов, для хранения размеров объектов или указателей. Соответственно адресная арифметика также строилась с использованием типов int и unsigned. Тип int используется в большинстве обучающих материалов по программированию на Си и Си++ в телах циклов и в качестве индексов. Практически каноническим выглядит пример следующего вида: for (int i = 0; i < n; i++) a[i] = 0; С развитием микропроцессоров и ростом их разрядности стало нерационально дальнейшее увеличение размерностей типа int. Причин для этого много: экономия используемой памяти, максимальная совместимость и так далее. В результате появилось несколько моделей данных, описывающих соотношение размеров базовых типов языка Си/Си++. В таблице N1 приведены основные модели данных и перечислены наиболее популярные системы их использующие.
  • 2. Таблица N1. Модели данных (data models) Как видно из таблицы, не так просто выбрать тип переменной для хранения указателя или размера объекта. Чтобы наиболее красиво решить эту проблему и появились типы size_t и ptrdiff_t. Они гарантированно могут использоваться для адресной арифметики. Теперь каноническим должен стать следующий код: for (ptrdiff_t i = 0; i < n; i++) a[i] = 0; Именно он может обеспечить надежность, переносимость, быстродействие. Почему - станет ясно из дальнейшего текста статьи. Тип size_t Тип size_t - базовый беззнаковый целочисленный тип языка Си/Си++. Является типом результата, возвращаемого оператором sizeof. Размер типа выбирается таким образом, чтобы в него можно было записать максимальный размер теоретически возможного массива любого типа. Например, на 32-битной системе size_t будет занимать 32-бита, на 64-битной - 64-бита. Другими словами в переменную типа size_t может быть безопасно помещен указатель. Исключение составляют указатели на функции классов, но это особый случай. Хотя в size_t можно помещать указатель, для этих целей лучше подходит другой беззнаковый целочисленный тип uintptr_t, само название которого отражает эту возможность. Типы size_t и uintptr_t являются синонимами. Тип size_t обычно применяется для счетчиков циклов, индексации массивов, хранения размеров, адресной арифметики. Максимально допустимым значением типа size_t является константа SIZE_MAX. Тип ptrdiff_t Тип ptrdiff_t - базовый знаковый целочисленный тип языка Си/Си++. Размер типа выбирается таким образом, чтобы в него можно было записать максимальный размер теоретически возможного массива любого типа. На 32-битной системе ptrdiff_t будет занимать 32-бита, на 64- битной - 64-бита. Как и в size_t в переменную типа ptrdiff_t может быть безопасно помещен
  • 3. указатель, за исключением указателя на функцию класса. Также ptrdiff_t является типом результата выражения, где один указатель вычитается из другого (ptr1-ptr2). Тип ptrdiff_t обычно применяется для счетчиков циклов, индексации массивов, хранения размеров, адресной арифметики. У типа ptrdiff_t есть синоним intptr_t, название которого лучше отражает, что тип может хранить в себе указатель. Переносимость size_t и ptrdiff_t Типы size_t и ptrdiff_t позволяют писать переносимый код. Размер size_t и ptrdiff_t всегда совпадают с размером указателя. По этой причине именно эти типы следует использовать в качестве индексов больших массивов, для хранения указателей и арифметики с указателями. Разработчики Linux приложений часто используют для этих целей тип long. В рамках 32-битных и 64-битных моделей данных, принятых в Linux это действительно работает. Размер типа long совпадает с размером указателя. Но такой код несовместим с моделью данных Windows и соответственно его нельзя считать хорошо переносимым. Более правильным решением будет использование типов size_t и ptrdiff_t. Разработчики Windows в качестве альтернативы size_t и ptrdiff_t могут использовать типы DWORD_PTR, SIZE_T, SSIZE_T и так далее. Но желательно также ограничиваться типами size_t и ptrdiff_t. Безопасность типов ptrdiff_t и size_t в адресной арифметике Проблемы адресной арифметики стали активно проявлять себя с началом освоения 64-битных систем. Наибольшее число проблем при переносе 32-битных приложений на 64-битные системы связанно с использованием неподходящих для работы с указателями и массивами типов, таких как int и long. Этим проблемы переноса приложений на 64-битные системы не ограничивается, но большинство ошибок связанны именно с адресной арифметикой и работе с индексами. Возьмем самый простой пример: size_t n = ...; for (unsigned i = 0; i < n; i++) a[i] = 0; Если мы работаем с массивом, состоящим более чем из UINT_MAX элементов, то данный код является некорректным. При этом выявить ошибку и предсказать поведение данного кода не так просто. Отладочная (debug) версия зависнет, но редко кто в отладочной версии будет обрабатывать гигабайты данных. А вот рабочая (release) версия в зависимости от настроек оптимизации и особенностей кода, может как зависнуть, так и неожиданно корректно заполнить все ячейки массива, создавая иллюзию корректной работы. В результате в программе появляются плавающие ошибки, возникающие или пропадающие после малейшего изменения кода. Подробнее о таких фантомных ошибках и их опасностях можно познакомиться в статье "64- битный конь, который умеет считать" [1]. Пример еще одной дремлющей ошибки, которая проявит себя при определенном сочетании входных данных (значении переменных A и B):
  • 4. int A = -2; unsigned B = 1; int array[5] = { 1, 2, 3, 4, 5 }; int *ptr = array + 3; ptr = ptr + (A + B); //Error printf("%in", *ptr); Данный код будет успешно выполняться в 32-битном варианте и печатать на экране число "3". После компиляции 64-битном режиме при выполнении кода возникнет сбой. Рассмотрим последовательность выполнения кода и причину ошибки: • Переменная A типа int приводится к типу unsigned; • Происходит сложение A и B. В результате мы получаем значение 0xFFFFFFFF типа unsigned; • Вычисляется выражение "ptr + 0xFFFFFFFFu". Результат зависит от размерности указателя на данной платформе. В 32-битной программе, выражение будет эквивалентно "ptr - 1" и мы успешно распечатаем число 3. В 64-битной программе к указателю прибавится значение 0xFFFFFFFFu, в результате чего указатель окажется далеко за пределами массива. Приведенные ошибки можно легко избежать, используя тип size_t или ptrdiff_t. В первом случае, если тип переменной "i" будет size_t, то не возникнет зацикливания. Во втором, если мы используем типы size_t или ptrdiff_t для переменных "A" и "B", то корректно распечатаем число "3". Сформулируем совет: везде, где присутствует работа с указателями или массивами следует использовать типы size_t и ptrdiff_t. Более подробно с тем, каких ошибок можно избежать, используя типы size_t и ptrdiff_t можно познакомиться в следующих статьях: • 20 ловушек переноса Си++ - кода на 64-битную платформу [2]; • Безопасность 64-битного кода [3]; • Поиск ловушек в Си/Си++ коде при переносе приложений под 64-битную версию Windows [4]. Быстродействие кода, использующего типы ptrdiff_t и size_t Использование типов ptrdiff_t и size_t в адресной арифметике помимо повышения надежности кода может дать дополнительный выигрыш в производительности. Например, использование в качестве индекса типа int, размерность которого отличается от размерности указателя приводит к тому, что в двоичном коде будут присутствовать дополнительные команды преобразования данных. Речь идет о 64-битном коде, в котором размер указателей стал равен 64-битам, а размер типа int остался 32-битным. Показать короткий пример преимущества size_t над unsigned не простая задача. Чтобы быть объективным необходимо использовать оптимизирующие возможности компилятора. А два варианта оптимизированного кода часто становятся слишком непохожим, чтобы легко было
  • 5. продемонстрировать отличие. Попытка создать нечто близкое к простому примеру увенчалась успехом только с шестой попытки. И все равно пример не идеален, так как показывает не лишние преобразование типов данных, о которых сказано выше, а то, что компилятор смог построить более эффективный код при использовании типа size_t. Рассмотрим код программы, переставляющий элементы массива в обратном порядке: unsigned arraySize; ... for (unsigned i = 0; i < arraySize / 2; i++) { float value = array[i]; array[i] = array[arraySize - i - 1]; array[arraySize - i - 1] = value; } В примере переменные "arraySize" и "i" имеют тип unsigned. Этот тип легко можно заменить на тип size_t и сравнить небольшой участок ассемблерного кода, показанный на рисунке 1. Рисунок N1. Сравнение 64-битного ассемблерного кода при использовании типов unsigned и size_t Компилятор смог построить более лаконичный код, когда использовал 64-битные регистры. Автор не берется утверждать, что код, созданный при использовании типа unsigned (текст слева), будет работать медленнее, чем код с использованием size_t (текст слева). Сравнить скорость
  • 6. выполнения кода на современных процессорах крайне сложная задача. Но из примера видно, что когда компилятор работает с массивами, используя 64-битные типы, он может строить более короткий и быстрый код. По личному опыту автора, грамотная замена типов int и unsigned на ptrdiff_t и size_t может дать на 64-битной системе дополнительный прирост производительности до 10%. С одним из примеров увеличения скорости от использования типов ptrdiff_t и size_t можно познакомиться в четвертой главе статьи "Разработка ресурсоемких приложений в среде Visual C++" [5]. Рефакторинг кода с целью перехода на типы ptrdiff_t и size_t Как читатель уже убедился, использование типов ptrdiff_t и size_t имеет ряд преимуществ для 64- битных программ. Но и заменить, скажем все unsigned на size_t - не является выходом. Во-первых, это не гарантирует корректность программы на 64-битной системы. Во-вторых, скорее всего из-за такой замены возникнут новые ошибки, нарушится совместимость форматов данных и так далее. Не стоит забывать, что после такой замены существенно возрастет и объем потребляемой программой памяти. Причем увеличение объема требуемой памяти может замедлить работу приложения, так как в кэш будет помещаться меньше объектов, с которыми идет работа. Следовательно, внедрение в старый код типов ptrdiff_t и size_t является задачей постепенного рефакторинга, требующего большого количества времени. Фактически необходимо просмотреть весь код и внести необходимые исправления. Такой подход практически является слишком дорогостоящим и неэффективным. Можно предложить 2 варианта: 1. Использовать специализированные инструменты, такие как Viva64, входящий в состав PVS- Studio. Viva64 это статический анализатор кода, обнаруживающий места, где рационально изменить типы данных, чтобы программа была корректна и эффективно работала на 64- битных системах. Подробнее смотрите "Учебное пособие по PVS-Studio" [6]. 2. Если 32-битную программу не планируется адаптировать для 64-битных систем, то и нет смысла заниматься рефакторингом типов данных. 32-битная программа не получит никаких преимуществ от использования типов ptrdiff_t и size_t. Библиографический список 1. Андрей Карпов. 64-битный конь, который умеет считать. http://www.viva64.com/art-1-1- 1064884779.html 2. Андрей Карпов, Евгений Рыжков. 20 ловушек переноса Си++ - кода на 64-битную платформу. http://www.viva64.com/art-1-1-1958348565.html 3. Андрей Карпов. Безопасность 64-битного кода. http://www.viva64.com/art-1-1- 38488545.html 4. Андрей Карпов, Евгений Рыжков. Поиск ловушек в Си/Си++ коде при переносе приложений под 64-битную версию Windows. http://www.viva64.com/art-1-1- 329725213.html 5. Андрей Карпов, Евгений Рыжков. Разработка ресурсоемких приложений в среде Visual C++. http://www.viva64.com/art-1-1-640027853.html 6. Евгений Рыжков. Учебное пособие по PVS-Studio. http://www.viva64.com/art-4-1- 1796251700.html