Я запутался с size_t
в C. Я знаю, что он возвращается оператором sizeof
. Но что это такое? Это тип данных?
Скажем, у меня есть цикл for
:
for(i = 0; i < some_size; i++)
Должен ли я использовать int i;
или size_t i;
?
Я запутался с size_t
в C. Я знаю, что он возвращается оператором sizeof
. Но что это такое? Это тип данных?
Скажем, у меня есть цикл for
:
for(i = 0; i < some_size; i++)
Должен ли я использовать int i;
или size_t i;
?
Согласно стандарту ISO ISO 1999 (C99),
size_t
- целое число без знака тип не менее 16 бит (см. разделы 7.17 и 7.18.3).
size_t
- неподписанный тип данных определенных несколькими стандартами C/С++, например стандарт C99 ISO/IEC 9899, который определяется вstddef.h
. 1 Он может быть дополнительно импортированы путем включенияstdlib.h
, так как этот внутренний файл включаетstddef.h
.Этот тип используется для представления размер объекта. Функции библиотеки которые берут или возвращают размеры, ожидают их быть типом или иметь тип возврата
size_t
. Кроме того, наиболее часто используемые компиляторы оператор sizeof должен оценивать постоянное значение, совместимое сsize_t
.
В качестве импликации size_t
является типом, гарантирующим, что будет содержать любой индекс массива.
size_t
является неподписанным типом. Таким образом, он не может представлять никаких отрицательных значений (<0). Вы используете его, когда считаете что-то, и уверены, что он не может быть отрицательным. Например, strlen()
возвращает size_t
потому что длина строки должна быть не менее 0.
В вашем примере, если ваш индекс цикла будет всегда больше 0, может иметь смысл использовать size_t
или любой другой неподписанный тип данных.
Когда вы используете объект size_t
, вы должны убедиться, что во всех контекстах, которые он использует, включая арифметику, вы хотите получить неотрицательные значения. Например, скажем, у вас есть:
size_t s1 = strlen(str1);
size_t s2 = strlen(str2);
и вы хотите найти разницу в длинах str2
и str1
. Вы не можете:
int diff = s2 - s1; /* bad */
Это связано с тем, что значение, присвоенное diff
, всегда будет положительным числом, даже если s2 < s1
, потому что вычисление выполняется с неподписанными типами. В этом случае, в зависимости от вашего варианта использования, вам может быть лучше использовать int
(или long long
) для s1
и s2
.
В C/POSIX есть некоторые функции, которые могут/должны использовать size_t
, но не из-за исторических причин. Например, второй параметр для fgets
идеале должен быть size_t
, но является int
.
size_t
- это тип, который может содержать любой индекс массива.
В зависимости от реализации это может быть любое из:
unsigned char
unsigned short
unsigned int
unsigned long
unsigned long long
Здесь size_t
определяется в stddef.h
моей машины:
typedef unsigned long size_t;
Если вы эмпирический тип,
echo | gcc -E -xc -include 'stddef.h' - | grep size_t
Выход для Ubuntu 14.04 64-bit GCC 4.8:
typedef long unsigned int size_t;
Обратите внимание, что stddef.h
предоставляется GCC, а не glibc в src/gcc/ginclude/stddef.h
в GCC 4.2.
Интересные появления C99
malloc
принимает size_t
в качестве аргумента, поэтому он определяет максимальный размер, который может быть выделен.
И так как он также возвращается sizeof
, я думаю, что он ограничивает максимальный размер любого массива.
Смотрите также: Каков максимальный размер массива в C?
В manpage для types.h говорится:
size_t должен быть целым числом без знака
Поскольку никто еще не упомянул об этом, основным лингвистическим значением size_t
является то, что оператор sizeof
возвращает значение этого типа. Аналогично, основным значением ptrdiff_t
является то, что вычитание одного указателя из другого даст значение этого типа. Функции библиотеки, которые его принимают, делают это, потому что это позволит таким функциям работать с объектами, размер которых превышает UINT_MAX в системах, где такие объекты могут существовать, не заставляя вызывающих абонентов тратить код, передавая значение, большее, чем "unsigned int" в системах, где больший тип будет достаточным для всех возможных объектов.
size_t
и int
не являются взаимозаменяемыми. Например, в 64-разрядной версии Linux size_t
имеется 64-разрядный размер (т.е. sizeof(void*)
), но int
- 32-разрядный.
Также обратите внимание, что size_t
не имеет знака. Если вам нужна подписанная версия, на некоторых платформах есть ssize_t
, и это будет более актуально для вашего примера.
Как правило, я бы предложил использовать int
для большинства общих случаев и использовать только size_t
/ssize_t
, когда есть определенная потребность в нем (например, mmap()
).
Чтобы понять, почему size_t
должен существовать и как мы сюда попали:
С практической точки зрения, size_t
и ptrdiff_t
гарантированно имеют ширину 64 бита в 64-битной реализации, 32 бита в 32-битной реализации и так далее. Они не могли заставить любой существующий тип означать это на каждом компиляторе, не нарушая устаревший код.
size_t
или ptrdiff_t
не обязательно совпадают с intptr_t
или uintptr_t
. Они отличались в некоторых архитектурах, которые все еще использовались, когда size_t
и ptrdiff_t
были добавлены в стандарт в конце 80-х, и устарели, когда C99 добавил много новых типов, но еще не вышел (например, 16-битная Windows). Сервер x86 в 16-разрядном защищенном режиме имел сегментированную память, в которой максимальный размер массива или структуры мог составлять всего 65 536 байт, но far
указатель должен был иметь ширину 32 бита и шире регистров. Для них intptr_t
должен был бы иметь ширину 32 бита, но size_t
и ptrdiff_t
могут иметь ширину 16 бит и помещаться в регистр. И кто знал, какая операционная система может быть написана в будущем? Теоретически, архитектура i386 предлагает 32-битную модель сегментации с 48-битными указателями, которую никогда не использовала ни одна операционная система.
Тип смещения памяти не может быть long
потому что слишком большой унаследованный код предполагает, что long
составляет ровно 32 бита. Это предположение было даже встроено в API-интерфейсы UNIX и Windows. К сожалению, во многих других устаревших кодах также предполагалось, что long
достаточно широкий, чтобы содержать указатель, смещение файла, количество секунд, прошедших с 1970 года, и так далее. POSIX теперь предоставляет стандартизированный способ заставить последнее предположение быть верным вместо первого, но ни одно из них не является переносимым.
Это не может быть int
потому что только крошечная горстка компиляторов в 90-х int
64-битной ширины. Тогда они действительно стали странными, держа long
32 бита. Следующая редакция Стандарта объявила незаконным, чтобы int
был шире, чем long
, но int
по-прежнему имеет ширину 32 бита в большинстве 64-битных систем.
Это не может быть long long int
, который в любом случае был добавлен позже, поскольку он был создан, чтобы иметь ширину не менее 64 бит даже в 32-битных системах.
Итак, новый тип был необходим. Даже если это не так, все эти другие типы означают нечто иное, чем смещение в массиве или объекте. И если бы был один урок из фиаско 32-битной миграции, то нужно было конкретно указать, какие свойства должен иметь тип, и не использовать тот, который имел разные значения в разных программах.
В общем, если вы начинаете с 0 и поднимаетесь вверх, всегда используйте неподписанный тип, чтобы избежать переполнения, что привело вас к ситуации с отрицательным значением. Это критически важно, потому что, если границы вашего массива будут меньше, чем максимальный ваш цикл, но ваш цикл max окажется больше максимального вашего типа, вы обернете негатив, и вы можете столкнуться с ошибка сегментации (SIGSEGV). Поэтому, вообще говоря, никогда не используйте int для цикла, начинающегося с 0 и идущего вверх. Используйте unsigned.
size_t - целочисленный тип данных без знака. В системах, использующих библиотеку GNU C, это будет unsigned int или unsigned long int. size_t обычно используется для индексирования массива и подсчета циклов.
size_t или любой неподписанный тип можно рассматривать как переменную цикла, поскольку переменные цикла обычно больше или равны 0.
Когда мы используем объект size_t, мы должны убедиться, что во всех контекстах, которые он использует, включая арифметику, мы хотим только неотрицательные значения. Например, следующая программа определенно даст неожиданный результат:
// C program to demonstrate that size_t or
// any unsigned int type should be used
// carefully when used in a loop
#include<stdio.h>
int main()
{
const size_t N = 10;
int a[N];
// This is fine
for (size_t n = 0; n < N; ++n)
a[n] = n;
// But reverse cycles are tricky for unsigned
// types as can lead to infinite loop
for (size_t n = N-1; n >= 0; --n)
printf("%d ", a[n]);
}
Output
Infinite loop and then segmentation fault
Псевдоним одного из фундаментальных целочисленных типов без знака.
Это тип, который может представлять размер любого объекта в байтах: size_t - это тип, возвращаемый оператором sizeof, и широко используется в стандартной библиотеке для представления размеров и подсчетов.
С моей точки зрения, size_t
- это целое число unsigned
, размер бит которого достаточно велик, чтобы удерживать указатель собственной архитектуры.
Итак:
sizeof(size_t) >= sizeof(void*)