видел. Пришёл на новую работу, и тут повсеместно используется size_t для того, чтобы "что-то посчитать", т.е. в качестве "целочисленного типа общего назначения". Я полностью согласен с Джоном Калбом, что подобный тип должен быть знаковым (https://youtu.be/wvtFGa6XJDU). Так что size_t - плохой выбор. Долгое время я для этих целей использовал просто int, но т.к. пишу код для 64-битных процессоров, более естественным (и более эффективным: https://gcc.godbolt.org/z/fMEvsn6aE) является int64_t или даже ещё лучше intptr_t.
Но вот беда - его неудобно печатать. Ну то есть надо тянуться пальцами до "64", а потом ещё и зажимать шифт для подчёркивания. К тому же, "64" звучит как некая деталь реализации. Я хочу просто какой-то наиболее подходящий в общем случае целочисленный тип. В котором можно что-то посчитать, поумножать и повычитать, который можно использовать для индексирования массива. Вот если бы я что-то сериализовывал - тогда да, можно было бы явно указать, что там 64 бита. А так - мме.
Вот я бы сделал #define int intptr_t, но мне нравятся мои ноги, не хочу отстреливать. Можно сделать using Int = intptr_t. Но это выглядит как какой-то шибко умный тип. Есть предложение using word = intptr_t, что мне пока кажется наиболее интересным. Но может будут ещё какие-то идеи? Что вообще думаете по этому поводу?
не совсем понял, какой смысл использовать знаковый тип для индексации массива?
1) удивлен, что вы ни разу не упомянули ptrdiff_t 2) 64-битные инты точно не самый эффективный тип для арифметики на 64-битных архитектурах, так как результат сложения или перемножения двух таких переменных в общем случае не умещается в int64_t
Массивы индексировать — size_t или ssize_t, мне кажется. Просто какое-то не очень большое целое — int. Я бы не усложнял. Но и оптимизациями ассемблера не занимался и не особо шарю
Зачем знаковый размер?
и еще нужно сказать, что стандартная библиотека, видимо, навечно останется источником беззнаковых типов в пользовательском коде
Михаил, как думаешь, почему в языке 10 целочисленных типов, а не один? Правильно, чтобы в разных случаях использовать наиболее подходящий для этого случая тип данных. Вопрос "Я хочу просто какой-то наиболее подходящий в общем случае целочисленный тип." - он плохой уже сам по себе. В каждом случае надо подбирать подходящий для этого случая тип, а не ВООБЩЕ.
Как-будто что-то плохое...
арифметику хочется делать на знаковом. а если тебе нужны индексы в явном виде, то часто на них делается арифметика.
1 - тот же intptr_t, только в профиль
2 - не понял, так результат сложения/умножения чисел в любом типе не обязан умещаться в этот тип
возведи в квадрат (int64_max - 1)
в смысле "размер"?
абсолютно верно
академически - верно. на практике они неизбежно начинают встречаться, и в большинстве мест вполне подходит какой-то "тип по умолчанию". речь именно о нём.
И что ты предлагаешь то?
о да, очень плохое. и создатели языка и библиотеки это признали
intptr_t по умолчанию
По умолчанию где, куда?
Когда мне нужно бежать по массиву (если нужен индекс), считать какое-то количество (подлкючений, пользователей), делать арифметику любого рода, сравнивать числа
если бы они еще что-то с этим сделали, помимо ssize
Ну, ты можешь свой код как угодно писать, да, только без UB желательно.
[p1491]
P1491R0: Don’t add to the signed/unsigned mess (by Bjarne Stroustrup) (2019-02-14) (Related: GitHub issue)
Знаковый тип крайне плохо выражает намерения программиста — при каждой индексации есть сомнение вида "а не могло ли нам придти отрицательное число как-нибудь?". Если же тип беззнаковый, такое сомнение возникает куда реже (на моём опыте) — в местах явного каста (не забываем про -Wall -Werror) Т.е. при использовании знаковых мы момент валидации индексов откладываем до момента непосредственного использования, которых, во-первых, больше, во-вторых тут зачастую уже становится непонятно, откуда нам пришли невалидные значения и как исправлять ошибку
Это иллюзия безопаности. Джон как раз объясняет в видео, почему это не так. Если кратко - то у тебя никогда нет уверенности, что в твоём беззнаковом типена самом деле не лежит -1, перевёрнутый в MAX. Но ты никак этого не сможшеь проверить. А если тип знаковый, ты хотя бы сможешь это увидеть.
А откуда уверенность, что в знаковом int64_t не лежит 2^63 - 1?
переполнения нуля вниз - гораздо более частое явление, чем переполнения 2^63 вверх
Я помню этот видос, сумашедший довольно...
Проверки на отрицательность для отлова ошибок — симптоматическое лечение
Там -1 может придти либо из литерала и это довольно просто отследить, либо при любых подозрительных операциях с беззнаковым типом, на которые обязательно пожалуется компилятор и потребует явных кастов, которые заметить куда как легче Различные проявления UB я в расчёт тут не беру, тут сложно и с беззнаковыми, и со знаковыми
эмм. непонятно. ну самый лучший вариант - не делать ошибок, конечно )
если вычитаешь два беззнаковых, и второе оказывается случайно больше первого, то компилятор не поможет. а знаковый тип помог бы
А если вычитаешь два беззнаковых и результат оказывается больше знакового?
если уж совсем честно писать, то std::intptr_t (акцент на std::)
Вы либо так же делаете range check (и одной проверкой, а не двумя), либо любое значение подходит
тогда мы работаем с оцень большими числами. утверждение в том, что обычно мы работаем с числами, которые гораздо меньше 64 бит
это академизм ) intptr_t если он есть, то обязан совпадать с std::intptr_t. а если его нет - то компилятор тебе быстро об этом скажет )
какой range check я сделаю для беззнакового типа?
не всегда известен size(). может вы вычисляете как раз новый size()
size() не бывает (size_t)-1 ?
в смысле бывает ли size() равен max_size_t-1? на 64 битах не бывает. если вам не хватает 63 бит для ваших массивов - вы делаете что-то крайне специфическое. ну, по сравнению с моим доменом
В таком случае вы, вероятнее, заинтересованы не в героическом преодолении отсутствия необходимых диагностик в компиляторе через затачивание всей кодовой базы под их отсутствие, а в наличии таких диагностик
с трудом могу себе представить такую диагностику. ну и речь всё-таки не об этом
Я имел ввиду варн любого вычитания из беззнакового как потенциально небезопасную операцию. Там, где мы полностью уверены в том, что вычитание не может привести к переполнению можно было бы пометить специальным аттрибутом
А можно поподробнее о эффективности? Потому что я вижу разницу только в случае imul с одним операндом: https://uops.info/table.html?search=imul&cb_lat=on&cb_tp=on&cb_uops=on&cb_ports=on&cb_SKL=on&cb_measurements=on&cb_doc=on&cb_base=on
исправил я уже потом понял, что мой аргумент для беззнаковых больше, а знаковые не проседают, потому что переполнение UB
а за uops.info спасибо
Беззнаковые действительно ограничивают возможности оптимизации компилятора из-за фиксированного поведения при переполнении. Речь об этом? Но вроде это никак не связано с размерами типов.
кажется, если пользоваться беззнаковыми типами нативного размера, то гарантии при переполнении обеспечиваются сами собой, но если тип меньше, то может потребоваться неоптимальный код, поэтому размер имеет значение по крайней мере такие выводы напрашиваются из этого примера
хм. Это интересное наблюдение, что u64 даст тот же эффект, что i32. Спасибо.
Обсуждают сегодня