Здравствуйте, reversecode, Вы писали:
_>>И кто еще пользуется этим устаревшим ARMv4 ?
R>https://godbolt.org/z/vpt9Tw R>раскомментируйте unsigned и увидите на сколько уменьшиться выхлоп
Ага и еще добавить -O2
Лучше сравните такое:
int main() {
//unsigned int a = 3,
b = 1;
return a > b;
}
Здравствуйте, Евгений Музыченко, Вы писали:
SVZ>>В коде без разницы, а под отладчиком очень большая разница — увидеть "-2" и "-1" или "4294967294" и "4294967295".
ЕМ>Кстати, есть ли гуйные отладчики, умеющие при наведении показывать такое в заданных альтернативных форматах?
Если ручками в wacth добавить с кастом. Но это гемор
V>Имхо, приведенная рекомендация не релевантна. Интересно услышать длинный ответ.
В общем-то, есть довольно много материалов, посвященных проблемам, связанными с беззнаковыми целыми. Как правило, рассматриваются проблемы производительности или баги. Классический пример проблемы производительности выглядит так:
void baz(char );
void foo(char* begin, char* end) {
for (int i = 0; i < (end - begin); ++i)
baz(begin[i]);
}
Данный код предполагает, что расстояние между началом и концом уменьшается в 32 бита, и в рамках этого предположения работает отлично. Если заменить тип управляющей переменной на unsigned int, скорость кода заметно уменьшится. Связано это с тем, что компилятору придется контролировать переполнение на каждой операции (как правило, путем засовывания числа в отдельный короткий регистр), так как беззнаковые целые разрешают переполнение. Классическая рекомендация — использовать знаковое целое.
Очевидно, что проблему можно решить и более правильно — путем использования уместного типа, std::ptrdiff_t. Ирония заключается в том, что ptrdiff_t — знаковый тип.
Багов связанных с неправильным использованем беззнаковых целых много. Большинство из них связаны с тем, что стандартная арифметика на них не работает. Она, конечно, не работает и на знаковых числах на концах их диапазона, однако на беззнаковых она не работает в районе нуля. Из практики, средний код чаще работает с числами в районе нуля, чем с числами на конце диапазона знаковых целых.
В частности, классический пример: из школьной математики мы помним, что если A + B > C, то A > B — C. На беззнаковых целых это не работает при А = В = 2, С = 3. Подобное поведение, как правило, является довольно удивительным, потому что большинство практических программистов привыкли к школьной математике, которую они учили в четвертом классе, а не к программированию на С++, которое они учили в колледже.
Кроме того, в С++ в математических операциях, смешивающих знаковые и беззнаковые числа, знаковые приводятся к беззнаковым, после чего операция выполняется по правилам беззнаковых. Это тоже часто становится сюрпризом, классичский пример:
bool less()
{
int a = -1;
unsigned int b = 1;
return a < b; // что вернет?
}
Подытоживая — сами по себе беззнаковые числа не являются потенциально опасными. Они ведут себя в строгом соответствии с правилами. Проблема в том, что правила эти противоречат школьной арифметике, и как правило, являются неприятной неожиданностью для многих программистов. В связи с этим, сообщество выработало рекомендации не пользоваться беззнаковыми целыми для математических операций, включая индексацию, и использовать их там, где их примение оправдано — в основном, битовые операции и "чистые" счетчики, например, счетчик циклов в процессоре.
Здравствуйте, PM, Вы писали:
PM>Те люди, кто для знаковых индексов обычно используют int, используют его же не задумываясь для количества элементов в контейнеров,
Ну скорее всего это не криминал. 2 миллиарда элементов в контейнере — это, воля ваша, что-то не то. Если иметь в виду, что гранулярность выделения по malloc, ЕМНИП, 16 байт, то это 32 Гигабайта. Едва ли в большинстве задач такое возможно. Да и скорее всего тут будет что-то более сложное, чем простой контейнер.
Здравствуйте, Pavel Dvorkin, Вы писали:
PM>>Те люди, кто для знаковых индексов обычно используют int, используют его же не задумываясь для количества элементов в контейнеров,
PD>Ну скорее всего это не криминал. 2 миллиарда элементов в контейнере — это, воля ваша, что-то не то. Если иметь в виду, что гранулярность выделения по malloc, ЕМНИП, 16 байт, то это 32 Гигабайта. Едва ли в большинстве задач такое возможно. Да и скорее всего тут будет что-то более сложное, чем простой контейнер.
2 миллиарда байт в векторе — вполне себе норм, и ровно 2 гига + 15 байт на гранулярность
>>размера и позиции в файловых потоках.
PD>А вот это да. Это непростительно.
Нормально. Знак может показывать, откуда отсчитываем — от начала или конца. Какая разница, знаковый или беззнаковый?
Здравствуйте, Homunculus, Вы писали:
H>Каждый раз перед обращением к выбранному должно проверяться — а есть ли выбранный.
Тогда, повторю, любое спецзначение, заведомо отличное от валидного индекса, можно использовать для обозначения отсутствия выбора.
H>Потому что индекс может быть равен 65525. Ну чисто так, по логике. Может же? Может. Только вот это будет битый индекс. H>А "-1" он не может быть равен никак.
Да почему не может-то? Отрицательные индексы допустимы по правилам C/C++. А если индекс хранится в 16-разрядной переменной, то 65525 полностью эквивалентно -11.
H>если ты забьешь невалидное значение именно как max_unsigned_short, например, то при смене размерности массива до long — ты огребешь проблем.
Чтобы не огрести проблем, очевидно же, что разрядность индекса (а не размерность массива) должна определяться рядом со спецзначением "не выбрано". Вообще, любые связанные параметры должны определяться рядом.
Ну, ладно, используй unsigned, чо могу сказать
Я сказал почему я не использую, спорить дальше смысла не вижу, но и использовать я их не начну, ну если уж только в башку не стукнет доя каких-нибудь констант. Хотя... не. Там же с ними сравнения какие-нибудь могут быть. Не, нафиг эти анзигнед
Хотя не, один случай в голову пришел. Чтоб избавиться от ворнингов, связанных с size_t. Да и то, только по месту если тип приводить.
Здравствуйте, Marty, Вы писали:
PD>>Ну скорее всего это не криминал. 2 миллиарда элементов в контейнере — это, воля ваша, что-то не то. Если иметь в виду, что гранулярность выделения по malloc, ЕМНИП, 16 байт, то это 32 Гигабайта. Едва ли в большинстве задач такое возможно. Да и скорее всего тут будет что-то более сложное, чем простой контейнер. M>2 миллиарда байт в векторе — вполне себе норм, и ровно 2 гига + 15 байт на гранулярность
Там, где это возможно, давно 64-битные указатели, а в языках без плохого наследия типы смещения тоже 64-битные.
Здравствуйте, netch80, Вы писали:
PD>>>Ну скорее всего это не криминал. 2 миллиарда элементов в контейнере — это, воля ваша, что-то не то. Если иметь в виду, что гранулярность выделения по malloc, ЕМНИП, 16 байт, то это 32 Гигабайта. Едва ли в большинстве задач такое возможно. Да и скорее всего тут будет что-то более сложное, чем простой контейнер. M>>2 миллиарда байт в векторе — вполне себе норм, и ровно 2 гига + 15 байт на гранулярность
N>Там, где это возможно, давно 64-битные указатели, а в языках без плохого наследия типы смещения тоже 64-битные.
Я не об этом. Я о том, что коллега не слишком корректно обошелся с гранулярностью
Здравствуйте, T4r4sB, Вы писали:
TB>Здравствуйте, Шахтер, Вы писали:
Ш>>Это из-за багов в генах.
TB>Поясни. Тебе выкатили простыню из кучи случаев, где отсутствие знака в типе переменной провоцирует ошибки. Ты молча влепил минус без обоснования. Давай, поясняй.
Ошибки провоцирует не отсутствие знака а безграмотность.
Здравствуйте, Nuzhny, Вы писали:
N>Да, давай узнаем, зачем нужно кольцо целых чисел
Этого как раз узнавать не нужно. Нужно узнать, для чего математика продолжает пользоваться понятием натурального числа после создания алгебраических структур вроде колец. Вы где-нибудь видели математическую запись суммы или произведения по i/k, где было бы оговорено, что i/k принадлежат кольцу целых чисел?
N>и все ли свои операции с беззнаковыми ты выполняешь в моноиде натуральных чисел или регулярно выходишь за пределы в видео кастов к другим типам. Статистика будет?
Лично у меня статистика весьма показательная — касты используются исключительно там, где передавать натуральные числа под видом целых вынуждают сторонние API.
Здравствуйте, Vamp, Вы писали:
V>В общем-то, есть довольно много материалов, посвященных проблемам, связанными с беззнаковыми целыми. Как правило, рассматриваются проблемы производительности или баги. Классический пример проблемы производительности выглядит так:
V>
V>void baz(char );
V>void foo(char* begin, char* end) {
V> for (int i = 0; i < (end - begin); ++i)
V> baz(begin[i]);
V>}
V>
V>Данный код предполагает, что расстояние между началом и концом уменьшается в 32 бита, и в рамках этого предположения работает отлично. Если заменить тип управляющей переменной на unsigned int, скорость кода заметно уменьшится. Связано это с тем, что компилятору придется контролировать переполнение на каждой операции (как правило, путем засовывания числа в отдельный короткий регистр), так как беззнаковые целые разрешают переполнение. Классическая рекомендация — использовать знаковое целое.
Ноги у приведенной проблемы растут из за того что для адресных манипуляций используется слишком маленький тип. Правила расширения знаковых от 32 до 64бит дают такой эффект, что компилятору не приходится контролировать размер. Для без-знаковых — такого эффекта нет и компилятору приходится это делать вручную. Если этот код загнать на платформу, в которой размер int равен размеру указателя — беззнаковый вариант будет, как минимум, не хуже. А обычно он чуть-чуть лучше.
V>Очевидно, что проблему можно решить и более правильно — путем использования уместного типа, std::ptrdiff_t. Ирония заключается в том, что ptrdiff_t — знаковый тип.
Очевидно, что проблему можно решить и более правильно — путем использования уместного типа, std::size_t. Ирония заключается в том, что size_t — без-знаковый тип.
Здравствуйте, Vamp, Вы писали:
V>... V>Багов связанных с неправильным использованем беззнаковых целых много. Большинство из них связаны с тем, что стандартная арифметика на них не работает. Она, конечно, не работает и на знаковых числах на концах их диапазона, однако на беззнаковых она не работает в районе нуля. Из практики, средний код чаще работает с числами в районе нуля, чем с числами на конце диапазона знаковых целых.
V>В частности, классический пример: из школьной математики мы помним, что если A + B > C, то A > B — C. На беззнаковых целых это не работает при А = В = 2, С = 3. Подобное поведение, как правило, является довольно удивительным, потому что большинство практических программистов привыкли к школьной математике, которую они учили в четвертом классе, а не к программированию на С++, которое они учили в колледже.
Чуть выше по теме Pavel Dvorkin называл это "правилами обычной школьной арифметики".. В принципе, согласен, человеку намного легче так думать.
V>Кроме того, в С++ в математических операциях, смешивающих знаковые и беззнаковые числа, знаковые приводятся к беззнаковым, после чего операция выполняется по правилам беззнаковых. Это тоже часто становится сюрпризом, классичский пример:
V>
V>bool less()
V>{
V> int a = -1;
V> unsigned int b = 1;
V> return a < b; // что вернет?
V>}
V>
Блин.. Какой концентрированный пример. Из за концентрированности не видно самой проблемы. Но согласен, жизнь полна сюрпризов.
V>Подытоживая — сами по себе беззнаковые числа не являются потенциально опасными. Они ведут себя в строгом соответствии с правилами. Проблема в том, что правила эти противоречат школьной арифметике, и как правило, являются неприятной неожиданностью для многих программистов. В связи с этим, сообщество выработало рекомендации не пользоваться беззнаковыми целыми для математических операций, включая индексацию, и использовать их там, где их примение оправдано — в основном, битовые операции и "чистые" счетчики, например, счетчик циклов в процессоре.
Здравствуйте, Шахтер, Вы писали:
Ш>Ошибки провоцирует не отсутствие знака а безграмотность.
Ты думаешь, что все эти люди, написавшие std::max(u-3, 0), не знают, как работают беззнаковые числа, да?
Мне это напомнило ранные споры с адептами С, любившими писать код в опасном стиле, "память течёт не от языка, а потому что программист безграмотный и не знает, что после маллока надо делать фри, а порча памяти происходит от безграмотных программистов, не знающих, что выходить за диапазон массива нельзя".
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Лично у меня статистика весьма показательная — касты используются исключительно там, где передавать натуральные числа под видом целых вынуждают сторонние API.
А также при записи выражений типа
for (... i < static_cast<size_t>(some_expression(static_cast<int>(i)))
Поэтому как раз надо просто везде использовать знаковые.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, netch80, Вы писали:
N>Именно в C и C++ у знаковых принципиально лучше с точки зрения производительности, что позиция, что программист всегда позаботился о переполнениях, позволяет массу оптимизаций, типа замены a+1>a на true. Были отзывы о получении 20-50% выигрыша на этом в отдельных характерно интересных случаях.
Ха, интересно, про неопределённость роловера для знаковых и определённость для беззнаковых уловил.
Интересно, как далеко можно пойти на этом неопределённом переполнении знаковых, но принял к сведению.
(MSVC похоже никуда не идёт, он тупо делает реальное сравнение в обоих случаях a + 1 > a)
N>Что сравнивали-то? Intel рисует (почти) противоположное — для длинного умножения времена одинаковы, а для короткого (которому пофиг на знак) на 1-2 такта меньше (3 вместо до 5).
Обычное деление достаточно больших чисел int64_t / uint64_t, компиляция MSVC x64, беззнаковые ощутимо быстрее. Нужно воспроизвести?
AG>>С точки зрения корректности, знаковые как бы ограждают от одного типа ошибок (отрицательное число -> переполнение), но вносят другие (отрицательное число -> выход за диапазон), шило на мыло.
N>А у положительного переполнение у обоих. А так как мелкие числа, включая отрицательные, чаще, чем крупные, то знаковые числа заведомо выигрывают.
В положительную сторону переполняются одинаково. Что до отрицательной, неожиданное отрицательное число, чуть-чуть вышедшее за диапазон, даёт больше вреда, чем такое же положительное, при тех же условиях вышедшее в огромные значения.
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Вы где-нибудь видели математическую запись суммы или произведения по i/k, где было бы оговорено, что i/k принадлежат кольцу целых чисел?
Слово "кольцо" обычно опускают. Но суммы, где индекс отрицательный встречаются очень часто: пределы интегрирования, например. Или я не понял вопроса.