Які найкращі практики щодо непідписаних літер?


43

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

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

Редагувати: З цього питання я також помічав, що в C повернення негативних значень для помилок є звичайним явищем, а не викиданням винятків, як у C ++.


26
Просто стежте за for(unsigned int n = 10; n >= 0; n --)(петлі нескінченно)
Кріс Берт-Браун

3
У C і C ++ непідписані вставки мають точно визначене поведінку переповнення (модуль 2 ^ n). Підписані вставки не відповідають. Оптимізатори все частіше використовують таку не визначену поведінку переповнення, що призводить до дивовижних результатів у деяких випадках.
Steve314

2
Гарне питання! Я теж колись спокусився використати унти з обмеженим діапазоном, але виявив, що ризик / незручність переважував будь-яку користь / зручність. Як ви вже говорили, більшість бібліотек приймають регулярні вставки, де це робиться утінка. Це ускладнює роботу, але також ставить питання: чи варто це? На практиці (якщо припустити, що ви не розбираєтесь у дурних речах), у вас рідко буде значення -218, коли очікується позитивне. Це -218, мабуть, прийшло звідкись, правда? і ви можете простежити його походження. Трапляється рідко. Використовуйте твердження, винятки, кодові договори, щоб допомогти вам.
Робота

@William Ting: Якщо мова йде лише про C / C ++, слід додати відповідні теги до свого питання.
CesarGon

2
@Chris: Наскільки вагомою є проблема нескінченного циклу? Я маю на увазі, якщо він все-таки пробивається до випуску, тоді код, очевидно, не був перевірений. Навіть коли вам потрібні кілька годин, щоб налагодити її в перший раз, коли ви робите цю помилку, вдруге ви повинні знати, на що звернути увагу, коли ваш код не припиняється циклічно.
Безпечний

Відповіді:


28

Чи є щось вирішальне, що я не помічаю?

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

Я вважаю, що це головна причина, чому Java опустила неподписані типи int.


3
Іншим рішенням буде вимагати, щоб ви вручну додавали свої номери, як це доречно. Це те, що, схоже, робить Go (я хоч трохи погрався з ним), і мені це подобається більше, ніж підхід Java.
Тихон Єлвіс

2
Це було хорошим приводом для того, щоб Java не включала 64-розрядний неподписаний тип, і, можливо, гідна причина не включати 32-бітний неподписаний тип [хоча семантика додавання підписаних та непідписаних 32-бітних значень не буде важкою-- така операція повинна просто дати результат, підписаний 64 бітами]. intТим не менш підписані типи, ніж вони, не становлять такої складнощі (оскільки будь-які обчислення будуть сприяти int) Я не маю нічого хорошого сказати про відсутність безпідписаного типу.
supercat

17

Я думаю, що у Майкла є вагомий момент, але ІМО, чому всі користуються int весь час (особливо в for (int i = 0; i < max, i++), полягає в тому, що ми дізналися це саме так. Коли кожен приклад в « як навчитися програмувати » книга використовує intв forциклі, далеко не всі коли - небудь питання такої практики.

Інша причина - intце на 25% коротше uint, і ми всі ліниві ... ;-)


2
Я згоден з навчальним питанням. Більшість людей, здається, ніколи не ставлять під сумнів те, що вони читають: Якщо це в книзі, це не може бути помилкою, правда?
Матьє М.

1
Ось імовірно, чому всі використовують постфікс ++при збільшенні, незважаючи на те, що його особлива поведінка рідко потрібна і навіть може призвести до безглуздого розгортання копій, якщо індекс циклу є ітератором чи іншим не фундаментальним типом (або компілятор дійсно щільний) .
підкреслити_16

Просто не робіть чогось типу "для (uint i = 10; i> = 0; --i)". Використання лише ints для змінних циклу уникає такої можливості.
Девід Торнлі

11

Кодування інформації про діапазон у типи - це хороша річ. Він застосовує розумні числа під час компіляції.

Багато архітектури , здається, мають спеціалізовані інструкції по роботі з int-> floatперетворення. Перетворення з unsignedможе бути повільніше (крихітний біт) .


8

Змішування підписаних та непідписаних типів може перенести вас у світ болю. І ви не можете використовувати всі неподписані типи, оскільки ви зіткнетеся з речами, які мають дійсний діапазон, який включає негативні числа, або потрібно значення, яке вказує на помилку, і -1 є найбільш природним. Отже, чистий результат полягає в тому, що багато програмістів використовують усі підписані цілі числа.


1
Можливо, це краща практика не змішувати дійсні значення з вказівкою на помилку в одній змінній і використовувати для цього окремі змінні. Зрозуміло, що стандартна бібліотека C не дає тут хорошого прикладу.
Безпечний

7

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

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


