Неопределенное поведение (согласно clang -fsanitize=integer) на libstdc++ std::random из-за отрицательного индекса на движке Mersenne Twister

Я использую clang++ 10 на Ubuntu 20.04 LTS с -fsanitize-undefined-trap-on-error -fsanitize=address,undefined,nullability,implicit-integer-truncation,implicit-integer-arithmetic-value-change,implicit-conversion,integer

Мой код генерирует случайные байты с

    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<uint8_t> dd(0, 255);
    ...
    ch = uint8_t(dd(gen));

Эта последняя строка заставляет дезинфицирующее средство сообщать о неопределенном поведении в bits/random.tcc.

template<...> void  mersenne_twister_engine<...>::
    _M_gen_rand(void)   {
      const _UIntType __upper_mask = (~_UIntType()) << __r;
      const _UIntType __lower_mask = ~__upper_mask;

      for (size_t __k = 0; __k < (__n - __m); ++__k)
      {
         _UIntType __y = ((_M_x[__k] & __upper_mask)
               | (_M_x[__k + 1] & __lower_mask));
         _M_x[__k] = (_M_x[__k + __m] ^ (__y >> 1)
               ^ ((__y & 0x01) ? __a : 0));
      }

      for (size_t __k = (__n - __m); __k < (__n - 1); ++__k)
      {
          _UIntType __y = ((_M_x[__k] & __upper_mask)
                   | (_M_x[__k + 1] & __lower_mask));
          _M_x[__k] = (_M_x[__k + (__m - __n)] ^ (__y >> 1)  <<<<===== this line
               ^ ((__y & 0x01) ? __a : 0));
      }

      _UIntType __y = ((_M_x[__n - 1] & __upper_mask)
               | (_M_x[0] & __lower_mask));
      _M_x[__n - 1] = (_M_x[__m - 1] ^ (__y >> 1)
               ^ ((__y & 0x01) ? __a : 0));
      _M_p = 0;
    }

Ошибка гласит:

/usr/include/c++/10/bits/random.tcc:413:33: runtime error: unsigned integer overflow: 397 - 624 cannot be represented in type 'unsigned long'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/include/c++/10/bits/random.tcc:413:33 in 
/usr/include/c++/10/bits/random.tcc:413:26: runtime error: unsigned integer overflow: 227 + 18446744073709551389 cannot be represented in type 'unsigned long'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/include/c++/10/bits/random.tcc:413:26 in

Похоже, что есть разница __m-__n == 397 - 624, которая явно отрицательна, но все операнды беззнаковые.

Вычитаемые переменные являются параметрами шаблона, определенными как size_t __n, size_t __m, так что это не случайный пограничный случай, а реальный реализуемый шаблон.

Является ли это ошибкой в ​​этой реализации STL или мое использование неверно?

Минимальный воспроизводимый пример: https://godbolt.org/z/vvjWscPnj


ОБНОВЛЕНИЕ: Проблема (не ошибка) зарегистрирована в GCC https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106469 - закрыта как "НЕ ИСПРАВЛЕНО"

Команда GCC назвала переполнение ubsan целого числа без знака clang проверяющим неправильную практику, потому что поведение четко определено (как перенос по модулю) в ISO C++. Хотя в ГПСЧ используется модульная арифметика, в данном конкретном случае это не так.

Однако в большинстве кодов пользовательского пространства беззнаковое переполнение является почти всегда является ошибкой, которую нужно отловить, и эта не-ошибка в GCC STL не позволяет пользователям воспользоваться этой полезной проверкой.

Мне кажется, это скорее ложное срабатывание дезинфицирующего средства UB.

Captain Giraffe 28.07.2022 21:03

Переполнение целого числа без знака не является UB в C++. UBSAN отключает эту проверку по умолчанию. Если вы включите эту проверку, вы должны понимать, что делаете. Обычно сторонний код идет на подавление. Руководство: -fsanitize=unsigned-integer-overflow: переполнение целого числа без знака, когда результат вычисления целого числа без знака не может быть представлен в своем типе. В отличие от переполнения целого числа со знаком, это не неопределенное поведение, а часто непреднамеренное.

