Есть ли еще причина использовать "int" в коде C++?

Многие руководства по стилю, такие как Google, рекомендуют использовать int как целое число по умолчанию при индексировании массивов, например. С ростом 64-битных платформ, где большую часть времени int составляет всего 32 бита, что не является естественной шириной платформы. Как следствие, я не вижу причин, кроме простого, сохранить этот выбор. Мы ясно видим, что при компиляции следующего кода:

double get(const double* p, int k) {
  return p[k];
}

который компилируется в

movslq %esi, %rsi
vmovsd (%rdi,%rsi,8), %xmm0
ret

где первая команда поддерживает 32-битное целое число в целое число 64 бит.

Если код преобразуется в

double get(const double* p, std::ptrdiff_t k) {
  return p[k];
}

сгенерированная сборка теперь

vmovsd (%rdi,%rsi,8), %xmm0
ret

что ясно показывает, что CPU чувствует себя как дома с std::ptrdiff_t чем с int. Многие пользователи C++ перешли в std::size_t, но я не хочу использовать целые числа без знака, если мне действительно не нужно поведение по модулю 2^n.

В большинстве случаев использование int не ухудшает производительность, так как неопределенное поведение или недочетное целочисленное переполнение позволяют компилятору внутренне продвигать любой int в std::ptrdiff_t в циклах, которые имеют дело с индексами, но мы ясно видим из вышеизложенного, что компилятор делает не чувствовать себя как дома с int. Кроме того, использование std::ptrdiff_t на 64-битной платформе сделает переполнение менее вероятным, поскольку я вижу, что все больше и больше людей попадают в ловушку от переполнения int когда им приходится иметь дело с целыми числами больше 2^31 - 1 которые становятся действительно распространенными Эти дни.

Из того, что я видел, единственное, что делает int стороне, похоже, состоит в том, что литералы, такие как 5 являются int, но я не вижу, где это может вызвать любую проблему, если мы перейдем к std::ptrdiff_t как значение по умолчанию целое число.

Я нахожусь на грани создания std::ptrdiff_t как стандартного целого числа de facto для всего кода, написанного в моей небольшой компании. Есть ли причина, почему это может быть плохой выбор?

PS: Я согласен с тем, что имя std::ptrdiff_t является уродливым, и именно поэтому я наложил его на il::int_t который выглядит немного лучше.

PS: Насколько я знаю, многие люди рекомендуют мне использовать std::size_t как целое по умолчанию, я действительно хочу дать понять, что я не хочу использовать целое число без знака как мое целое число по умолчанию. Использование std::size_t как целого по умолчанию в STL было ошибкой, признанной Бьярне Страуступом и стандартным комитетом в интерактивной панели видео : спросите нас все, что угодно, в момент времени 42:38 и 1:02:50.

PS: Что касается производительности, то на любой 64-битной платформе, которую я знаю, +, - и * компилируется одинаково для int и std::ptrdiff_t. Таким образом, нет никакой разницы в скорости. Если вы разделите константу времени компиляции, скорость будет одинаковой. Это только когда вы делите a/b когда ничего не знаете о b что использование 32-битного целого числа на 64-битной платформе дает вам небольшое преимущество в производительности. Но этот случай настолько редок, что я не вижу в качестве выбора переход от std::ptrdiff_t. Когда мы имеем дело с векторизованным кодом, здесь есть явная разница, и чем меньше, тем лучше, но это другая история, и не было бы оснований придерживаться int. В таких случаях я бы рекомендовал использовать типы фиксированного размера C++.

Ответы

Ответ 1

Было проведено обсуждение основных принципов C++, которые следует использовать:

https://github.com/isocpp/CppCoreGuidelines/pull/1115

Herb Sutter написал, что gsl::index будет добавлен (в будущем, возможно, std::index), который будет определен как ptrdiff_t.

hsutter прокомментировал 26 декабря 2017 г. •