4
Але я, можливо, хочу обмежити свої значення на місяць лише від 1 до 12. Чи використовую я для нього інший тип? Що за місяць? Деякі мови фактично дозволяють обмежувати такі значення. Інші, наприклад .Net / C #, надають кодові договори. Звичайно, невід'ємні цілі числа трапляються досить часто, але більшість мов, які підтримують цей тип, не підтримують подальших обмежень. Отже, чи варто використовувати суміш уніт та перевірки помилок чи просто робити все через перевірку помилок? Більшість бібліотек не запитують утину, де було б сенс використовувати її, отже, використання однієї і кастинг може бути незручним.
Робота

@Job, я б сказав, що ви повинні використовувати якесь компілятор / інтерпретаторське обмеження на ваші місяці. Це може дати вам встановити певну плиту, але на майбутнє у вас є накладене обмеження, яке запобігає помилкам і повідомляє набагато чіткіше, що ви очікуєте. Запобігання помилкам та полегшення спілкування набагато важливіше, ніж незручності під час впровадження.
daramarak

1
"Можливо, я хочу обмежити свої значення на місяць лише від 1 до 12" Якщо у вас є обмежений набір значень, як місяці, вам слід використовувати тип перерахунку, а не необроблені цілі числа.
Джош Касвелл

6

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


14
Напевно, ви повинні використовувати для цього size_t в c ++
JohnB

2
Я знаю, я просто не можу заважати.
Quant_dev

3

Вам слід потурбуватися про це, коли ви маєте справу з цілим числом, яке може насправді наближатись або перевищувати межі підписаного int. Оскільки позитивний максимум 32-бітного цілого числа становить 2,147,483,647, то слід використовувати непідписаний int, якщо ви знаєте, що це: a) ніколи не буде від'ємним і b) може досягти 2,147,483,648. У більшості випадків, включаючи ключі бази даних та лічильники, я навіть ніколи не підходжу до подібних чисел, тому не турбуюся про те, щоб переживати, чи використовується бітовий знак для числового значення чи для позначення знаку.

Я б сказав: використовуйте Int, якщо ви не знаєте, що вам потрібен неподписаний int.


2
Працюючи зі значеннями, які можуть досягати максимальних значень, слід розпочати перевірку операцій на цілі переповнення незалежно від знаку. Ці перевірки, як правило, простіші для непідписаних типів, тому що більшість операцій мають чітко визначені результати без визначеного та визначеного поведінкою поведінки.
Безпечний

3

Це компроміс між простотою та надійністю. Чим більше помилок, які можна виявити під час компіляції, тим надійніше програмне забезпечення. Різні люди та організації знаходяться в різних точках уздовж цього спектру.

Якщо ви коли-небудь робите будь-яке програмування з високою надійністю в Ada, ви навіть використовуєте різні типи для таких змінних, як відстань у футах проти відстані в метрах, і компілятор позначає це, якщо випадково призначите одну іншу. Це ідеально підходить для програмування керованої ракети, але перевищення (призначене каламбур), якщо ви перевіряєте веб-форму. Не обов'язково нічого поганого в будь-якому випадку, якщо це відповідає вимогам.


2

Я схильний погодитися з міркуваннями Джоела Етертона, але приходжу до протилежного висновку. Як я це бачу, навіть якщо ви знаєте, що цифри навряд чи колись наблизяться до меж підписаного типу, якщо ви знаєте, що від'ємні числа не відбудуться, то є дуже мало причин використовувати підписаний варіант типу.

З тієї ж причини, чому я в кількох вибраних екземплярах в таблицях SQL Server використовував BIGINT(64-бітове ціле число), а не INTEGER(32-бітове ціле число). Ймовірність того, що дані потраплять на 32-бітову межу протягом будь-якого розумного проміжку часу, є незначною, але якщо це станеться, наслідки в деяких ситуаціях можуть бути досить руйнівними. Просто не забудьте правильно зіставити типи між мовами, інакше ви зіткнетесь із цікавими дивацтвами дуже далеко ...

Це означає, що для деяких речей, таких як значення первинного ключа бази даних, підписані або непідписані насправді не має значення, оскільки, якщо ви вручну не ремонтуєте зламані дані чи щось за цими лініями, ви ніколи не маєте прямого значення зі значенням; це ідентифікатор, більше нічого. У цих випадках послідовність, ймовірно, важливіша, ніж точний вибір підписаності. В іншому випадку ви опинитесь підписаними стовпцями інших іноземних ключів та іншими, які не мають підпису, без явного шаблону - або знову ж таки цікавою дивністю.


Якщо ви працюєте з даними, витягнутими з системи SAP, я настійно рекомендую BIGINT для полів ідентифікації (наприклад, CustomerNumber, ArticleNumber тощо). Поки ніхто не використовує буквено-цифрові рядки в якості ідентифікаторів, тобто ... зітхне
Треб

1

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

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


1
"Я рекомендую [...] зазвичай використовувати підписані типи." Гм, ви забули згадати переваги підписаних типів і дали лише список, коли використовувати неподписані типи. "дивацтво" ? Хоча більшість непідписаних операцій мають чітко визначені поведінку та результати, ви вводите невизначене та визначене реалізацією поведінку під час використання підписаних типів (переповнення, зсув бітів, ...). У вас тут дивне визначення поняття "дивацтво".
Безпечний