273K 28.07.2022 21:06

Я бегу с -ggdb3 -O0 -fsanitize-undefined-trap-on-error -fsanitize=address,undefined,nullability,implicit-integer-tr‌​uncation,implicit-in‌​teger-arithmetic-val‌​ue-change,implicit-c‌​onversion,integer -fno-omit-frame-pointer

MadFred 28.07.2022 21:06

@HFTrader Все параметры -fsanitize=nullability, implicit-integer-truncation, implicit-integer-arithmetic-value-change, implicit-conversion и integer включают проверки UBsan, которые не отмечают фактическое неопределенное поведение. Вы должны быть осторожны, чтобы не включить их для стороннего кода, который может полагаться на поведение, которое они отмечают, и что вы сами не полагаетесь на их поведение. Флаги предназначены только для обозначения поведения часто непреднамеренный. См. clang.llvm.org/docs/UndefinedBehaviorSanitizer.html. Хотя оказывается, что у вас действительно есть библиотека UB, согласно опубликованному ответу.

user17732522 28.07.2022 21:12

Я думаю, нам нужно увидеть минимальный воспроизводимый пример.

Ted Lyngmo 28.07.2022 21:33

@TedLyngmo Поехали: godbolt.org/z/vvjWscPnj

MadFred 28.07.2022 21:38

«Команда GCC сослалась на плохую практику проверки переполнения целочисленного числа без знака ubsan в clang» — что? Я понятия не имею, что это должно было сказать.

user2357112 29.07.2022 12:03

@user17732522 user17732522 Судя по опубликованному ответу, здесь можно использовать uint8_t UB, но это четко определенное беззнаковое переполнение, которое дезинфицирующее средство улавливает и ошибочно вызывает неопределенное поведение.

Jan Hudec 29.07.2022 13:34

@JanHudec Да, я написал ответ, объясняющий это после того, как сделал комментарии выше.

user17732522 29.07.2022 15:57

Хорошая коллекция предупреждающих флагов дезинфицирующих средств! Предполагая, что их поведение не слишком педантично, я должен рассмотреть возможность их использования для моих сборок CI...

saxbophone 31.07.2022 23:08
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
20
10
1 494
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Результат использования uint8_t в std::uniform_int_distribution не определен, поэтому:

std::uniform_int_distribution<uint8_t> dd(0, 255); // Don't do this!

Вместо этого вы можете использовать любой из short, int, long, long long, unsigned short, unsigned int, unsigned long или unsigned long long.

Цитата из rand.req.gen/1,5

Throughout this subclause [rand], the effect of instantiating a template:
that has a template type parameter named IntType is undefined unless the corresponding template argument is cv-unqualified and is one of short, int, long, long long, unsigned short, unsigned int, unsigned long, or unsigned long long.

Если это не помогает, пропустите вариант -fsanitize=integer, так как

-fsanitize=integer: Checks for undefined or suspicious integer behavior (e.g. unsigned integer overflow). Enables signed-integer-overflow

... и переполнение целого числа без знака имеет ли нетнеопределенное поведение. Проверка целочисленного переполнения подписал будет автоматически включена с помощью -fsanitize=undefined, поэтому вам не нужно включать это отдельно.

Если это все еще не помогает, это может быть ошибка в реализации библиотеки gcc, используемой clang++, которая вызывает это. Вы можете попробовать использовать реализацию библиотеки clang++, чтобы увидеть, поможет ли это:

clang++ -stdlib=libc++ ...

Круто, ТИЛ. Теперь мне интересно, почему это так!

Captain Giraffe 28.07.2022 21:10

@CaptainGiraffe Да, я помню, как читал его некоторое время назад и исправил место на cppreference, в котором не упоминалось это ограничение. Мои правки были отменены, пока я не процитировал стандарт. В стандарте не сказано Почему, просто так оно и есть.

