Чи має сенс "довга" заборона?


109

У сучасному кросплатформенному світі C ++ (або C) ми маємо :

Data model  | short |   int |   long | long long | pointers/size_t  | Sample operating systems
... 
LLP64/IL32P64   16      32      32     64           64                Microsoft Windows (x86-64 and IA-64)
LP64/I32LP64    16      32      64     64           64                Most Unix and Unix-like systems, e.g. Solaris, Linux, BSD, and OS X; z/OS
...

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

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

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

Чи має це сенс , чи є випадок використання longв сучасному коді C ++ (або C), який повинен запускати крос-платформу? (платформа - це настільні, мобільні пристрої, але не такі речі, як мікроконтролери, DSP тощо)


Можливо цікаві фонові посилання:


14
Як ви будете мати справу з дзвінками до бібліотек, які використовують довго?
Анхель

14
longце єдиний спосіб гарантувати 32 біти. intможе бути 16 біт, тому для деяких застосувань цього недостатньо. Так, intіноді буває 16 біт на сучасних компіляторах. Так, люди пишуть програмне забезпечення на мікроконтролери. Я б заперечував, що більше людей пише програмне забезпечення, яке має більше користувачів на мікроконтролерах, ніж на ПК із зростанням пристроїв iPhone і Android, не кажучи вже про підйом Arduinos тощо.
slebetman

53
Чому б не заборонити char, short, int, long і long long і не використовувати типи [u] intXX_t?
іммібіс

7
@slebetman Я викопав трохи глибше, схоже, вимога все ще існує, хоча і прихована в §3.9.1.3, де стандарт C ++ зазначає: "Підписані та непідписані цілі числа повинні відповідати обмеженням, наведеним у стандарті C, розділ 5.2. 4.2.1. " А в стандарті C § 5.2.2.2.2 зазначено мінімальний діапазон, саме так, як ви написали. Ви були абсолютно праві. :) Очевидно, що володіти копією стандарту C ++ недостатньо, потрібно знайти і копію стандарту C.
Томмі Андерсен

11
Вам не вистачає світу DOSBox / Turbo C ++, у якому intще дуже багато 16 біт. Я ненавиджу це говорити, але якщо ти збираєшся писати про "сьогоднішній міжплатформенний світ", ти не можеш ігнорувати весь індійський субконтинент.
Гонки легкості по орбіті

Відповіді:


17

Єдина причина, яку я longсьогодні використовував би - це для виклику або реалізації зовнішнього інтерфейсу, який його використовує.

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

longз іншого боку - безлад. Для всіх 32-бітних систем, наскільки мені відомо, він мав такі характеристики.

  1. Це був рівно 32-бітовий розмір.
  2. Він був такого ж розміру, як адреса пам'яті.
  3. Він був такого ж розміру, як і найбільша одиниця даних, яка могла зберігатися в звичайному реєстрі та працювати над однією інструкцією.

Велика кількість коду була написана на основі однієї або декількох цих характеристик. Однак з переходом на 64-бітну зберегти їх не вдалося. Unix-подібні платформи дісталися для LP64, які зберегли характеристики 2 і 3 за ціною характеристики 1. Win64 перейшов на LLP64, який зберіг характеристику 1 за ціною характеристик 2 і 3. В результаті ви більше не можете покладатися на жодну з цих характеристик і що ІМО залишає мало підстав для використання long.

Якщо ви хочете використовувати тип, який має розмір рівно 32 біт int32_t.

Якщо ви хочете використовувати тип, який має той самий розмір, що і вказівник, ви повинні використовувати intptr_t(або краще uintptr_t).

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


PS

Я б не переймався типами "швидкий" або "найменший". "Найменші" типи мають значення лише в тому випадку, якщо ви дбаєте про портабельність дійсно незрозумілих архітектур CHAR_BIT != 8. Розмір типів "швидких" на практиці здається досить довільним. Linux, схоже, робить їх як мінімум такого ж розміру, що й покажчик, що нерозумно на 64-бітних платформах із швидкою 32-бітовою підтримкою, як x86-64 та arm64. IIRC iOS робить їх якомога меншими. Я не впевнений, що роблять інші системи.


