Что такое size_t и ptrdiff_t
Upcoming SlideShare
Loading in...5
×
 

Что такое size_t и ptrdiff_t

on

  • 672 views

Статья поможет читателю разобраться, что представляют собой типы size_t и ptrdiff_t, для чего они нужны и когда ...

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

Statistics

Views

Total Views
672
Views on SlideShare
672
Embed Views
0

Actions

Likes
0
Downloads
2
Comments
0

0 Embeds 0

No embeds

Accessibility

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Что такое size_t и ptrdiff_t Что такое size_t и ptrdiff_t Document Transcript

  • Что такое 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); //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 не простая задача. Чтобы бытьобъективным необходимо использовать оптимизирующие возможности компилятора. А дваварианта оптимизированного кода часто становятся слишком непохожим, чтобы легко было
  • продемонстрировать отличие. Попытка создать нечто близкое к простому примеру увенчаласьуспехом только с шестой попытки. И все равно пример не идеален, так как показывает не лишниепреобразование типов данных, о которых сказано выше, а то, что компилятор смог построитьболее эффективный код при использовании типа 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