Ted Lyngmo 28.07.2022 21:11

Это в стандарте С++? Я не вижу там ссылки на разрешенные типы.

MadFred 28.07.2022 21:16

@HFTrader угорь.is/c++draft/rand.req.genl#1.5

user17732522 28.07.2022 21:19

Боже, как сложно добавить чек в этот шаблон?

MadFred 28.07.2022 21:21

@HFTrader В то время, когда эта функция была добавлена, она не была распространена в аргументах шаблона класса ограничения SFINAE, подобных этому. Если бы это было введено сейчас, я почти уверен, что использовались бы проверки концепции. Но я также не очень понимаю, почему было сделано это ограничение. По-видимому, по этому поводу была проблема LWG, но она была закрыта как не являющаяся дефектом (вместо запроса функции): cplusplus.github.io/LWG/lwg-closed.html#2326

user17732522 28.07.2022 21:25

@TedLyngmo В конце концов, комментирование этой строки не привело к исчезновению сообщения. Проблема все еще в другой строке: std::uniform_int_distribution<uint64_t> ds(1, maxsize); size_t size = ds(gen); где maxsize имеет значения от 2 до 4096. Даже при 2 он срабатывает.

MadFred 28.07.2022 21:32

@HFTrader Хорошо, я не вижу другой причины для UB во фрагменте, который вы нам показали, поэтому, пожалуйста, обновите вопрос и поставьте в нем минимальный воспроизводимый пример.

Ted Lyngmo 28.07.2022 21:34

@TedLyngmo, использующий libc++, фактически заставляет сообщение исчезнуть

MadFred 28.07.2022 21:45

@HFTrader Хорошо! Затем я сильно подозреваю ошибку в реализации библиотеки gcc или в том, как clang++ ее использует.

Ted Lyngmo 28.07.2022 21:46

Не нужно заменять на signed-integer-overflow. -fsanitize=undefined уже включает почти все проверки, которые проверяют фактическое неопределенное поведение, я думаю, за исключением local-bounds (я думаю, из соображений производительности) и float-divide-by-zero (который определен для IEEE 754, но не для C и C++).

user17732522 28.07.2022 22:23

@user17732522 user17732522 Да, я заметил, что «-fsanitize=undefined: все проверки, перечисленные выше, кроме float-divide-by-zero, unsigned-integer-overflow, implicit-conversion, local-bounds и группы проверок nullability-*». Обновил ответ.

Ted Lyngmo 28.07.2022 22:30

«Глупого санитайзера clang, который жалуется на совершенно правильный код» недостаточно, чтобы заключить, что в gcc std::lib есть ошибка.

Jonathan Wakely 29.07.2022 00:24

@JonathanWakely Действительно. Я не пришел к выводу, что это ошибка в gcc std::lib (хотя я сильно подозревал, что это так). Я прочитал обо всех clang конкретных параметрах, используемых OP, и добавил возможное исправление, чтобы прекратить использование -fsanitize=integer к ответу.

Ted Lyngmo 29.07.2022 00:35

Интересно, есть ли у них также другие дезинфицирующие средства, «полагающиеся на явные гарантии в стандарте языка C++». Возможно, дезинфицирующее средство «предполагает, что байт имеет не менее 8 бит» или «предполагает, что std::string.c_str() возвращает буфер с нулевым завершением».

Cody Gray 29.07.2022 09:24

@CodyGray Есть много разных дезинфицирующих средств. Я не думаю, что два, о которых вы упомянули, существуют :-)

Ted Lyngmo 29.07.2022 10:03

@CodyGray, это единственная известная мне ловушка для поведения, которое на 100% гарантирует определенное поведение, которое вовсе не является «неопределенным».

Jonathan Wakely 30.07.2022 02:57

@CaptainGiraffe Почему нельзя использовать std::uniform_int_distribution<uint8_t> и std::uniform_int_distribution<int8_t>?