PPS

Однією з причин використання unsigned long(але не очевидною long) є те, що вона гарантовано має модульну поведінку. На жаль, завдяки розкрученим C правилами просування неподписані типи менше, ніж intне мають модульної поведінки.

На всіх основних платформах сьогодні uint32_tоднаковий розмір або більший, ніж int, і тому має модульну поведінку. Однак існувало історично, і теоретично це могло б бути в майбутньому платформами, де int64-бітова і, отже uint32_t, не має модульної поведінки.

Особисто я б сказав, що краще натрапити на звичку форсувати поведінку за модулем, використовуючи "1u *" або "0u +" на початку ваших рівнянь, оскільки це буде працювати для будь-якого розміру неподписаного типу.


1
Усі типи "заданого розміру" були б набагато кориснішими, якби вони могли вказати семантику, яка відрізнялася від вбудованих типів. Наприклад, було б корисно мати тип, який використовував би арифметику mod-65536 незалежно від розміру "int", а також тип, який міг би містити числа від 0 до 65535, але міг довільно і не обов'язково послідовно бути здатним тримання чисел, більших за це. Який тип розміру буде найшвидшим, залежатиме від більшості машин, залежно від контексту, тож можливість дозволити компілятору довільно вибирати було б оптимальним для швидкості.
supercat

204