(Спасибо многим экспертам WG21 за их комментарии и отзывы в этой заметке.)

Добавьте следующий тип typedef в GSL

namespace gsl { using index = ptrdiff_t; }

и рекомендуем gsl::index для всех индексов/индексов/размеров контейнера.

обоснование

В Руководстве рекомендуется использовать подписанный тип для индексов/индексов. См. ES.100 - ES.107. C++ уже использует знаковые целые числа для индексов массива.

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

Если у нас нет короткого слова, такого как index который бы конкурировал с int и auto, люди все равно будут использовать int и auto и получать свои ошибки. Например, они будут писать for(int i=0; i<v.size(); ++i) или for(auto i=0; i<v.size(); ++i) которые имеют 32-разрядные размерные ошибки на широко используемых платформах и for(auto i=v.size()-1; i>=0; ++i) который просто не работает. Я не думаю, что мы можем научить for(ptrdiff_t я =... с прямым лицом или что люди согласятся с этим.

Если бы у нас был насыщающий арифметический тип, мы могли бы использовать это. В противном случае наилучшим вариантом будет ptrdiff_t который имеет почти все преимущества насыщающего арифметического неподписанного типа, за исключением того, что ptrdiff_t все еще создает широко распространенный стиль цикла for(ptrdiff_t i=0; i<v.size(); ++i) испускает подписанные/неподписанные несоответствия на i<v.size() (и аналогично для i!=v.size()) на сегодняшний день контейнеры STL. (Если будущий STL изменяет свой файл size_type, который будет подписан, даже этот последний недостаток исчезнет.)

Однако было бы безнадежно (и неловко) пытаться научить людей регулярно писать for (ptrdiff_t я =... ;... ;...). (Даже "Руководства" в настоящее время используют его только в одном месте, а "плохой" пример не связан с индексированием ".)

Поэтому мы должны предоставить gsl::index (который позже может быть предложен для рассмотрения как std::index) как typedef для ptrdiff_t, поэтому мы можем надеяться (и не стыдно) научить людей регулярно писать (index я =... ;... ;...).

Почему бы просто не попросить людей написать ptrdiff_t? Потому что мы считаем, что было бы неловко рассказать людям, что вы должны делать в C++, и даже если бы мы этого не сделали, люди не сделают этого. Написание ptrdiff_t слишком уродливое и непривлекательное по сравнению с auto и int. Точка добавления index имени - сделать его максимально простым и привлекательным, чтобы использовать тип подписанного типа с правильным размером.

Изменение: Больше логики от Herb Sutter

Достаточно ли ptrdiff_t? Да. Стандартные контейнеры уже не должны содержать больше элементов, чем может быть представлено ptrdiff_t, потому что вычитание двух итераторов должно соответствовать разности.

Но действительно ли ptrdiff_t достаточно большой, если у меня есть встроенный массив char или byte который больше половины размера адресного пространства памяти и поэтому имеет больше элементов, чем может быть представлен в ptrdiff_t? Да. C++ уже использует знаковые целые числа для индексов массива. Поэтому используйте index по умолчанию для большинства приложений, включая все встроенные массивы. (Если вы сталкиваетесь с крайне редким случаем массива или типа типа массива, который больше половины адресного пространства и чьими элементами являются sizeof(1), и вы осторожны, избегая проблем с усечением, продолжайте и используйте size_t для индексов только в этом очень специальном контейнере. Такие животные на практике очень редки, и когда они возникают, часто не будут индексироваться непосредственно по пользовательскому коду. Например, они обычно возникают в диспетчере памяти, который берет на себя системное распределение и выделяет отдельные меньшие распределения, которые используют его пользователи, или в MPEG или аналогичном, который предоставляет свой собственный интерфейс, и в обоих случаях size_t должен быть необходим только внутри диспетчера памяти или реализации класса MPEG.)

Ответ 2

Я пришел к этому с точки зрения старого таймера (pre C++)... В тот день было понято, что int является родным словом платформы и, вероятно, даст лучшую производительность.

Если вам нужно что-то большее, то вы будете использовать его и заплатить цену за производительность. Если вам нужно что-то меньшее (ограниченная память или конкретная потребность в фиксированном размере), то же самое. В противном случае используйте int. И да, если бы ваше значение находилось в диапазоне, где int на одной целевой платформе мог вместить его, а int на другой целевой платформе не мог... тогда у нас был определенный размер времени компиляции (до того, как они стали стандартизованными, мы сделали свои собственные).

Но теперь, на сегодняшний день, процессоры и компиляторы намного сложнее, и эти правила не применяются так легко. Также сложнее предсказать, какое влияние на ваш выбор повлияет на какую-то неизвестную будущую платформу или компилятор... Как мы действительно знаем, что uint64_t, например, будет работать лучше или хуже, чем uint32_t для какой-либо конкретной будущей цели? Если вы не являетесь гуру процессора/компилятора, вы не...

Итак... возможно, это старомодно, но если я не пишу код для ограниченной среды, такой как Arduino, и т.д. Я все еще использую int для значений общего назначения, которые, как я знаю, будут находиться в пределах int по всем разумным целям для приложения, которое я пишу, И компилятор берет его оттуда... В наши дни это означает, что 32 бита подписываются. Даже если предположить, что 16 бит - это минимальный целочисленный размер, он охватывает большинство случаев использования. И варианты использования для чисел, которые больше, чем они, легко идентифицируются и обрабатываются соответствующими типами.

Ответ 3

Большинство программ не живут и не умирают на краю нескольких циклов процессора, а int очень легко писать. Однако, если вы чувствительны к производительности, я предлагаю использовать целые типы фиксированной ширины, определенные в <cstdint>, такие как int32_t или uint64_t. Они имеют преимущество в том, чтобы быть очень ясными в их предполагаемом поведении в отношении того, чтобы быть подписанными или неподписанными, а также их размер в памяти. Этот заголовок также включает в себя быстрые варианты, такие как int_fast32_t, которые, как минимум, имеют размер, но могут быть больше, если это помогает производительности.

Ответ 4

Нет формальной причины использовать int. Это не соответствует ни одному нормальному стандарту. Для индексов вы почти всегда хотите целое число со знаком указателя.

Тем не менее, набрав int похоже, что вы только что сказали, что эй Ритчи и набирать std::ptrdiff_t чувствует, что Страуструп просто ударил вас в задницу. Кодеры тоже люди, не приносят слишком много уродства в свою жизнь. Я бы предпочел использовать long или некоторый легко типизированный index типа typedef вместо std::ptrdiff_t.

Ответ 5

Это несколько основано на мнениях, но, увы, этот вопрос тоже требует.

Прежде всего, вы говорите о целых и индексах, как если бы они были одним и тем же, что не так. Для любой такой вещи, как "целое число, не уверен, какой размер", просто используя int конечно, большую часть времени, по-прежнему уместно. Это работает отлично в большинстве случаев, для большинства приложений, и компилятор им удобен. По умолчанию это прекрасно.

Для индексов массивов это другая история.

На сегодняшний день существует одна формально правильная вещь, и что std::size_t. В будущем может быть std::index_t который делает намерение более ясным на исходном уровне, но пока нет.
std::ptrdiff_t как индекс "работает", но так же неверен, как и int поскольку он допускает отрицательные индексы.
Да, это происходит так, как считает г-н Саттер, но я прошу отличить. Да, на уровне инструкции на языке ассемблера это поддерживается просто отлично, но я все же возражаю. В стандарте говорится:

8.3.4/6: E1[E2] идентичен *((E1)+(E2)) [...] Из-за правил преобразования, которые применяются к +, если E1 является массивом, а E2 - целым числом, то E1[E2] относится к элементу E2 -th E1.
5.7/5: [...] Если оба операнда указателя и результат указывают на элементы одного и того же объекта массива или один за последним элементом объекта массива [...], в противном случае поведение не определено.

Подписка на массив относится к элементу E2 -th E1. Нет такой вещи, как отрицательный элемент -th массива. Но что более важно, арифметика указателя с отрицательным аддитивным выражением вызывает неопределенное поведение.

Другими словами: подписанные индексы любого размера являются неправильным выбором. Индексы являются неподписанными. Да, подписанные индексы работают, но они все еще неправы.

Теперь, хотя size_t по определению является правильным выбором (целочисленный тип без знака, который достаточно велик, чтобы содержать размер любого объекта), может быть спорным, действительно ли это хороший выбор для среднего случая или по умолчанию.

Будьте честны, когда вы в последний раз создали массив с 10 19 элементами?

Я лично использую unsigned int по умолчанию, потому что 4 миллиарда элементов, которые это допускает, достаточно для (почти) каждого приложения, и он уже подталкивает средний пользовательский компьютер, довольно близкий к его пределу (если просто подписывать массив целых чисел, что предполагает выделение 16 ГБ смежной памяти). Я лично считаю дефолт по 64-битным индексам смешным.

Если вы программируете реляционную базу данных или файловую систему, то да, вам понадобятся 64-разрядные индексы. Но для средней "нормальной" программы 32-разрядные индексы достаточно хороши, и они потребляют лишь половину хранения.

Если вы держите вокруг значительно больше, чем несколько индексов, и если я могу себе позволить (потому что массивы не превышают 64k элементов), я даже спускаюсь к uint16_t. Нет, я не шучу.

Является ли хранилище действительно такой проблемой? Это смешно жадничать о двух или четырех байтах, не так ли? Ну нет...

Размер может быть проблемой для указателей, поэтому достаточно уверен, что это может быть и для индексов. X32 ABI не существует ни по какой причине. Вы не заметите накладных расходов на ненужные большие индексы, если у вас всего лишь несколько из них (точно так же, как указатели, они все равно будут в реестрах, никто не заметит, имеют ли они 4 или 8 байт).

Но подумайте, например, о карте слота, где вы храните индекс для каждого элемента (в зависимости от реализации, два индекса на элемент). О, черт возьми, это наверняка делает облом разницы: каждый раз вы нажимаете L2, или у вас есть промахи в кеше при каждом доступе! Больше не всегда лучше.

В конце дня вы должны спросить себя, за что вы платите, и за то, что получаете взамен. Имея это в виду, моя рекомендация в стиле:

Если это стоит вам "ничего", потому что у вас есть только один указатель и несколько индексов, чтобы держать вокруг, то просто используйте то, что формально правильно (это будет size_t). Формально правильно, хорошо, правильно всегда работает, оно читаемо и интеллектуально, и правильно... никогда не ошибается.

Если, однако, это стоит вам (у вас может быть несколько сотен или тысяч или десять тысяч индексов), и то, что вы вернете, ничего не стоит (потому что, например, вы даже не можете хранить 2 20 элементов, поэтому можете ли вы подписаться на 2 32 или 2 64 не имеет значения), вы должны подумать дважды о том, чтобы быть слишком расточительным.

Ответ 6

На большинстве современных 64-разрядных архитектур int составляет 4 байта, а ptrdiff_t - 8 байтов. Если ваша программа использует множество целых чисел, использование ptrdiff_t вместо int может удвоить ваши потребности в программной памяти.

Также учтите, что современные процессоры часто сталкиваются с недостатками производительности памяти. Использование 8-байтовых целых чисел также означает, что ваш кеш процессора теперь имеет пополам столько элементов, как раньше, поэтому теперь он должен ждать медленную основную память чаще (что может легко принять несколько сотен циклов).

Во многих случаях стоимость выполнения операций с 32-на-64-битным преобразованием полностью затмевает производительность памяти.

Итак, это практическая причина, по которой int по-прежнему пользуется популярностью на 64-битных машинах.

  • Теперь вы можете рассуждать о двух десятках разных целых типов и переносимости, а также о стандартных комитетах и всем, но правда в том, что для многих C++ программ, написанных там, существует "каноническая" архитектура, о которой они думают, что часто единственная архитектура, о которой они когда-либо беспокоились. (Если вы пишете программу 3D-графики для игры в Windows, вы уверены, что она не будет работать на мэйнфрейме IBM.) Поэтому для них вопрос сводится к следующему: "Нужно ли мне 4-байтовое целое или 8-байтовый здесь? "

Ответ 7

Мой совет для вас - не слишком много смотреть на вывод ассемблера, не слишком беспокоиться о том, какой размер каждой переменной есть, а не говорить такие вещи, как "компилятор чувствует себя как дома". (Я действительно не знаю, что вы подразумеваете под этим последним).

Для целых чисел сада, которые большинство программ заполнены, простой int должен быть хорошим типом для использования. Он должен быть естественным размером слова машины. Он должен быть эффективным в использовании, не теряя ненужной памяти и не вызывая большого количества дополнительных преобразований при перемещении между памятью и регистрами вычислений.

Теперь верно, что существует множество специализированных применений, для которых простой int больше не подходит. В частности, размеры объектов, количество элементов и индексы в массивах почти всегда size_t. Но это не значит, что все целые числа должны быть size_t !

Также верно, что смеси типов подписи и без знака, а также смеси разных размеров могут вызывать проблемы. Но большинство из них хорошо разбираются в современных компиляторах и предупреждениях, которые они испускают для небезопасных комбинаций. Поэтому, пока вы используете современный компилятор и обратите внимание на его предупреждения, вам не нужно выбирать неестественный тип, чтобы попытаться избежать проблем с несоответствием типов.

Ответ 8

Я не думаю, что есть настоящая причина использования int.

Как выбрать целочисленный тип?

  • Если это для операций с битами, вы можете использовать неподписанный тип, иначе использовать подписанный
  • Если это связано с памятью (индекс, размер контейнера и т.д.), Для которых вы не знаете верхнюю границу, используйте std::ptrdiff_t (единственная проблема заключается в том, что размер больше PTRDIFF_MAX, что редко встречается в практика)
  • В противном случае используйте intXX_t или int(_least)/(_fast)XX_t.

Эти правила охватывают все возможные способы использования int, и они дают лучшее решение:

  • int не подходит для хранения связанных с памятью вещей, так как его диапазон может быть меньше индекса (это не теоретическая вещь: для 64-битных машин int обычно 32-бит, поэтому с помощью int вы можете обрабатывать только 2 миллиарда элементов)
  • int не подходит для хранения "общих" целых чисел, так как его диапазон может быть меньше необходимого (неопределенное поведение происходит, если диапазон недостаточен), или, наоборот, его диапазон может быть намного больше, чем необходимо (так что память пропадает впустую)

Единственная причина, почему можно было бы использовать int, если один делает расчет, и знает, что диапазон вписывается в [-32767; 32767]. (Стандарт гарантирует только этот диапазон Обратите внимание на то, что реализации свободны предоставить больше размера int s, и они обычно делают это. В настоящее время int 32-бит на многих платформах).

Поскольку упомянутые типы std немного утомительны для записи, можно было бы typedef их короче (я использую s8/u8/.../s64/u64 и spt/upt ("(un) подписанный тип размера указателя") для ptrdiff_t/size_t. Я использую эти typedefs в течение 15 лет, и я никогда не писал ни одного int с...).

Ответ 9

профессионал

Легче печатать, наверное? Но вы всегда можете typedef.

Многие API-интерфейсы используют int, включая части стандартной библиотеки. Это исторически вызвало проблемы, например, при переходе на 64-битные размеры файлов.

Из-за правил продвижения по типу по умолчанию типы, более узкие, чем int, могут быть расширены до int или unsigned int, если вы не добавляете явные приведения во многих местах, а множество разных типов может быть уже чем int при некоторой реализации где-то. Итак, если вы заботитесь о переносимости, то это небольшая головная боль.

Против

Я также использую ptrdiff_t для индексов, большую часть времени. (Я согласен с Google в том, что индексы без знака являются аттракторами ошибок.) Для других видов математики theres int_fast64_t. int_fast32_t и т.д., который также будет лучше или лучше, чем int. Почти нет систем реального мира, за исключением нескольких несуществующих Unices из прошлого века, используют ILP64, но есть много процессоров, где вам понадобится 64-битная математика. И компилятор технически разрешен стандартным образом, чтобы разорвать вашу программу, если ваш int превышает 32 767.

Тем не менее, любой компилятор C, достойный его соли, будет протестирован на большом количестве кода, который добавит int к указателю во внутреннем цикле. Так что он не может сделать что-то слишком тупые. Самым худшим сценарием на современном оборудовании является то, что ему нужна дополнительная инструкция для подписывания - расширение 32-разрядного знакового значения до 64 бит. Но, если вы действительно хотите, это самая быстрая математика указателя, самая быстрая математика для значений с величиной между 32 киби и 2 gibi или наименее потраченная впустую записка, вы должны сказать, что вы имеете в виду, а не компилятор.

Ответ 10

Я думаю, что в 99% случаев нет причин использовать int (или целое число со знаком других размеров). Тем не менее, есть еще ситуации, когда использование int является хорошим вариантом.


Выступление:

Одна разница между int и size_t заключается в том, что i++ может быть неопределенным поведением для int - если i MAX_INT. На самом деле это может быть хорошо, потому что компилятор может использовать это неопределенное поведение для ускорения работы.

Например, в этом вопросе разница была -fwrapv фактором 2 между использованием неопределенного поведения и использованием флага компилятора -fwrapv который запрещает этот эксплойт.

Если мой рабочий конь-для цикла становится в два раза быстрее, используя int - уверен, что я буду использовать его


B) Меньше кода, подверженного ошибкам

Перевернутые for-loops с size_t выглядят странно и являются источником ошибок (я надеюсь, что все правильно):

for(size_t i = N-1; i < N; i--){...}

Используя

for(int i = N-1; i >= 0; i--){...}

вы заслужили бы благодарность менее опытных программистов на С++, которым когда-нибудь придется управлять своим кодом.


C) Разработка с использованием подписанных индексов

Используя индексы int качестве индексов, вы можете сигнализировать о неправильных значениях/вне диапазона с отрицательными значениями, что может пригодиться и может привести к более четкому коду.

  1. "найти индекс элемента в массиве" может вернуть -1 если элемент отсутствует. Для обнаружения этой "ошибки" вам не нужно знать размер массива.

  2. бинарный поиск может возвращать положительный индекс, если элемент находится в массиве, и -index для позиции, в которой элемент будет вставлен в массив (и не находится в массиве).

Очевидно, что одна и та же информация может быть закодирована с положительными значениями индекса, но код становится несколько менее интуитивным.


Ясно, что есть также причины выбора int over std::ptrdiff_t - один из них - пропускная способность памяти. Существует много алгоритмов, связанных с памятью, для них важно уменьшить объем памяти, передаваемой из ОЗУ в кеш.

Если вы знаете, что все числа меньше 2^31 что было бы преимуществом для использования int потому что в противном случае половина передачи памяти будет писать только 0 о которых вы уже знаете, что они есть.

Примером являются сжатые разреженные строки (crs) - их индексы сохраняются как ints и не long long. Поскольку многие операции с разреженными матрицами связаны с памятью, действительно существует разница между использованием 32 или 64 бит.