phuclv 30.07.2022 18:41

Ах, всегда неприятная проблема «унифицированное распределение int не специализировано для char/uint8_t». Я действительно думаю, что это должно быть зарегистрировано как дефект стандарта C++, если это еще не было сделано. Кажется довольно произвольным не поддерживать это (даже если реализация просто оборачивает это для uint16_t и приводит результат, что я делаю сейчас, когда мне нужны случайные байты).

saxbophone 31.07.2022 23:06

@JonathanWakely В ссылке, которой поделился phuclv, вы цитируете слова «Однобайтовые целые числа не поддерживаются намеренно, а не случайное упущение, и поэтому мы должны быть осторожны, просто изменяя это, не консультируясь с разработчиками C++ 11». И, поскольку отчет о дефекте был закрыт как «не дефект», интересно, по какой первоначальной причине не были включены однобайтовые целые числа? Я предполагаю, что те из вас, кто обрабатывал отчет о дефектах, получили мотивацию от разработчиков C++11.

Ted Lyngmo 01.08.2022 01:44

@saxbophone Отчет о дефекте был отправлен и закрыт как «не дефект», как видно из ссылки, которой поделился phuclv.

Ted Lyngmo 01.08.2022 01:50

@JonathanWakely Я нашел ответ на свой вопрос в комментарий Брайану Би, который спросил то же самое: "нет, но если бы мне пришлось угадывать, это потому, что символы не являются целыми числами, а целые числа не являются символами".

Ted Lyngmo 01.08.2022 02:03

Хотя, как указывает другой ответ, в соответствии со стандартом создание экземпляра std::uniform_int_distribution с аргументом шаблона uint8_t является неопределенным поведением, предупреждение UBsan здесь не связано с этим.

UBsan помечает реализацию самого вихря Мерсенна, но реализация не имеет неопределённого поведения или ошибок.

Если вы внимательно посмотрите, то увидите, что оскорбительное выражение

_M_x[__k + (__m - __n)]

где __k — значение в диапазоне от (__n - __m) до (__n - 1) через цикл for.

Все типы, участвующие в этих операциях, являются std::size_t беззнаковыми. Как следствие, все эти операции используют модульную арифметику, и, следовательно, даже если __m - __n отрицательно и не может быть представлено в беззнаковом типе, результат

__k + (__m - __n)

будет лежать между 0 и __m - 1, так что индексация массива с ним не проблема. Никакого неопределенного поведения, неопределенного поведения или поведения, определяемого реализацией, не задействовано.

Проверка UBSan, которая помечает это, не помечает фактическое неопределенное поведение. Совершенно нормально полагаться на циклическое поведение беззнаковой арифметики, подобное этому, если кто-то знает об этом. Проверка неподписанного переполнения предназначена только для пометки случаев такого переноса, когда это не было преднамеренным. Вы не должны использовать его в чужом коде, который может полагаться на него, или в своем собственном коде, если вы можете на него полагаться.

В -fsanitize=address,undefined,nullability,implicit-integer-truncation,implicit-integer-arithmetic-value-change,implicit-conversion,integer все, кроме address и undefined, включают проверки UBsan, которые не отмечают фактическое неопределенное поведение, но во многих случаях обусловливают непреднамеренность май. Флаг дезинфицирующего средства по умолчанию -fsanitize=undefined не включает проверку переполнения целого числа без знака по умолчанию по причинам, указанным выше. Подробнее см. в https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html.

Тогда сообщение вводит в заблуждение SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ...

MadFred 28.07.2022 21:58

@HFTrader Я думаю, они могли бы сформулировать сообщение лучше и использовать для этой проверки что-то еще, кроме «неопределенного поведения», но вы можете получить это сообщение, только если намеренно включите проверку, которую обычно не следует включать, поэтому я думаю, что один можно ожидать, что пользователь будет знать фактическое значение. Возможно, стоит опубликовать отчет об ошибке или запрос функции, чтобы исправить формулировку.

