Имеет ли смысл «длинный» запрет?

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?
immibis
7
@slebetman Я вырыл немного глубже, кажется, что требование все еще в силе, хотя скрыто в §3.9.1.3, где стандарт C ++ заявляет: «Целочисленные типы со знаком и без знака должны удовлетворять ограничениям, данным в стандарте C, раздел 5.2. 4.2.1 «. А в стандарте C §5.2.4.2.1 в нем указан минимальный диапазон, в точности, как вы написали. Вы были абсолютно правы. :) Очевидно, что обладать копией стандарта C ++ недостаточно, нужно также найти копию стандарта C.
Томми Андерсен
11
Вам не хватает мира DOSBox / Turbo C ++, в котором intеще очень много 16 бит. Ненавижу это говорить, но если вы собираетесь писать о «современном кроссплатформенном мире», вы не можете игнорировать весь индийский субконтинент.
Гонки легкости на орбите

Ответы:

17

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

Как вы говорите в своем посте, short и int сегодня имеют достаточно стабильные характеристики для всех основных настольных / серверных / мобильных платформ, и я не вижу причин для того, чтобы это изменилось в обозримом будущем. Так что я вижу мало причин избегать их вообще.

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, и, следовательно, имеет поведение по модулю. Однако были исторически и теоретически могут быть в будущих платформах, где intявляется 64-битным и, следовательно uint32_t, не по модулю поведения.

Лично я бы сказал, что лучше привыкнуть к принудительному поведению по модулю, используя «1u *» или «0u +» в начале ваших уравнений, поскольку это будет работать для любого размера беззнакового типа.

Питер Грин
источник
1
Все типы «указанного размера» были бы гораздо полезнее, если бы они могли указывать семантику, которая отличается от встроенных типов. Например, было бы полезно иметь тип, который использовал бы арифметику mod-65536 независимо от размера «int», наряду с типом, который мог бы содержать числа от 0 до 65535, но мог бы произвольно и не обязательно последовательно быть способен проведения чисел больше, чем это. Какой тип размера будет самым быстрым, будет зависеть от большинства машин, поэтому возможность произвольного выбора компилятора будет оптимальной для скорости.
Суперкат
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 Я бы хотел, чтобы Стандарт требовал, чтобы каждый из них влиял только на свое соответствующее требуемое пространство имен - а не, казалось бы, отказывался от некачественных соглашений, установленных некоторыми реализациями, - но, хорошо, мы здесь.
underscore_d
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,intЭто идеальный выбор, если только вы не собираетесь хранить огромные массивы из них, и в этом случае вы можете использовать более компактный тип данных за счет того, что вам придется платить более высокую цену за доступ к отдельным элементам. Более эффективное кэширование, скорее всего, окупится. Многие функции операционной системы также определены в терминах int. Было бы глупо конвертировать свои аргументы и результаты туда и обратно. Все, что это может сделать, это ввести ошибки переполнения.

longобычно это самый широкий тип, который может быть обработан с помощью одной машинной инструкции. Это делает его особенно unsigned longпривлекательным для работы с необработанными данными и всеми видами манипуляций с битами. Например, я бы ожидал увидеть unsigned longв реализации бит-вектор. Если код написан аккуратно, то не имеет значения, насколько он на самом деле широк (потому что код будет адаптироваться автоматически). На платформах, где собственное машинное слово является 32-битным, наличие резервного массива бит-вектора будет массивомunsigned32-битные целые числа наиболее желательны, потому что было бы глупо использовать 64-битный тип, который должен загружаться с помощью дорогих инструкций, только чтобы в любом случае снова сдвинуть и замаскировать ненужные биты. С другой стороны, если размер собственного слова платформы составляет 64 бита, я хочу массив этого типа, потому что это означает, что такие операции, как «найти первый набор», могут выполняться в два раза быстрее. Таким образом, «проблема» типа longданных, который вы описываете, в том, что его размер варьируется от платформы к платформе, на самом деле является функцией, которую можно использовать с пользой. Это становится проблемой только в том случае, если вы думаете о встроенных типах как о типах определенной ширины в битах, чего у них просто нет.

char, intИ longочень полезные типы , как описано выше. shortи long longне так полезны, потому что их семантика гораздо менее ясна.

5gon12eder
источник
4
В частности, ОП выявила разницу в размерах longмежду Windows и Unix. Возможно, я неправильно понимаю, но ваше описание различий в размерах long«функции» вместо «проблемы» имеет смысл для сравнения 32- и 64-разрядных моделей данных, но не для этого конкретного сравнения. В конкретном случае этот вопрос задается, действительно ли это особенность? Или это особенность в других ситуациях (то есть в целом) и безвредная в этом случае?
Дэн Гетц
3
@ 5gon12eder: Проблема в том, что такие типы, как uint32_t, были созданы с целью позволить поведению кода быть независимым от размера «int», но отсутствие типа, значение которого было бы «вести себя как uint32_t», работает на 32- Битовая система »делает написание кода, поведение которого правильно не зависит от размера« int », намного сложнее, чем написание кода, который является почти правильным.
Суперкат
3
Да, я знаю ... вот откуда пришло проклятие. Первоначальные авторы просто встали на путь сопротивления аренды, потому что, когда они писали код, 32-битные ОС были на расстоянии более десяти лет.
Стивен Бернап
8
@ 5gon12eder К сожалению, суперкат это правильно. Все типы точной ширины являются «только определения типов» и правила продвижения целое не обращать внимания на них, а это значит , что арифметика uint32_tзначений будет осуществляться в подписанном , int-Width арифметике на платформе , где intнаходится шире , чем uint32_t. (С сегодняшним ABI это, скорее всего, будет проблемой для uint16_t.)
zwol
9
Во-первых, спасибо за подробный ответ. Но: О, дорогой. Ваш длинный абзац: « longобычно будет самым широким шрифтом, который может быть обработан с помощью одной машинной инструкции. ...» - и это совершенно неправильно . Посмотрите на модель данных Windows. ИМХО, весь ваш следующий пример ломается, потому что на x64 Windows long все еще 32 бит.
Мартин Ба
6

В другом ответе уже подробно рассматриваются типы cstdint и менее известные их варианты.

Я хотел бы добавить к этому:

использовать доменные имена типов

То есть, не объявляйте ваши параметры и переменные uint32_t(конечно, нет long!), Но имена, такие как channel_id_type, room_count_typeи т. Д.

о библиотеках

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

Лучше вещь , чтобы сделать обертки.

В общем, моя стратегия состоит в том, чтобы создать набор функций, похожих на приведение, которые будут использоваться. Они перегружены, чтобы принимать только те типы, которые точно соответствуют соответствующим типам, а также любые вариации указателя и т. Д., Которые вам нужны. Они определены специально для os / compiler / settings. Это позволяет удалять предупреждения и в то же время обеспечивать использование только «правильных» преобразований.

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
источник
почему отрицание (без комментариев)?
JDługosz
Потому что большинство голосов в этой сети появляются без комментариев ...
Руслан