size_t або int для розмірів, індексу тощо


15

У C ++ size_t(або правильніше, T::size_typeщо є "зазвичай" size_t; тобто unsignedтип) використовується як повернене значення size(), аргумент до operator[]тощо (див std::vector. Та ін.)

З іншого боку, мови .NET використовують int(і, необов'язково, long) для тих же цілей; насправді мови, сумісні з CLS, не потрібні для підтримки неподписаних типів .

Зважаючи на те, що .NET є більш новим, ніж C ++, щось говорить про те, що можуть виникнути проблеми з використанням unsigned intнавіть для речей, які "не можуть" бути негативними, як індекс масиву чи довжина. Чи підхід C ++ "історичний артефакт" для зворотної сумісності? Або існують реальні та значні компроміси між двома підходами?

Чому це має значення? Ну ... що я повинен використовувати для нового багатовимірного класу в C ++; size_tабо int?

struct Foo final // e.g., image, matrix, etc.
{
    typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
    typedef size_t size_type; // c.f., std::vector<>

    dimension_type bar_; // maybe rows, or x
    dimension_type baz_; // e.g., columns, or y

    size_type size() const { ... } // STL-like interface
};

6
Варто зауважити: в декількох місцях .NET Framework -1повертається з функцій, які повертають індекс, щоб вказати "не знайдено" або "поза діапазоном". Він також повертається з Compare()функцій (реалізуючи IComparable). 32-бітний інт вважається переходом до типу для загального числа, тому, що я сподіваюся, є очевидними причинами.
Роберт Харві

Відповіді:


9

Зважаючи на те, що .NET є більш новим, ніж C ++, щось підказує мені, що можуть виникнути проблеми з використанням непідписаного int навіть для речей, які "не можуть" бути негативними, як індекс масиву чи довжина.

Так. Для певних типів програм, таких як обробка зображень або обробка масивів, часто потрібен доступ до елементів відносно поточного положення:

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

У цих типах додатків неможливо виконати перевірку діапазону з непідписаними цілими числами, не продумавши ретельно:

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

Натомість вам доведеться переставити вираз перевірки діапазону. У цьому головна відмінність. Програмісти також повинні пам’ятати цілі правила перетворення. Якщо ви сумніваєтесь, перечитайте http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions

Багатьом додаткам не потрібно використовувати дуже великі індекси масиву, але вони повинні виконувати перевірку діапазону. Крім того, багато програмістів не навчаються виконувати цю гімнастику перестановки виразів. Єдина пропущена можливість відкриває двері до подвигу.

C # дійсно розроблений для тих програм, яким не потрібно більше 2 ^ 31 елемента на масив. Наприклад, додатку для електронних таблиць не потрібно мати справу з такою кількістю рядків, стовпців або комірок. C # має справу з верхньою межею, маючи необов'язково перевірену арифметику, яку можна включити для блоку коду з ключовим словом, не псуючи параметри компілятора. З цієї причини C # підтримує використання підписаного цілого числа. Коли ці рішення розглядаються взагалі, це має сенс.

C ++ просто інший і важче отримати правильний код.

Що стосується практичної важливості дозволу підписаної арифметики усунути потенційне порушення "принципу найменшого здивування", то справа в тому, є OpenCV, який використовує підписане 32-бітове ціле число для індексу матричного елемента, розміру масиву, кількості каналів пікселів тощо. обробка - приклад домену програмування, який активно використовує відносний індекс масиву. Непідписаний цілий цілий підпис (негативний результат, обернений навколо) суттєво ускладнить реалізацію алгоритму.


Це саме моя ситуація; дякую за конкретні приклади. (Так, я знаю це, але це може бути корисно, щоб цитувати "вищі органи влади".)
1616

1
@Dan: якщо вам потрібно щось цитувати, це повідомлення було б краще.
rwong

1
@Dan: Джон Реджер активно досліджує це питання на мовах програмування. Дивіться blog.regehr.org/archives/1401
rwong

Існують суперечливі думки: gustedt.wordpress.com/2013/07/15/…
rwong

14

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

size_t - цілий розмір з метою:

Тип size_t- це визначений реалізацією цілочисельний тип без підпису, який достатньо великий, щоб містити розмір у байтах будь-якого об'єкта. (Специфікація C ++ 11 18.2.6)

Таким чином, будь-коли, коли ви хочете працювати з розміром об'єктів у байтах, ви повинні використовувати size_t. Зараз у багатьох випадках ви не використовуєте ці параметри / індекси для підрахунку байтів, але більшість розробників вирішують використовувати size_tїх для послідовності.

Зауважте, що ви завжди повинні використовувати, size_tякщо ваш клас призначений мати вигляд класу STL. Усі STL-класи в специфікації використовують size_t. Це справедливо для компілятора ЬурейеЕ size_tбути unsigned int, і це справедливо і для того , щоб бути typedefed до unsigned long. Якщо ви користуєтесь intчи longбезпосередньо, ви з часом натрапите на компілятори, де людина, яка вважає, що ваш клас слідувала стилю STL, потрапляє в пастку, оскільки ви не дотримувались стандарту.

Щодо використання підписаних типів, є кілька переваг:

  • Коротші назви - люди насправді легко набирати int, але набагато складніше захаращувати код unsigned int.
  • Одне ціле число для кожного розміру - Існує лише одне ціле число, сумісне з CLS, з 32 біт, яке є Int32. У C ++ є два ( int32_tі uint32_t). Це може спростити сумісність API

Великий недолік підписаних типів очевидний: ви втрачаєте половину свого домену. Підписане число не може вважатися таким же високим, як безпідписане число. Коли C / C ++ з'явився, це було дуже важливо. Потрібно мати можливість вирішувати всі можливості процесора, і для цього вам потрібно було використовувати безпідписані номери.

Для типів програм, на які орієнтовано .NET, не було настільки сильної потреби в індексі непідписаного повного домену. Багато з цілей таких номерів просто недійсні в керованій мові (пам'ятається об'єднання пам'яті). Крім того, як .NET вийшов, 64-бітні комп'ютери очевидно були майбутнім. Ми далекі від необхідності повного діапазону 64-бітового цілого числа, тому жертвувати одним бітом не так боляче, як це було раніше. Якщо вам дійсно потрібно 4 мільярди індексів, ви просто переходите до використання 64-бітових цілих чисел. У гіршому випадку, ви запускаєте його на 32-бітній машині, і це трохи повільно.

Я розглядаю торгівлю як одну із зручностей. Якщо у вас є достатня обчислювальна потужність, яку ви не заперечуєте витрачати частину свого типу індексу, яку ви ніколи не використовуєте, то зручно просто набрати intабо longпіти від неї. Якщо ви виявили, що справді хотіли цього останнього шматочка, то, напевно, вам варто було б звернути увагу на підпис ваших номерів.


скажімо, реалізація size()була return bar_ * baz_;; Хіба це не створить потенційну проблему з цілим переповненням (обертання), якого б у мене не було, якби я не використовував size_t?
Ðа

5
@Dan Ви можете сконструювати такі випадки, коли мали б непідписані вставки, і в тих випадках найкраще використовувати цілі мовні функції для її вирішення. Однак я мушу сказати, що було б цікавою конструкцією мати клас, який bar_ * baz_може переповнювати підписане ціле число, а не непідписане ціле число. Обмежившись C ++, варто відзначити, що неподписане переповнення визначено у специфікації, але підписане переповнення є невизначеною поведінкою, тому, якщо бажана модульна арифметика непідписаних цілих чисел, обов'язково використовуйте їх, тому що це фактично визначено!
Корт Аммон

1
@Dan - якщоsize() захлеснула підписана множення, ви на мову UB землі. (а в fwrapvрежимі дивіться далі :) Коли тоді , лише з крихітним тріском більше, він переповнив безпідписане множення, ви, у користувальницькому коді-помилки, повернете неправдивий розмір. Тож я не думаю, що тут багато купує без підпису.
Мартін Ба

4

Я думаю, що відповідь rwong вище вже чудово висвітлює питання.

Я додам свій 002:

  • size_t, тобто розмір, який ...

    може зберігати максимальний розмір теоретично можливого об'єкта будь-якого типу (включаючи масив).

    ... потрібен лише для індексів діапазону sizeof(type)==1, коли ви маєте справу з charтипами байтів ( ). (Але зауважимо, він може бути меншим, ніж тип ptr :

  • Як такий, xxx::size_typeйого можна використовувати в 99,9% випадків, навіть якщо це був тип підписаного розміру. (порівняти ssize_t)
  • Те, що std::vectorі друзі вибрали size_t, не підписаний тип, для розміру та індексації , дехто вважає вадою дизайну. Я згоден. (Серйозно, візьміть 5 хвилин і подивіться на блискавичну розмову CppCon 2016: Джон Калб "без підпису: Настанова щодо кращого коду" .)
  • Розробляючи API C ++ сьогодні, ви знаходитесь в тісному місці: Використовуйте, size_tщоб відповідати Стандартній бібліотеці, або використовуйте ( підписаний ) intptr_tабо ssize_tдля легких і менших розрахунків індексації, схильних до помилок.
  • Не використовуйте int32 або int64 - використовуйте, intptr_tякщо ви хочете підписатись і хочете розміру машинного слова або використовувати ssize_t.

Щоб безпосередньо відповісти на це питання, це не зовсім "історичний артефакт", оскільки теоретичне питання про необхідність розгляду більше половини адресного простору ("індексація" або) має бути, аем, вирішено якось на низькому рівні мови, як C ++.

Я, на думку особисто , думаю, що це помилка дизайну, що Стандартна бібліотека використовує без підпису size_tвсюди навіть там, де вона не представляє собою необмежений розмір пам'яті, а ємність введених даних, як для колекцій:

  • задані правила просування цілих чисел на C ++ s ->
  • непідписані типи просто не роблять хороших кандидатів на "семантичні" типи для чогось такого типу розміру, який є семантично непідписаним.

Я повторю поради Йона тут:

  • Виберіть типи операцій, які вони підтримують (не діапазон значень). (* 1)
  • Не використовуйте в API вам неподписані типи. Це приховує клопів, що не мають переваг.
  • Не використовуйте "без підпису" для кількості. (* 2)

(* 1), тобто без підпису == бітова маска, ніколи не робіть на ній математику (тут потрапляє перше виняток - вам може знадобитися лічильник, який завертає - це повинен бути неподписаний тип.)

(* 2) величини, що означають те, що ви рахуєте та / або займаєтесь математикою.


Що ви маєте на увазі під "повною придатною плоскою пам'яттю"? Крім того, впевнений, що ви не хочете ssize_t, визначений як підписаний кулон size_tзамість intptr_t, який може зберігати будь-який (не членський) вказівник і, таким чином, може бути більшим?
Дедуплікатор

@Deduplicator - Ну, мабуть, я, можливо, отримав size_tвизначення дещо зіпсованим. Див. Розмір_t проти intptr та en.cppreference.com/w/cpp/types/size_t На сьогодні дізналися щось нове. :-) Я думаю, що решта аргументів стоять, я побачу, чи зможу виправити використані типи.
Мартін Ба

0

Я просто додам, що з міркувань продуктивності я зазвичай використовую size_t, щоб переконатися, що прорахунки спричиняють підтік, що означає, що обидві перевірки діапазону (нижче нуля та вище розміру ()) можна зменшити до одного:

за допомогою підписаного int:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

з використанням неподписаного int:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}

1
Ви дійсно хочете пояснити це ще ретельніше.
Мартін Ба

Щоб зробити відповідь кориснішою, можливо, ви можете описати, як виглядає межі цілого масиву або порівняння зсуву (підписане та непідписане) у машинному коді від різних постачальників компіляторів. Існує багато онлайн-компіляторів C ++ та розбиральних сайтів, які можуть показати відповідний компільований машинний код для заданого коду C ++ та прапорів компілятора.
rwong

Я спробував це ще трохи пояснити.
asger
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.