user17732522 28.07.2022 22:01

Меньшее значение mre: godbolt.org/z/9T6nqxdvs . Треки к этому: -fsanitize=unsigned-integer-overflow: Unsigned integer overflow, where the result of an unsigned integer computation cannot be represented in its type. Unlike signed integer overflow, this is not undefined behavior, but it is often unintentional. This sanitizer does not check for lossy implicit conversions performed before such a computation

MadFred 28.07.2022 22:08

Хотя я согласен с тем, что результат будет правильным, тот факт, что собственная libc++ clang не показывает такого поведения, говорит мне, что это не предназначено и работает случайно.

MadFred 29.07.2022 04:24

@MadFred Я уверен, что авторы libstdc++ знают, что делают, и что эта конструкция создана намеренно. Если вы посмотрите с точки зрения, что арифметические операторы выполняют модульную арифметику вместо ограниченной арифметики, такой код имеет смысл и на самом деле часто более элегантен, чем работа в ограниченной арифметике.

user17732522 29.07.2022 05:32

@MadFred Libc++ также не избегает этого полностью. Вместо этого он использует атрибуты для подавления проверки переполнения целого числа без знака там, где на него полагаются. Найдите _LIBCPP_DISABLE_UBSAN_UNSIGNED_INTEGER_CHECK в исходном коде libc++. Libstdc++ не беспокоится о добавлении таких аннотаций, потому что они не поддерживают использование проверки с самого начала. Это просто разница в подходе между разработчиками Clang и GCC.

user17732522 29.07.2022 05:32

Я бы с вами согласился, ЕСЛИ не было бы проще написать вместо этого (__k+__m)-__n. Единственная опасность этого — беззнаковое переполнение k+m, но это кажется невозможным, учитывая, что они являются индексами в массиве. Я считаю, что это просто недосмотр, который прошел тесты.

MadFred 29.07.2022 06:17

@MadFred Дело в том, что сложение в модульной арифметике ассоциативно. Неважно, где вы ставите скобки. Я предполагаю, что они размещены так, как они есть, чтобы указать, что этот индекс перемещается по массиву, сдвинутому на __m-__n из диапазона, в котором сам __k перемещается. Я думаю, в этом отношении это выглядело бы лучше, чем __k - (__n - __m), но на самом деле это не имеет значения.

user17732522 29.07.2022 06:28

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

MadFred 01.08.2022 02:34

@MadFred Если вы сомневаетесь, какой ответ лучше, я с радостью отдам свой ответ этому. Мне нравится мой собственный ответ как руководство к ответу «как решить проблему». Этот ответ копается в нем. Я был бы не против, если бы вы изменили свой голос. Кстати, я дал этому ответу свой голос, как только увидел его.

Ted Lyngmo 08.08.2022 03:26

@TedLyngmo Вот в чем дело: вы помогли мне найти проблему, и это имеет наибольшую ценность, по крайней мере, для меня, и вот мое благодарственное голосование. Админы могут не согласиться.

MadFred 08.08.2022 03:37

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

Ted Lyngmo 08.08.2022 03:41

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

Разработчики GCC правы: неразумно рассматривать неподписанную упаковку все как проблему. Еще более неразумно распечатывать, что это «неопределенное поведение», а не проблема возможный. Если бы ubsan clang сказал вам в первую очередь, что он четко определен в C++ и, возможно, предназначен, вам не пришлось бы беспокоить разработчиков GCC отчетом об ошибке, который им не был полезен. Или вы могли бы сформулировать это как запрос функции после понимания проблемы.

Но вы также правы: с библиотечными функциями в заголовках, где они становятся частью вашего собственного кода, очень сложно отделить библиотечный код (такой как этот PRNG) от вашего собственного кода, когда он встраивается в ту же единицу компиляции. И параметры ubsan для каждого файла.


