Що таке розмір_t в С?


626

Я плутаюсь size_tв C. Я знаю, що він повертається sizeofоператором. Але що це саме? Це тип даних?

Скажімо, у мене є forцикл:

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

Чи варто використовувати int i;або size_t i;?


11
Якщо це лише ваші варіанти, використовуйте, intякщо 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. Крім того, розмір оператора на основі компілятора, який найчастіше використовується, повинен оцінювати до постійного значення, сумісного з 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
@Draemon Добре. Ця відповідь цитує Вікіпедію, яка в цьому випадку не найкраще пояснює, на мій погляд. Сам стандарт C набагато зрозуміліший: він визначає size_tяк тип результату sizeofоператора (7.17p2 про <stddef.h>). Розділ 6.5 пояснює, як працюють вирази C (6.5.3.4 для sizeof). Оскільки ви не можете застосувати sizeofфайл диска (здебільшого через те, що C навіть не визначає, як працюють диски та файли), немає місця для плутанини. Іншими словами, звинувачуйте Вікіпедію (і це відповідь за цитування Вікіпедії, а не власне стандарту С).
jw013

2
@Draemon - Я також погодився б з оцінкою "фундаментальної плутанини". Якщо ви ще не читали стандартів C / C ++, ви можете подумати, що "об'єкт" відноситься до "об'єктно-орієнтованого програмування", чого він не робить. Прочитайте стандарт C, який не має жодного з цих об'єктів OOP, але ще не має об’єктів, і з’ясуйте це. Відповідь може вас здивувати!
Хіт Ханнікутт

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 - s1overflows an 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". Чому хтось повинен створити цю змінну для мене?
midkin

1
@midkin size_tне є змінною. Це тип, який можна використовувати, коли ви хочете представити розмір об'єкта в пам'яті.
Арджун Средхаран

1
це правда, що size_tна 32-бітній машині завжди 32 біти, 64 біт так само?
Джон Ву

70

Якщо ви емпіричний тип ,

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

Вихід для 64-розрядного GCC 4.8 для Ubuntu 14.04:

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 біти, передаючи опцію GCC "-m32", результат був: "typedef unsigned int size_t". Дякуємо, що поділилися цією дивовижною командою @Ciro, вона мені дуже допомогла! :-)
silvioprog

2
Справа сама по собі не бентежить. Саме заплутаний розум намагається задати багато питань і дати багато відповідей. Я здивований, що ця відповідь і відповідь Арджуна Средхарана все ще не заважає людям запитувати і відповідати.
biocyberman

1
Чудова відповідь, адже він насправді говорить вам про те, що size_tє , принаймні, у популярному дистрибутиві Linux.
Андрій


19

Оскільки про це ніхто ще не згадував, головне мовне значення size_tполягає в тому, що sizeofоператор повертає значення цього типу. Точно так само головне значення ptrdiff_tполягає в тому, що віднімання одного вказівника від іншого дасть значення цього типу. Функції бібліотеки, які приймають це, роблять це, тому що вони дозволять таким функціям працювати з об'єктами, розмір яких перевищує UINT_MAX в системах, де такі об'єкти могли існувати, не примушуючи абонентів витрачати код, передаючи значення, більше, ніж "непідписаний int" у системах, де тип більшого типу вистачило б на всі можливі об’єкти.


Моє питання завжди було: Якщо розмір ніколи не існував, чи була б потреба у розмірі_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` значення ...
Supercat

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

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-х зробила int64 біти ширшими. Тоді вони дійсно стали дивними, тримаючи long32 біти в ширину. Наступна редакція Стандарту визнала його незаконним для intширшого, ніж 32-бітну ширину long, але intв більшості 64-бітних систем.

Це не могло бути long long int, що все одно було додано пізніше, оскільки це було створено для ширини принаймні 64 біт навіть у 32-бітних системах.

Отже, потрібен був новий тип. Навіть якщо б це не було, всі ці інші типи означали щось інше, ніж зсув у масиві чи об’єкті. І якщо був би один урок з фіаско 32-до-64-бітової міграції, то слід було б конкретно визначити, які властивості потрібно мати типу, а не використовувати той, який означав різні речі в різних програмах.


Не погоджуйтесь із " 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 елементів ... підписаний переповнюється на 127, а 128-й елемент буде sigsegv, але якщо ви не підписуєтеся, то він пройде через весь масив за призначенням. Потім знову, коли ви використовуєте int, ваші масиви насправді не перевищуватимуть 2 мільярди елементів, так чи інакше це не має значення ...
Purple Ice

1
Я не можу уявити жодну ситуацію, в якій ціле число переповнення не є помилкою, незалежно від того, чи є вона позитивною чи негативною. Тільки тому, що ви не отримуєте segfault, не означає, що ви бачите правильну поведінку! І ви можете зазнати помилки сегментації, чи ні, чи є ваш залік позитивним чи негативним; все залежить від вашого макета пам’яті. @PurpleIce, я не думаю, що ти говориш те саме, що ця відповідь; ваш аргумент виглядає так, що вам слід вибрати тип даних, достатньо великий, щоб утримувати найбільше значення, яке ви хочете вкласти в нього, що є просто здоровим глуздом.
Сорен Бьорнстад

Однак, я вважаю за краще використовувати семантично непідписаний тип для циклів ; якщо ваша змінна ніколи не буде негативною, ви можете також вказати це в обраному вами типі. Це також може дозволити компілятору виявити помилку, у якій значення в кінцевому підсумку було негативним, хоча GCC принаймні є досить жахливим при виявленні цієї конкретної помилки (одного разу я ініціалізував неподписаний на -1 і не отримав попередження). Аналогічно, size_t є семантично відповідним для індексів масиву.
Сорен Бьорнстад

3

size_t - непідписаний цілочисельний тип даних. У системах, що використовують бібліотеку GNU C, це буде без підпису 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покажчики, але розмір_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.