Як ви згадуєте у своєму запитанні, сучасне програмне забезпечення - це взаємодія між платформами та системами в Інтернеті. Стандарти C і C ++ дають діапазони для цілих розмірів типів, а не конкретних розмірів (на відміну від таких мов, як Java та C #).

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

Введіть, <cstdint>що забезпечує саме це, і є стандартним заголовком, який повинні надати всі компіляторні та стандартні бібліотечні платформи. Примітка: цей заголовок був потрібний лише для C ++ 11, але багато старих впроваджень бібліотеки все одно надавали його.

Хочете 64-бітне ціле число без підпису? Використовуйте uint64_t. 32-бітове ціле число? Використовуйте int32_t. Хоча типи в заголовку необов’язкові, сучасні платформи повинні підтримувати всі типи, визначені в цьому заголовку.

Іноді потрібна певна бітова ширина, наприклад, у структурі даних, що використовується для зв'язку з іншими системами. Іншого разу це не так. Для менш суворих ситуацій <cstdint>передбачені типи, що мають мінімальну ширину.

Є найменші варіанти: int_leastXX_tбуде цілий тип мінімум XX біт. Він використовуватиме найменший тип, що забезпечує XX біт, але тип може бути більшим за вказану кількість біт. На практиці вони, як правило, такі ж, як описані вище типи, що дають точну кількість біт.

Існують також швидкі варіанти: int_fastXX_tмає принаймні XX біт, але слід використовувати тип, який виконує швидкість на певній платформі. Визначення "швидкого" в цьому контексті не визначено. Однак на практиці це, як правило, означає, що тип, менший за розмір реєстру процесора, може бути псевдонімом типу типу реєстру процесора. Наприклад, заголовок Visual C ++ 2015 вказує, що int_fast16_tце 32-бітове ціле число, оскільки 32-бітна арифметика в цілому швидша на x86, ніж 16-бітна арифметика.

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

Так що так, longслід заборонити сучасний код C ++. Так повинно int, shortі long long.


20
Я хотів би, щоб я мав ще п’ять акаунтів, щоб підтвердити це ще кілька разів.
Стівен Бернап

4
+1, я мав справу з деякими дивними помилками пам'яті, які трапляються лише тоді, коли розмір структури залежить від того, на якому комп’ютері ви компілюєте.
Джошуа Снайдер

9
@Wildcard це заголовок C, який також є частиною C ++: див. Префікс "c" на ньому. Існує також певний спосіб помістити typedefs в stdпростір імен, коли #included в блоці компіляції C ++, але документація, яку я зв'язав, не згадує, і Visual Studio, здається, не хвилює, як я отримую доступ до них.

11
Заборона intможе бути ... надмірною? (Я вважаю, що код повинен бути надзвичайно портативним на всіх незрозумілих (і не настільки незрозумілих) платформах. Заборона його "коду програми" може не дуже сильно співпрацювати з нашими розробниками.
Мартін Ба

5
@Snowman #include <cstdint>це потрібно , щоб помістити типи в std::і ( до жаль) , необов'язково допускається також , щоб помістити їх в глобальному просторі імен. #include <stdint.h>саме зворотне. Те саме стосується будь-якої іншої пари заголовків C. Дивіться: stackoverflow.com/a/13643019/2757035 Я хотів би, щоб Стандарт вимагав, щоб кожен зачіпав лише його відповідний простір імен - а не здавався прив'язком до поганих конвенцій, встановлених деякими реалізаціями - але добре, ось ми.
підкреслюй_

38

Ні, заборона вбудованих цілих типів була б абсурдною. Однак ними також не слід зловживати.

Якщо вам потрібне ціле число, що має рівно N біт, використовуйте (або якщо вам потрібна версія). Думати як ціле 32 біт і як 64 бітове ціле число - просто неправильно. Це може статися таким на ваших поточних платформах, але це спирається на поведінку, визначену реалізацією.std::intN_tstd::uintN_tunsignedintlong long

Використання цілих чисел фіксованої ширини також корисно для взаємодії з іншими технологіями. Наприклад, якщо деякі частини вашої програми написані на Java, а інші на C ++, ви, ймовірно, захочете відповідати цілим типам, щоб ви отримали стійкі результати. (Ще слід пам’ятати, що переповнення Java має чітко виражену семантику, тоді як signedпереповнення C ++ є невизначеною поведінкою, тому послідовність є високою метою.) Вони також будуть неоціненними при обміні даними між різними вузлами обчислень.

Якщо вам не потрібні саме N біт, а лише тип, достатньо широкий , подумайте про використання (оптимізованого для простору) або (оптимізованого для швидкості). Знову ж таки, в обох сімей є також аналоги.std::int_leastN_tstd::int_fastN_tunsigned

Отже, коли використовувати вбудовані типи? Отже, оскільки стандарт точно не визначає їх ширину, використовуйте їх, коли вам не байдужа фактична ширина біта, а інші характеристики.

A char- найменше ціле число, на яке може бути адресоване обладнання. Мова насправді змушує вас використовувати її для зведення довільної пам'яті. Це також єдиний життєздатний тип представлення (вузьких) символьних рядків.

intЗазвичай буде найшвидшим типом машина може працювати. Він буде достатньо широким, щоб його можна було завантажувати та зберігати за допомогою однієї інструкції (без необхідності маскування чи зсуву бітів) та досить вузькою, щоб з ним можна було керуватись (найбільш) ефективними інструкціями щодо обладнання. Тому intце ідеальний вибір для передачі даних і виконання арифметики, коли переповнення не викликає особливих проблем. Наприклад, базовим типом перерахунків є типовий перелік int. Не змінюйте його на 32-бітове ціле число лише тому, що можете. Крім того, якщо у вас є значення, яке може бути лише –1, 0 і 1, anintє ідеальним вибором, якщо ви не збираєтесь зберігати величезні масиви з них, в цьому випадку, можливо, ви хочете використовувати більш компактний тип даних за ціну платити більш високу ціну за доступ до окремих елементів. Більш ефективне кешування, ймовірно, окупиться за них. Багато функцій операційної системи також визначені в термінах int. Було б нерозумно перетворювати свої аргументи та результати туди і назад. Все це, можливо, може ввести помилки переповнення.

longзазвичай це найширший тип, з яким можна обробляти одні інструкції з машини. Це робить особливо unsigned longпривабливим для роботи з необробленими даними та всіма видами маніпуляцій з бітами. Наприклад, я б очікував побачити unsigned longв реалізації біт-вектора. Якщо код написано ретельно, не має значення, наскільки широким є тип власне (тому що код адаптується автоматично). На платформах, де власне машинне слово 32-бітне, маючи резервний масив бітового вектора, бути масивомunsigned32-бітні цілі числа є найбажанішим, тому що було б нерозумно використовувати тип 64 біт, який доводиться завантажувати за допомогою дорогих інструкцій, лише щоб зрушити і замаскувати непотрібні біти все одно. З іншого боку, якщо розмір нативного слова платформи становить 64 біт, я хочу масив цього типу, оскільки це означає, що операції типу "знайти перший набір" можуть працювати вдвічі швидше. Тож “проблема” типу longданих, яку ви описуєте, що її розмір змінюється від платформи до платформи, насправді є функцією, яку можна добре застосувати. Це стає лише проблемою, якщо ви думаєте про вбудовані типи як типи певної ширини бітів, яких у них просто немає.

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


4
Зокрема, ОП назвала різницю в розмірах longміж Windows та Unix. Мені може бути непорозуміння, але ваш опис різниці в розмірі long"функції" замість "проблеми" має сенс для мене для порівняння 32 і 64 бітових моделей даних, але не для цього конкретного порівняння. У конкретному випадку, про яке задали це запитання, чи справді це особливість? Або це особливість в інших ситуаціях (тобто загалом) і нешкідлива в даному випадку?
Ден Гец

3
@ 5gon12eder: Проблема полягає в тому, що такі типи, як uint32_t, були створені для того, щоб поведінка коду не залежала від розміру "int", але відсутність типу, значення якого буде "вести себе так, як uint32_t працює на 32- бітова система "робить письмовий код, поведінка якого правильно не залежить від розміру" int "набагато складніше, ніж код написання, який майже правильний.
supercat

3
Так, я знаю ... саме звідси походили прокляття. Оригінальні автори просто пішли на опір оренди, оскільки, коли вони писали код, 32-розрядні ОС були за десять років.
Стівен Бернап

8
@ 5gon12eder На жаль, суперкарт правильний. Всі типи точної ширини є «тільки визначення типів» і правила просування ціле не звертати уваги на них, а це значить , що арифметика uint32_tзначень буде здійснюватися в підписаному , int-Width арифметиці на платформі , де intзнаходиться ширше , ніж uint32_t. (З сьогоднішніми ABI це набагато більше шансів, що це буде проблемою uint16_t.)
zwol

9
По-перше, дякую за детальну відповідь. Але: О дорогий. Ваш довгий абзац: " longзазвичай це найширший тип, з яким можна обробляти за допомогою однієї інструкції на машині. ..." - і це абсолютно неправильно . Подивіться на модель даних Windows. IMHO, весь ваш наступний приклад розбивається, тому що в x64 Windows довгий ще 32 бітний.
Мартін Ба

6

Ще одна відповідь уже розглядає типи cstdint та маловідомі їх варіанти.

Я хотів би додати до цього:

використовувати імена типів домену

Тобто не оголошуйте свої параметри та змінні такими, що вони є uint32_t(звичайно, ні long!), А такі назви, як channel_id_typeі room_count_typeт.д.

про бібліотеки

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

Краще річ , щоб зробити обгортки.

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

channel_id_type cid_out;
...
SomeLibFoo (same_thing_really<int*>(&cid_out));

Зокрема, для різних примітивних типів, що створюють 32 біти, ваш вибір способу int32_tвизначення може не відповідати виклику бібліотеки (наприклад, int vs long у Windows).

Функція литого документа документує зіткнення, передбачає перевірку часу компіляції результату, що відповідає параметру функції, і видаляє будь-яке попередження або помилку, якщо і лише тоді, коли фактичний тип відповідає реальному розміру. Тобто, він перевантажений і визначається, якщо я переношу (у Windows) a int*чи a long*і видає помилку часу компіляції в іншому випадку.

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


чому потік (без коментарів)?
JDługosz

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