1
@Secure: "Дивацтво", на яке я посилаюсь, має відношення до семантики операторів порівняння, особливо в операціях, що передбачають змішані типи підписаних та непідписаних типів. Ви впевнені, що поведінка підписаних типів не визначена при використанні значень, досить великих для переповнення, але поведінка непідписаних типів може бути дивовижною навіть при роботі з відносно невеликою кількістю. Наприклад, (-3) + (1u) більше -1. Крім того, деякі нормальні математичні асоціативні відносини, які застосовуватимуться до чисел, не стосуються неподписаних. Наприклад, (ab)> c не означає (ac)> b.
supercat

1
@Secure: Хоча це правда, що не завжди можна покладатися на таку асоціативну поведінку з "великими" підписаними числами, поведінка працює так, як очікувалося, коли маєш справу з числами, які є "малими" відносно області підписаних цілих чисел. Навпаки, вищезазначене неприєднання є проблематичним з непідписаними значеннями "2 3 1". Між іншим, той факт, що підписані форми поведінки мають невизначену поведінку при використанні поза межами, може дозволяти покращити генерацію коду на деяких платформах при використанні значень, менших за розмір нативного слова.
supercat

1
Якби ці коментарі були у вашій відповіді в першу чергу, замість рекомендації та "виклику імен" без жодних причин, я б не коментував це. ;) Хоча я і досі не погоджуюся з "дивацтвом" тут, це просто визначення типу. Використовуйте правильний інструмент для даної роботи, і, звичайно, знайте інструмент. Непідписані типи - це неправильний інструмент, коли потрібні +/- відносини. Є причина, чому він size_tне підписаний і ptrdiff_tпідписаний.
Безпечний

1
@Secure: якщо те, що потрібно, - це представляти послідовність бітів, неподписані типи чудові; Я думаю, що ми там згодні. А на деяких невеликих мікросхемах неподписані типи можуть бути ефективнішими для числових величин. Вони також корисні у випадках, коли дельти представляють числові величини, але фактичні значення не відповідають (наприклад, порядкові номери TCP). З іншого боку, у будь-який момент, коли віднімати неподписані значення, потрібно турбуватися про кутові випадки, навіть коли числа невеликі; така математика з підписаними значеннями є лише кутовими випадками, коли числа великі.
supercat

1

Я використовую непідписані вставки, щоб зробити свій код і його наміри більш зрозумілими. Одне, що я хочу захистити від несподіваних неявних перетворень, виконуючи арифметику як з підписаними, так і без підписаними типами, - це використовувати непідписаний короткий (зазвичай 2 байти) для моїх непідписаних змінних. Це ефективно з кількох причин:

  • Якщо ви робите арифметику зі своїми непідписаними короткими змінними та літералами (які є тип int) або змінними типу int, це гарантує, що неподписана змінна завжди буде переведена в int перед оцінкою виразу, оскільки int завжди має більш високий ранг, ніж короткий . Це дозволяє уникнути будь-якої несподіваної поведінки, яка виконує арифметику з підписаними та непідписаними типами, припускаючи, що результат вираження вписується в підписаний int, звичайно.
  • У більшості випадків непідписані змінні, які ви використовуєте, не перевищуватимуть максимальне значення непідписаного 2-байтового короткого (65,535)

Загальний принцип полягає в тому, що тип ваших непідписаних змінних повинен мати нижчий ранг, ніж тип підписаних змінних, щоб забезпечити просування до підписаного типу. Тоді у вас не буде несподіваної поведінки переповнення. Очевидно, ви не можете цього забезпечити постійно, але (найчастіше) це зробити можливо.

Наприклад, нещодавно у мене було щось для циклу приблизно такого:

const unsigned short cuint = 5;
for(unsigned short i=0; i<10; ++i)
{
    if((i-2)%cuint == 0)
    {
       //Do something
    }
}

Буквальне '2' має тип int. Якщо я був непідписаним int замість непідписаного короткого, то в підвиразі (i-2) 2 буде переведено на безпідписаний int (оскільки unsigned int має більший пріоритет, ніж підписаний int). Якщо i = 0, то підвираз дорівнює (0u-2u) = деяке масивне значення через переповнення. Ідентична ідея з i = 1. Однак, оскільки i є непідписаним коротким, він переходить до того ж типу, що і буквальний '2', який підписаний int, і все працює добре.

Для додаткової безпеки: в рідкісному випадку, коли архітектура, яку ви реалізуєте, викликає, що int буде 2 байти, це може призвести до того, що обидва операнди в арифметичному виразі будуть переведені на безпідписаний int у випадку, коли непідписана коротка змінна не підходить в підписаний 2-байтний int, останній з яких має максимальне значення 32 767 <65,535. (Див. Https://stackoverflow.com/questions/17832815/c-implicit-conversion-signed-unsigned для отримання більш детальної інформації). Щоб захиститись від цього, ви можете просто додати static_assert до вашої програми наступним чином:

static_assert(sizeof(int) == 4, "int must be 4 bytes");

і не буде компілюватися в архітектурах, де int - 2 байти.

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