Что такое size_t и ptrdiff_tАвтор: Андрей КарповДата: 21.09.2009АннотацияСтатья поможет читателю разобраться, что представ...
Таблица N1. Модели данных (data models)Как видно из таблицы, не так просто выбрать тип переменной для хранения указателя и...
указатель, за исключением указателя на функцию класса. Также ptrdiff_t является типомрезультата выражения, где один указат...
int A = -2;unsigned B = 1;int array[5] = { 1, 2, 3, 4, 5 };int *ptr = array + 3;ptr = ptr + (A + B); //Errorprintf("%in", ...
продемонстрировать отличие. Попытка создать нечто близкое к простому примеру увенчаласьуспехом только с шестой попытки. И ...
выполнения кода на современных процессорах крайне сложная задача. Но из примера видно, чтокогда компилятор работает с масс...
Upcoming SlideShare
Loading in …5
×

Что такое size_t и ptrdiff_t

704 views
572 views

Published on

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

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
704
On SlideShare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
3
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Что такое size_t и ptrdiff_t

  1. 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. 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. 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. 4. int A = -2;unsigned B = 1;int array[5] = { 1, 2, 3, 4, 5 };int *ptr = array + 3;ptr = ptr + (A + B); //Errorprintf("%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. 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. 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

×