Что такое size_t в C?


626

Я запутался size_tв C. Я знаю, что он возвращается sizeofоператором. Но что именно это? Это тип данных?

Допустим, у меня есть forцикл:

for(i = 0; i < some_size; i++)

Я должен использовать int i;или size_t i;?


11
Если это ваши единственные варианты, используйте intif some_sizeподписано, size_tесли оно не подписано.
Нат

8
@Nate Это неправильно. POSIX имеет тип ssize_t, но на самом деле правильный тип - ptrdiff_t.
Стивен Стюарт-Галлус

2
Ответы не так ясны, как в Низкоуровневом программировании: C, Сборка и Выполнение программы на Intel® 64 . Как указано в книге, использование индекса int iможет быть недостаточно для обращения к огромному массиву. Таким образом, используя size_t iвы можете обращаться к большему количеству индексов, так что даже если у вас огромный массив, это не должно быть проблемой. size_tТип данных: обычно, unsigned long intно это зависит от вашей системы.
Бруно

Ответы:


461

Из Википедии :

В соответствии со стандартом ISO C 1999 года (C99) size_tэто целочисленный тип без знака длиной не менее 16 бит (см. Разделы 7.17 и 7.18.3).

size_tтип данных без знака, определенный несколькими стандартами C / C ++, например, стандартом C99 ISO / IEC 9899, ​​который определен в stddef.h. 1 Он может быть дополнительно импортирован путем включения, stdlib.hпоскольку этот файл содержит внутреннюю подпрограмму stddef.h.

Этот тип используется для представления размера объекта. Библиотечные функции, которые принимают или возвращают размеры, ожидают, что они имеют тип или возвращаемый тип size_t. Кроме того, наиболее часто используемый оператор sizeof на основе компилятора должен принимать постоянное значение, совместимое с size_t.

Как следствие, size_tэто тип, который гарантированно содержит любой индекс массива.


4
«Библиотечные функции, которые принимают или возвращают размеры, ожидают, что они будут иметь тип ... size_t» За исключением того, что stat () использует off_t для размера файла
Draemon

64
@Draemon Этот комментарий отражает фундаментальную путаницу. size_tдля объектов в памяти. Стандарт C даже не определяет stat()или off_t(это определения POSIX) или что-либо связанное с дисками или файловыми системами - он останавливается на FILEпотоках. Управление виртуальной памятью полностью отличается от файловых систем и управления файлами в том, что касается требований к размеру, поэтому упоминание off_tздесь не имеет значения.
jw013

3
@ jw013: Я бы вряд ли назвал это фундаментальным заблуждением, но вы делаете интересное замечание. Тем не менее, в цитируемом тексте не говорится «размеры объектов в памяти», а «смещение» вряд ли является хорошим названием для типа размера независимо от того, где он хранится.
Draemon

30
@ Дракон Хороший вопрос. Этот ответ цитирует Википедию, которая, на мой взгляд, не имеет лучшего объяснения. Сам стандарт C гораздо более понятен: он определяет size_tкак тип результата sizeofоператора (7.17p2 о <stddef.h>). В разделе 6.5 объясняется, как именно работают выражения C (6.5.3.4 для sizeof). Поскольку вы не можете применить sizeofк файлу диска (в основном потому, что C даже не определяет, как работают диски и файлы), здесь нет места для путаницы. Другими словами, виновата Википедия (и этот ответ за цитирование Википедии, а не фактический стандарт Си).
jw013

2
@Draemon - я бы тоже согласился с оценкой "фундаментальной путаницы". Если вы не читали стандарты C / C ++, вы можете подумать, что «объект» относится к «объектно-ориентированному программированию», а это не так. Прочитайте стандарт C, в котором нет ни одного из этих объектов ООП, но все же есть объекты, и выясните. Ответ может вас удивить!
Хит Ханникутт

220

size_tэто тип без знака. Таким образом, он не может представлять отрицательные значения (<0). Вы используете это, когда вы рассчитываете что-то, и уверены, что это не может быть отрицательным. Например, strlen()возвращает a, 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.


