﻿# Что такое size\_t и ptrdiff\_t

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

## Введение

Сразу заметим, что данные в статье определения и рекомендации относятся к наиболее распространённым на данный момент архитектурам \([IA\-32](https://pvs-studio.ru/ru/blog/terms/0060/), [Intel 64](https://pvs-studio.ru/ru/blog/terms/0022/), [IA\-64](https://pvs-studio.ru/ru/blog/terms/0017/)\) и могут быть неточны по отношению к экзотическим архитектурам\.

Типы _size\_t_ и _ptrdiff\_t_ были созданы для того, чтобы осуществлять корректную [адресную арифметику](https://pvs-studio.ru/ru/blog/terms/0005/)\. Долгое время было принято считать, что размер _int_ совпадает с размером машинного слова \(разрядностью микропроцессора\) и его можно использовать в качестве индексов для хранения размеров объектов или указателей\. Соответственно, адресная арифметика также строилась с использованием типов _int_ и _unsigned_\. Тип _int_ используется в большинстве обучающих материалов по программированию на C и C\+\+ в телах циклов и в качестве индексов\. Практически каноническим выглядит пример следующего вида:

```cpp
for (int i = 0; i < n; i++)
  a[i] = 0;
```

С развитием микропроцессоров и ростом их разрядности стало нерационально дальнейшее увеличение размерностей типа _int_\. Причин для этого много: экономия используемой памяти, максимальная совместимость и так далее\. В результате появилось несколько моделей данных, описывающих соотношение размеров базовых типов для языка C и C\+\+\. В таблице N1 приведены основные [модели данных](https://pvs-studio.ru/ru/blog/terms/0012/) и перечислены наиболее популярные системы, использующие их\.

![a0050_size_t_and_ptrdiff_t_ru/image2.png](https://import.viva64.com/docx/blog/a0050_size_t_and_ptrdiff_t_ru/image2.png)

_Таблица N1\. Модели данных \(data models\)_

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

```cpp
for (size_t i = 0; i < n; i++)
  a[i] = 0;
```

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

## Тип size\_t

[_size\_t_](https://pvs-studio.ru/ru/blog/terms/0044/) – это специальный беззнаковый целочисленный тип, определённый в стандартных библиотеках языков С и С\+\+\. Является типом результата, возвращаемого оператором [_sizeof_](https://timsong-cpp.github.io/cppwp/n4861/expr.sizeof#5)_ _и_ [alignof](https://timsong-cpp.github.io/cppwp/n4861/expr.alignof#2)_\.

Максимально допустимым значением типа _size\_t_ является константа _SIZE\_MAX_\.

Размер _size\_t_ выбирается таким образом, чтобы в него можно было записать максимальный размер теоретически возможного массива или объекта\. Другими словами, количество бит в _size\_t_ равно количеству бит, которое требуется для хранения максимального адреса в памяти машины\. Например, на 32\-битной системе _size\_t_ будет занимать 32\-бита, на 64\-битной — 64\-бита\. В свою очередь это означает, что в тип _size\_t_ может быть безопасно помещён указатель \(исключение составляют платформы с сегментной адресацией и указатели на функции\-члены классов\)\.

Данный тип перекладывает заботу о возможном разном поведении целочисленных переменных при смене платформы с плеч программиста на реализацию стандартной библиотеки\. Поэтому использование типа _size\_t_ безопаснее и эффективнее, чем использование обычных беззнаковых целочисленных типов:

1. Данный тип позволяет писать циклы и счётчики, не беспокоясь о возможном переполнении при смене платформы\. Например, когда количество необходимых итераций превышает _UINT\_MAX_;
1. Поскольку [стандартом](https://timsong-cpp.github.io/cppwp/n4861/support.types.layout#3) гарантируется, что _size\_t_ может вместить в себя размер максимально возможного объекта в системе, этот тип используют для хранения размеров объектов;
1. Из\-за этого же свойства _size\_t_ используют для индексации массивов\. В отличие от обычных базовых целочисленных типов, _size\_t_ гарантирует, что значение индекса не может быть больше _SIZE\_MAX_;
1. Так как в _size\_t_ обычно может быть безопасно помещён указатель, его используют для [адресной арифметики](https://pvs-studio.ru/ru/blog/terms/0005/)\. Однако для этих целей лучше подходит другой беззнаковый целочисленный тип [_uintptr\_t_](https://pvs-studio.ru/ru/blog/terms/0050/), само название которого отражает его предназначение;
1. Компилятор может построить более простой и, следовательно, более быстрый код, в котором будут отсутствовать лишние преобразования 32\-битных и 64\-битных данных\.

В языке С тип _size\_t_ объявлен в заголовочных файлах _<stddef\.h\>_, _<stdlib\.h\>_, _<string\.h\>_, _<wchar\.h\>_, _<uchar\.h\>_, _<time\.h\>_ и _<stdio\.h\>_\. В языке С\+\+ его декларация находится в файлах _<cstddef\>_, _<cstdlib\>_, _<cstring\>_, _<cwchar\>_, _<cuchar\>_, _<ctime\>_ и _<cstdio\>_\. Тип _size\_t_ размещён в глобальном пространстве имён и в _std_\. Стандартные заголовочные файлы языка C для обеспечения обратной совместимости также могут включаться в С\+\+ программы\.

Примечание\. Ещё существует тип [_rsize\_t_](https://pvs-studio.ru/ru/blog/terms/0095/)\. Он является синонимом _size\_t_\. Но он предназначен для хранения размера одиночного объекта\. Другими словами, используя _rsize\_t_, программист подчёркивает, что работает с размером одного\-единственного объекта\. При этом максимальный размер одиночного объекта задаётся константой _RSIZE\_MAX_\.

## Тип ptrdiff\_t

[_ptrdiff\_t_](https://pvs-studio.ru/ru/blog/terms/0041/) – это специальный знаковый целочисленный тип, определённый в стандартных библиотеках языков С и С\+\+\. Является типом результата [вычитания указателей](https://timsong-cpp.github.io/cppwp/n4861/expr.add#5)\. Поведение типа схоже с [_size\_t_](https://pvs-studio.ru/ru/blog/terms/0044/): на 32\-битной системе размер _ptrdiff\_t_ будет 32 бита, на 64\-битной – 64 бит\.

Также при работе с контейнерами стандартной библиотеки результат вычитания двух итераторов имеет тип _difference\_type_ используемого контейнера, который, в зависимости от стандартной библиотеки, часто равен _ptrdiff\_t_\.

Тип _ptrdiff\_t_ часто используется для [адресной арифметики](https://pvs-studio.ru/ru/blog/terms/0005/) и индексации массивов, если возможны отрицательные значения\. В программах, использующих для этого обычные целочисленные типы \(_int_\), может возникать [неопределённое поведение](https://pvs-studio.ru/ru/blog/terms/0066/)\. Например, если значение индекса превышает _INT\_MAX_\.

Для массивов меньших, чем _PTRDIFF\_MAX_, _ptrdiff\_t_ ведёт себя как аналог _size\_t_: может хранить размер массива любого типа и на большинстве платформ является синонимом [_intptr\_t_](https://pvs-studio.ru/ru/blog/terms/0023/)\. Однако если массив достаточно большой \(больше _PTRDIFF\_MAX_, но меньше _SIZE\_MAX_\) и разница его указателей не может быть представлена в виде _ptrdiff\_t_, то результат вычитания таких указателей [не определён](https://timsong-cpp.github.io/cppwp/n4861/expr.add#5.3)\.

В языке С тип _ptrdiff\_t_ объявлен в заголовочном файле _<stddef\.h\>_\. В языке С\+\+ его декларация находится в _<cstddef\> _и размещается в глобальном пространстве имён и в _std_\. Стандартные заголовочные файлы языка C для обеспечения обратной совместимости также могут включаться в С\+\+ программы\.

## Переносимость size\_t и ptrdiff\_t

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

Разработчики Linux приложений часто используют для этих целей тип _long_\. В рамках 32\-битных и 64\-битных моделей данных, принятых в Linux, это действительно работает\. Размер типа _long_ совпадает с размером указателя\. Но такой код несовместим с моделью данных Windows, и, соответственно, его нельзя считать хорошо переносимым\. В модели _LLP64_ \(Windows x64\) тип _long_ остался 32\-битным\. Более правильным решением будет использование типов _size\_t_ и _ptrdiff\_t_\.

Разработчики в экосистеме Windows в качестве альтернативы _size\_t_ и _ptrdiff\_t_ могут использовать типы _DWORD\_PTR_, _SIZE\_T,_ _SSIZE\_T_ и так далее\. Но желательно ограничиваться типами _size\_t_, _ptrdiff\_t_, _uintptr\_t_, _intptr\_t_ для большей совместимости\.

## Безопасность типов ptrdiff\_t и size\_t в адресной арифметике

Проблемы адресной арифметики стали активно проявлять себя с началом освоения 64\-битных систем\. Наибольшее число проблем при переносе 32\-битных приложений на 64\-битные системы связанно с использованием неподходящих для работы с указателями и массивами типов, таких как _int_ и _long_\. Этим проблемы переноса приложений на 64\-битные системы не ограничиваются, но большинство ошибок связаны именно с адресной арифметикой и работой с индексами\. Более подробно проблемы переноса кода раскрыты в [уроках по разработке 64\-битных приложений на языке C и C\+\+](https://pvs-studio.ru/ru/blog/lessons/) \[1\]\.

Рассмотрим простой пример:

```cpp
size_t n = ...;
for (int i = 0; i < n; i++)
  a[i] = 0;
```

Если мы работаем с массивом, состоящим более, чем из _INT\_MAX_ элементов, то данный код является некорректным\. При переполнении знаковой переменной возникает неопределённое поведение\. В отладочной \(debug\) версии программы скорее возникнет [Access Violation](https://pvs-studio.ru/ru/blog/terms/0063/), когда значение индекса переполнится\. А вот \(release\) версия в зависимости от настроек оптимизации и особенностей кода, может, например, неожиданно корректно заполнить все ячейки массива, создавая иллюзию корректной работы\! В результате в программе появляются плавающие ошибки, возникающие или пропадающие после малейшего изменения кода\. Подробнее о таких фантомных ошибках и их опасностях можно познакомиться в статье "[64\-битный конь, который умеет считать](https://pvs-studio.ru/ru/blog/posts/cpp/a0043/)" \[2\]\.

Пример еще одной дремлющей ошибки, которая проявит себя при определенном сочетании входных данных \(значении переменных _A_ и _B_\):

```cpp
int A = -2;
unsigned B = 1;
int array[5] = { 1, 2, 3, 4, 5 };
int *ptr = array + 3;
ptr = ptr + (A + B); // Error
printf("%i\n", *ptr);
```

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

* Переменная _A_ типа _int_ приводится к типу_ unsigned_;
* Происходит сложение _A_ и _B_\. В результате мы получаем значение 0xFFFFFFFF типа _unsigned_;
* Вычисляется выражение "ptr \+ 0xFFFFFFFF"\. Результат зависит от размерности указателя на данной платформе\. В 32\-битной программе выражение будет эквивалентно "ptr \- 1", и мы успешно распечатаем число 3\. В 64\-битной программе к указателю прибавится значение 0xFFFFFFFF, в результате чего указатель окажется далеко за пределами массива\.

Приведённые ошибки можно легко избежать, используя тип _size\_t_ или _ptrdiff\_t_\. В первом случае, если тип переменной _i_ будет _size\_t_, то переполнения не возникнет\. Во втором, если мы используем типы _size\_t_ или _ptrdiff\_t_ для переменных _A_ и _B_, то корректно распечатаем число "3"\.

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

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

* [20 ловушек переноса Си\+\+ \- кода на 64\-битную платформу](https://pvs-studio.ru/ru/blog/posts/cpp/a0004/) \[3\];
* [Безопасность 64\-битного кода](https://pvs-studio.ru/ru/blog/posts/cpp/a0046/) \[4\];
* [Поиск ловушек в Си/Си\+\+ коде при переносе приложений под 64\-битную версию Windows](https://pvs-studio.ru/ru/blog/posts/cpp/a0012/) \[5\];
* [Undefined behavior ближе, чем вы думаете](https://pvs-studio.ru/ru/blog/posts/cpp/0374/) \[6\]\.

## Быстродействие кода, использующего типы ptrdiff\_t и size\_t

Использование типов _ptrdiff\_t_ и _size\_t_ в адресной арифметике помимо повышения надёжности кода может дать дополнительный выигрыш в производительности\. Например, использование в качестве индекса типа _int_, размерность которого отличается от размерности указателя, приводит к тому, что в двоичном коде будут присутствовать дополнительные команды преобразования данных\. Речь идет о 64\-битном коде, в котором размер указателей стал равен 64 битам, а размер типа _int_ остался 32\-битным\.

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

```cpp
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\.

![a0050_size_t_and_ptrdiff_t_ru/image4.png](https://import.viva64.com/docx/blog/a0050_size_t_and_ptrdiff_t_ru/image4.png)

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

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

По личному опыту автора, грамотная замена типов _int_/_unsigned_ на _ptrdiff\_t_/_size\_t_ может дать на 64\-битной системе дополнительный прирост производительности до 10%\. С одним из примеров увеличения скорости от использования типов _ptrdiff\_t_ и _size\_t_ можно познакомиться в четвёртой главе статьи "[Разработка ресурсоемких приложений в среде Visual C\+\+](https://pvs-studio.ru/ru/blog/posts/a0018/)" \[7\]\.

## Рефакторинг кода с целью перехода на типы ptrdiff\_t и size\_t

Как читатель уже убедился, использование типов _ptrdiff\_t_ и _size\_t_ имеет ряд преимуществ для 64\-битных программ\. Но и заменить, скажем, все _unsigned_ на _size\_t_ не является выходом\. Во\-первых, это не гарантирует корректность программы на 64\-битной системе\. Во\-вторых, скорее всего, из\-за такой замены возникнут новые ошибки, нарушится совместимость форматов данных и так далее\. Не стоит забывать, что после такой замены может существенно возрасти и объем потребляемой программой памяти\. Причём увеличение объема требуемой памяти может замедлить работу приложения, так как в кэш будет помещаться меньше объектов, с которыми идет работа\.

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

1. Использовать специализированные инструменты, такие как [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/)\. Это статический анализатор кода, [обнаруживающий](https://pvs-studio.ru/ru/docs/warnings/#64CPP) места, где рационально изменить типы данных, чтобы программа была корректна и эффективно работала на 64\-битных системах\.
1. Если 32\-битную программу не планируется адаптировать для 64\-битных систем, то и нет смысла заниматься рефакторингом типов данных\. 32\-битная программа не получит никаких преимуществ от использования типов _ptrdiff\_t_ и _size\_t_\.

## Библиографический список

1. Андрей Карпов, Евгений Рыжков\. [Уроки разработки 64\-битных приложений на языке Си/Си\+\+](https://pvs-studio.ru/ru/blog/lessons/)\.
1. Андрей Карпов\. [64\-битный конь, который умеет считать](https://pvs-studio.ru/ru/blog/posts/cpp/a0043/)\.
1. Андрей Карпов, Евгений Рыжков\. [20 ловушек переноса Си\+\+ \- кода на 64\-битную платформу](https://pvs-studio.ru/ru/blog/posts/cpp/a0004/)\.
1. Андрей Карпов\. [Безопасность 64\-битного кода](https://pvs-studio.ru/ru/blog/posts/cpp/a0046/)\.
1. Андрей Карпов, Евгений Рыжков\. [Поиск ловушек в Си/Си\+\+ коде при переносе приложений под 64\-битную версию Windows](https://pvs-studio.ru/ru/blog/posts/cpp/a0012/)\.
1. Андрей Карпов\. [Undefined behavior ближе, чем вы думаете](https://pvs-studio.ru/ru/blog/posts/cpp/0374/)\.
1. Андрей Карпов, Евгений Рыжков\. [Разработка ресурсоемких приложений в среде Visual C\+\+](https://pvs-studio.ru/ru/blog/posts/a0018/)\.