Реализация libС++ mt19937 отключает эту проверку ubsan, где это необходимо. Это более поздняя реализация стандартной библиотеки C++ с чистого листа, разработанная как часть LLVM и в основном используемая с clang. Если бы какая-либо библиотека заголовков собиралась обслуживать этот санитайзер, который помечает некоторые допустимые операции C++ как проблемы, то это была бы libc++. https://godbolt.org/z/aeY5Yn9c6 показывает, что добавление -stdlib=libc++ к параметрам компиляции в Godbolt позволяет вашему тестовому сценарию работать без сбоев. Вам нужно будет установить его локально, чтобы фактически использовать его.

libc++ определяет макрос препроцессора _LIBCPP_DISABLE_UBSAN_UNSIGNED_INTEGER_CHECK как __attribute__((__no_sanitize__("unsigned-integer-overflow"))) (если поддерживается), поэтому он может отключить его для каждой функции. См., например, <utility> заголовок libcxx, где различные функции используют этот тег, и mersenne_twister_engine<...>::seed() в <random>. Но что интересно, он используется не везде, так что вы все равно можете воспользоваться проверкой переполнения.

Или вы можете написать функцию-оболочку вокруг генерации случайных чисел и поместить ее в отдельный .cpp, который вы компилируете без sanitize=integer. В сборке релиза с -flto он все еще может быть встроен. Или, если вам не нужна такая качественная случайность, используйте libc random(3); он компилируется отдельно, а не встроенный заголовок. random() Linux не ужасен, хотя и не велик. Другие PRNG, такие как xorshift / хороширо, быстры и хороши, но также будут использовать типы unsigned и полагаться на их обертку для умножения и/или добавления/подчинения, если только они не используют только сдвиги и xor, как LFSR.


Невозможно пометить только некоторые неподписанные операции как ожидаемые в ISO C++.

По крайней мере, один язык, Rust, делает решают эту проблему: переполнение диапазона значений всегда является ошибкой для простых +, -, *, / и т. д. для любого целочисленного типа, включая знаковый и беззнаковый. Вы можете использовать x.wrapping_sub(y) для выполнения знакового или беззнакового вычитания с четко определенным переходом. Аналогично для add/mul/div/rem/shift/pow. И есть saturating_sub/add/etc, и overflowing_..., который возвращает обернутый результат и логическое значение, или checked_add/sub/etc, который возвращает тип, который может быть None вместо того, чтобы содержать целое число. Так что, если вы хотите возиться с целочисленными переполнениями, Rust может быть языком для вас.

(Я не удивлюсь, если внутренняя проверка LLVM на неподписанное переполнение была частично мотивирована Rust, и кто-то подумал, что иногда может быть полезно выставить это для использования в C++. Но ожидайте ложных срабатываний в коде, написанном без этой проверки в разум.)


Расширения переполнения GNU C для переноса целых чисел

GCC/Clang и другие компиляторы, которые понимают диалект GNU C и C++, имеют встроенные функции целочисленного переполнения. Это включает в себя как signed, так и unsigned обертку add/sub/mul. Но только для (без подписи) int/long/long long; вам нужно будет выяснить, какой из них использовать для size_t в libstdС++. (например, в Windows x64 size_t должно быть long long, а в x86-64 System V это long)

unsigned long wrapping_sub(unsigned long x, unsigned long y)
{
    // return x - y;       // ISO C++ without working around sanitize=integer

    unsigned long res;
    bool borrow = __builtin_usubl_overflow(x, y, &res);
    return res;
}

Тестовый случай на Godbolt показывает, что __builtin_usubl_overflow безопасно выполняет вычитание с переносом 1UL, 2UL. (Создание asm, который даже не пытается обнаружить перенос, потому что мы сказали компилятору, что это не ошибка в этой операции.) Раскомментирование return x-y; перекрывает переполнение.

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