8
@Alok: два вопроса: 1) какой размер size_t? 2) почему я предпочитаю size_tчто-то подобное unsigned int?
Лазер

2
@Lazer: размер size_tесть sizeof(size_t). Стандарт C гарантирует, что SIZE_MAXон будет не менее 65535. size_tЭто тип, возвращаемый sizeofоператором, и используется в стандартной библиотеке (например, strlenвозвращает size_t). Как сказал Брендан, size_tне обязательно быть таким же, как unsigned int.
Алок Сингхал

4
@Lazer - да, size_tэто гарантированный тип без знака.
Алок Сингхал

2
@Celeritas нет, я имею в виду, что беззнаковый тип может представлять только неотрицательные значения. Я, наверное, должен был сказать «Это не может представлять отрицательные значения».
Алок Сингхал,

4
@JasonOster, дополнение до двух не является обязательным требованием в стандарте C. Если значение s2 - s1переполняется int, поведение не определено.
Алок Сингхал

73

size_t тип, который может содержать любой индекс массива

В зависимости от реализации это может быть любой из:

unsigned char

unsigned short

unsigned int

unsigned long

unsigned long long

Вот как size_tэто определено в stddef.hмоей машине:

typedef unsigned long size_t;

4
Конечно, typedef unsigned long size_tэто зависит от компилятора. Или ты предлагаешь это всегда так?
chux - Восстановить Монику

4
@chux: Действительно, только потому, что одна реализация определяет это как таковое, не означает, что все делают Показательный пример: 64-битная Windows. unsigned long32-битный, size_t64-битный.
Тим Час

2
какова цель size_t? Когда я могу создать переменную для себя, как: "int mysize_t;" или "long mysize_t" или "unsigned long mysize_t". Почему кто-то должен был создать эту переменную для меня?
Мидкин

1
@midkin size_tне является переменной. Это тип, который вы можете использовать, когда хотите представить размер объекта в памяти.
Арджун Шридхаран

1
правда ли, что size_tна 32-битной машине всегда 32 бита, так же 64 бита?
Джон Ву

70

Если вы эмпирический тип ,

echo | gcc -E -xc -include 'stddef.h' - | grep size_t

Выход для Ubuntu 14.04 64-битный 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?


1
У меня такая же среда, однако я протестировал ее на 32 бита, передав опцию "-m32" в GCC, результат был: "typedef unsigned int size_t". Спасибо, что поделились этой замечательной командой @Ciro, она мне очень помогла! :-)
silvioprog

2
Сам вопрос не сбивает с толку. Это сбивающий с толку ум пытается задать много вопросов и дать много ответов. Я удивлен, что этот ответ и ответ Арджуна Шридхарана до сих пор не мешают людям спрашивать и отвечать.
biocyberman

1
Отличный ответ, потому что он на самом деле говорит вам, что size_tесть , по крайней мере, в популярном дистрибутиве Linux.
Андрей Портной


19

Поскольку никто еще не упомянул об этом, основное лингвистическое значение в size_tтом, что sizeofоператор возвращает значение этого типа. Аналогично, основное значение этого ptrdiff_tсостоит в том, что вычитание одного указателя из другого приведет к значению этого типа. Библиотечные функции, которые принимают его, делают это, потому что это позволит таким функциям работать с объектами, размер которых превышает UINT_MAX в системах, где такие объекты могут существовать, не вынуждая вызывающих абонентов тратить код, передавая значение, большее, чем «unsigned int», в системах, где больший тип будет достаточно для всех возможных объектов.


Мой вопрос всегда был: если sizeof никогда не существовал, была бы потребность в size_t?
Дин Р

@DeanP: Возможно, нет, хотя тогда возникнет вопрос о том, какой тип аргумента следует использовать для подобных вещей malloc(). Лично я хотел бы видеть версии, которые принимают аргументы типа int, longи long long, с некоторыми реализациями, продвигающими более короткие типы, а другие реализующими, например, lmalloc(long n) {return (n < 0 || n > 32767) ? 0 : imalloc(n);}[на некоторых платформах, вызов imalloc(123)будет дешевле, чем вызов lmalloc(123);, и даже на платформе, где size_t16 биты, код, который хочет выделить размер, вычисляемый по значению `long` ...
суперкат

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

11

Чтобы понять, почему 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. К сожалению, во многих других устаревших кодах также предполагается, что a longдостаточно широк, чтобы содержать указатель, смещение файла, количество секунд, прошедших с 1970 года, и так далее. POSIX теперь предоставляет стандартизированный способ заставить последнее предположение быть верным вместо первого, но ни одно из них не является переносимым.

Этого не может быть, intпотому что лишь крошечная горстка компиляторов в 90-х имела intширину 64 бита. Тогда они действительно стали странными, держа long32 бита в ширину. Следующая редакция Стандарта объявила его незаконным, поскольку intон шире long, но intпо-прежнему имеет ширину 32 бита в большинстве 64-битных систем.

Этого не может быть long long int, что в любом случае было добавлено позже, поскольку он был создан, чтобы иметь ширину не менее 64 бит даже в 32-битных системах.

Итак, новый тип был необходим. Даже если это не так, все эти другие типы означают нечто иное, чем смещение в массиве или объекте. И если бы был один урок из фиаско 32-битной миграции, то нужно было конкретно указать, какие свойства должен иметь тип, и не использовать тот, который имел разные значения в разных программах.


Не согласны с « size_tи ptrdiff_tгарантированно будут иметь ширину 64 бита в 64-битной реализации» и т. Д. Гарантия завышена. Диапазон в size_tпервую очередь определяется объемом памяти реализации. «n-битная реализация» - это, прежде всего, собственная ширина процессора целых чисел. Конечно, во многих реализациях используется память одинакового размера и ширина шины процессора, но существуют широкие собственные целые числа со скудной памятью или узкие процессоры с большим объемом памяти, и они разделяют эти два свойства реализации.
chux - Восстановить Монику

8

size_tи intне являются взаимозаменяемыми. Например, в 64-битном Linux size_t- это 64-битный (т.е. sizeof(void*)), но int32-битный.

Также обратите внимание, что size_tэто без знака. Если вам нужна подписанная версия, то есть ssize_tна некоторых платформах, и это будет более подходящим для вашего примера.

Как общее правило, я бы предложил использовать intдля большинства общих случаев и использовать size_t/ только ssize_tкогда есть особая потребность в этом ( mmap()например, с).


3

В общем, если вы начинаете с 0 и идете вверх, всегда используйте тип без знака, чтобы избежать переполнения, приводящего к ситуации с отрицательным значением. Это критически важно, потому что если границы вашего массива оказываются меньше, чем максимум вашего цикла, но максимум вашего цикла оказывается больше, чем максимум вашего типа, вы обернетесь вокруг отрицательного значения и у вас может возникнуть ошибка сегментации (SIGSEGV ). Поэтому, вообще, никогда не используйте int для цикла, начинающегося с 0 и идущего вверх. Используйте без знака.


3
Я не могу принять вашу аргументацию. Вы говорите, что лучше, если ошибка переполнения молча приводит к доступу к действительным данным в вашем массиве?
maf-soft

1
@ maf-soft - это правильно. если ошибка остается незамеченной, это делает ее хуже, чем сбой программы. почему этот ответ получил отклик?
yoyo_fun

Если он обращается к действительным данным в вашем массиве, то это не ошибка, потому что тип без знака не будет переполнен при ограничении типа со знаком. Что это за логика, ребята? Допустим, по какой-то причине вы используете char для итерации по массиву из 256 элементов ... sign будет переполнен на 127, а 128-й элемент будет sigsegv, но если вы используете unsigned, он будет проходить весь массив, как и предполагалось. Опять же, когда вы используете int, ваши массивы на самом деле не будут больше, чем 2 миллиарда элементов, так что в любом случае это не имеет значения ...
Purple Ice,

1
Я не могу представить себе ситуацию, в которой целочисленное переполнение не является ошибкой, независимо от того, является ли оно положительным или отрицательным. Тот факт, что вы не получаете сегфо, не означает, что вы видите правильное поведение! И вы можете испытать ошибку сегментации, или нет, независимо от того, положительное или отрицательное смещение; все зависит от вашей памяти. @PurpleIce, я не думаю, что вы говорите то же самое, что и этот ответ; ваш аргумент выглядит так, что вы должны выбрать тип данных, достаточно большой, чтобы в нем содержалось наибольшее значение, которое вы хотите поместить в него, что является простым здравым смыслом.
Сорен Бьорнстад

Тем не менее, я предпочитаю использовать беззнаковый тип для циклических индексов семантически ; если ваша переменная никогда не будет отрицательной, то вы также можете указать это в выбранном вами типе. Это также может позволить компилятору обнаружить ошибку, в которой значение оказалось отрицательным, хотя GCC, по крайней мере, довольно ужасно обнаруживает эту конкретную ошибку (однажды я инициализировал unsigned в -1 и не получил предупреждение). Точно так же size_t семантически подходит для индексов массива.
Сорен Бьорнстад

3

size_t - целочисленный тип данных без знака. В системах, использующих библиотеку GNU C, это будет unsigned int или unsigned long int. size_t обычно используется для индексации массива и подсчета циклов.


1

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

1

size_tцелочисленный тип данных без знака, который может назначать только 0 и больше 0 целочисленных значений. Он измеряет байты любого размера объекта и возвращается sizeofоператором. constявляется синтаксическим представлением size_t, но без constвас можно запустить программу.

const size_t number;

size_tрегулярно используется для индексации массивов и подсчета циклов. Если компилятор 32-bitэто будет работать unsigned int. Если компилятор есть, 64-bitон также будет работать unsigned long long int. Там для максимального размера в size_tзависимости от типа компилятора.

size_tуже определяют в <stdio.h>файле заголовка, но он может также определить с помощью <stddef.h>, <stdlib.h>, <string.h>, <time.h>, <wchar.h>заголовки.

  • Пример (с const)
#include <stdio.h>

int main()
{
    const size_t value = 200;
    size_t i;
    int arr[value];

    for (i = 0 ; i < value ; ++i)
    {
        arr[i] = i;
    }

    size_t size = sizeof(arr);
    printf("size = %zu\n", size);
}

Вывод -: size = 800


  • Пример (без const)
#include <stdio.h>

int main()
{
    size_t value = 200;
    size_t i;
    int arr[value];

    for (i = 0 ; i < value ; ++i)
    {
        arr[i] = i;
    }

    size_t size = sizeof(arr);
    printf("size = %zu\n", size);
}

Вывод -: size = 800


-3

Насколько я понимаю, size_tэто unsignedцелое число, размер бит которого достаточно велик, чтобы содержать указатель на нативную архитектуру.

Так:

sizeof(size_t) >= sizeof(void*)

16
Не правда. Размер указателя может быть больше, чем size_t. Несколько примеров: компиляторы C в реальном режиме x86 могут иметь 32-битные FARили HUGEуказатели, но size_t по-прежнему составляет 16 бит. Другой пример: у Watcom C был специальный толстый указатель для расширенной памяти, который был шириной 48 бит, но size_tего не было. На встроенном контроллере с архитектурой Гарварда у вас также нет корреляции, потому что оба относятся к разным адресным пространствам.
Патрик Шлютер

1
И на этом stackoverflow.com/questions/1572099/… есть еще примеры AS / 400 с 128-битными указателями и 32-битнымиsize_t
Патрик Шлютер

Это явно ложно. Тем не менее, давайте держать это здесь
Антти Хаапала
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.