Поскольку беззнаковая математика четко определена как упаковка, обычной причиной использования неподписанных версий этих встроенных функций GNU C является захват вывода переноса/заимствования, поэтому вы знаете, что если они обернули. Вместо того, чтобы использовать sanitize=integer clang, вы можете использовать эти функции в своих операциях собственныйunsigned и assert(), что результат логического значения является ложным (без переполнения переноса).

Clang поддерживает внешний список игнорирования дезинфицирующего средства, основанный на искаженном имени, а также с подстановочными знаками (clang.llvm.org/docs/SanitizerSpecialCaseList.html), поэтому я думаю, что можно просто добавить в него оскорбительные стандартные библиотечные функции или даже полностью игнорировать стандартные библиотечные функции C++ с несколькими записями форма fun:_ZSt*, fun:_ZNSt* и т. д. Или, возможно, записи в исходном файле также работают для экземпляров шаблона (не тестировалось), и может быть достаточно добавить запись формы src: с подстановочным знаком к заголовкам стандартной библиотеки.

user17732522 29.07.2022 22:35

Mersenne Twister был находкой, когда появился 25 лет назад, но у него есть несколько недостатки. В наши дни я бы серьезно рекомендовал более современный PRNG, например, что-то из семейств ПКГ или xorshift.

PM 2Ring 30.07.2022 21:56

@Peter Cordes Rust по умолчанию обнаружит этот случай в нерелизном коде. Когда что-то было сделано по умолчанию на таком строгом языке, как Rust, вы знаете, что этот вопрос очень важен для пользователя.

MadFred 30.07.2022 23:11

@MadFred: Вы все еще утверждаете, что людям следует избегать написания кода на C++, который намеренно выполняет беззнаковую математику с оберткой по модулю, даже если это то, что им нужно? Как вы предлагаете написать libstdc++? С __builtin_usubl_overflow вместо - в этой и многих других функциях? Или со специфичным для clang __attribute__(()), контролируемым #ifdef, как это использует libc++? Или что этот случай должен был привести к ssize_t и обратно для вычитания со знаком?

Peter Cordes 30.07.2022 23:15

@MadFred: недавно разработанный язык, такой как Rust, имеет хорошие функции, которые позволяют более четко указывать безопасность целочисленного переполнения. Это не то, о чем думали разработчики C в начале 70-х, когда язык разрабатывался, а C++ наследует его семантику. Как я объяснил в своем ответе, ISO C++ не имеет способа указать, что перенос ожидается только для некоторых операций. Если вам не нравится, как работает общий C++, не используйте его. Или используйте только библиотеки (например, libc++), которые обслуживают то подмножество, которое вы хотите использовать, где вы определяете неподписанную упаковку как недопустимую.

Peter Cordes 30.07.2022 23:19

@PeterCordes Как вы считаете (как и команда GCC), что эта проверка (так в оригинале) «глупая» и ее следует удалить из инструментов clang?

MadFred 30.07.2022 23:39

@MadFred: Нет, это полезно иметь, если вы понимаете последствия его использования в мире C++. Как я уже сказал в своем ответе, глупо утверждать, что существует «неопределенное поведение», что вводит в заблуждение и сбивает с толку, и именно это привело к разрыву между вами и командой GCC. Вместо того, чтобы вежливо попросить их обойти дезинфицирующее средство clang, чтобы вы могли использовать его с libstdc++, вы сообщили о не-ошибке с заголовком ошибки, говоря, что это было неопределенное поведение, и предположив, что то, что делает код, было на самом деле неправильным. Естественно, они отнесутся к этому с пренебрежением.

Peter Cordes 30.07.2022 23:46

@MadFred: Кроме того, как указывает пользователь17732522, clang.llvm.org/docs/SanitizerSpecialCaseList.html документирует, как вы можете добавить список игнорируемых функций, чтобы вы могли отфильтровать функции libstdc++, которые могут вызвать ложные срабатывания с этим дезинфицирующим средством.

Peter Cordes 30.07.2022 23:51

Другие вопросы по теме