Эта программа с ограниченной рекурсией имеет неопределенное поведение? - PullRequest
5 голосов
/ 28 июня 2019

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

#include <cstdint>

using bigint = ::std::uint64_t;
constexpr const bigint add_val = 1442695040888963407;
constexpr const bigint mult_val = 6364136223846793005;

bigint compute(bigint t)
{
   if (t > 1) {
      return add_val + compute(t * mult_val);
   } else {
      return 1;
   }
}

int main(int argc, char const * const argv[])
{
   return compute(argc < 0 ? -argc : argc);
}

Это в основном использование рекурсии для циклического перебора всех значений линейного конгруэнтного генератора случайных чиселс периодом 2 ^ 64 , поэтому в конечном итоге он будет равен 0 или 1.

Я задаю очень четкий и конкретный вопрос.Может ли эта программа, которую кто-либо может скомпилировать и запустить полностью, вызывать неопределенное поведение в соответствии со стандартом C ++?

Ответы [ 3 ]

3 голосов
/ 29 июня 2019

Благодаря стандартной ссылке 101010, я думаю, что соответствующая фраза

4.1 Соответствие реализации [intro.compliance]

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

и

Приложение B (информативное)

Количество реализации [implimits]

в основном учитывает ограничения компилятора (максимальная длина символа, количество символов в строке и т. Д.), Но язык звучит так:

  1. Поскольку компьютеры конечны, реализации C ++ неизбежно ограничены в размерах программ, которые они могут успешно обрабатывать. Каждая реализация должна документировать те ограничения, если они известны . Эта документация может ссылаться на фиксированные ограничения там, где они существуют, например, как вычислять переменные ограничения как функцию от доступных ресурсов, или говорить, что фиксированные ограничения не существуют или неизвестны.

  2. Пределы могут ограничивать количества, в том числе те, которые описаны ниже или другие ...

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

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

Эта программа, которую кто-либо может скомпилировать и запустить полностью, вызывает неопределенное поведение в соответствии со стандартом C ++?

Нет. Но в соответствии с 4.1 / 2.1 реализациям разрешается не компилировать или некорректно выполнять корректную программу.

2 голосов
/ 29 июня 2019

Факты в первую очередь, из проекта стандарта N4800 §7.1 / P4 [expr.pre] ( Emphasis Mine ):

Если во времяПри вычислении выражения результат не определяется математически или не находится в диапазоне представимых значений для его типа, поведение не определено. [Примечание: обработка деления на ноль, формирования остатка с использованием делителя нуля ивсе исключения с плавающей точкой различаются для разных машин и иногда настраиваются библиотечной функцией.- примечание к концу]

Также пункт §6.7.1 / p2 Основные типы [basic.fundamental] ( Акцент на шахте ):

Длякаждому из стандартных целочисленных типов со знаком существует соответствующий (но различный) стандартный целочисленный тип без знака: «unsigned char», «unsigned short int», «unsigned int», «unsigned long int» и «unsigned long long int»,Аналогично, для каждого из расширенных целочисленных типов со знаком существует соответствующий расширенный целочисленный тип без знака.Стандартные и расширенные целочисленные типы без знака вместе называются целочисленными типами без знака.Целочисленный тип без знака имеет тот же показатель диапазона N, что и соответствующий целочисленный тип со знаком. Диапазон представимых значений для типа без знака составляет от 0 до 2 ^ N - 1 (включительно);Арифметика для типа без знака выполняется по модулю 2 ^ N.[Примечание: Арифметика без знака не переполняется.Переполнение для подписанной арифметики приводит к неопределенному поведению (7.1).- конец примечания]

Также §5.13.2 / p2 Целочисленные литералы [lex.icon]

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

enter image description here

Также §7.4 Обычные арифметические преобразования [expr.arith.conv]

(1.5) - В противном случае интегральные преобразования (7.3.6) должны выполняться для обоих операндов 60 .Затем к повышенным операндам должны применяться следующие правила:

  • (1.5.1) - если оба операнда имеют одинаковый тип, дальнейшее преобразование не требуется.

  • (1.5.2) - В противном случае, если оба операнда имеют целочисленные типы со знаком или оба имеют целочисленные типы без знака, операнд с типом меньшего целого ранга преобразования должен быть преобразован в тип операнда с большим рангом.

  • (1.5.3) - В противном случае, если операнд с целочисленным типом без знака имеет ранг, больший или равный рангу типа другого операнда, операнд со знакомЦелочисленный тип должен быть преобразован в тип операнда с целым типом без знака.

  • (1.5.4) - В противном случае, если тип операнда со целым типом со знаком может представлять всезначения типа операнда с целым типом без знака, операнд с целым типом без знака должны быть преобразованы в тип операнда с целым числом со знаком type.

  • (1.5.5) - В противном случае оба операнда должны быть преобразованы в целочисленный тип без знака, соответствующий типу операнда с целым типом со знаком

Итак, вопрос: математически определены ли арифметические результаты в этой программе и находятся ли они в пределах представимых значений для ее типов.Более конкретно, находится ли выражение 1442695040888963407 + compute(t * 6364136223846793005) в диапазоне представимых значений для его типов?

Для этого тип целочисленных литералов 1442695040888963407 и 6364136223846793005 должен упасть до меньшего или равного ранга преобразования с std::uint64_t, чтобы результаты конвертировались в std::uint64_t.К сожалению, такой гарантии нет.

Таким образом, чтобы ваша программа избегала UB, я бы пометил целочисленные литералы LU.

bigint compute(bigint t)
{
   if (t > 1) {
      return 1442695040888963407LU + compute(t * 6364136223846793005LU);
   } else {
      return 1;
   }
}

Теперь о том, почему вы получаете сегментациюошибка, из-за того, что вы переполняете свой стек.Хотя вышеупомянутая программа теоретически не имеет бесконечного количества рекурсий, которое является UB, количество рекурсий исчерпывает ресурсы вашей машины.

0 голосов
/ 29 июня 2019

Авторы Стандарта Стандарта признали, что разные реализации могут и должны различаться по диапазону программ, которые они могли бы обрабатывать с пользой.Хотя Стандарт, возможно, мог бы указать, что качественные реализации должны пытаться с пользой обрабатывать настолько широкий круг задач, насколько это практически возможно, с определением «практичности», оставляемым на усмотрение разработчика, - но они, вероятно, думали, что такое понятие будет само-себеОчевидно.

Если термин «Неопределенное поведение» применяется к каждой программе, для которой Стандарт не предъявляет никаких требований, то все программы будут вызывать Неопределенное поведение, кроме одного предупреждения: соответствующая реализация должна быть способна обрабатываться прихотя бы одну - возможно, надуманную и бесполезную - программу в порядке, установленном Стандартом.Следовательно, если программа не будет выполнять какие-либо действия, которые Стандарт характеризует как неопределенное поведение, Стандарт будет определять поведение программы при запуске соответствующей реализацией, которая была неспособна запустить любую другую программу способом, определеннымСтандартный .Он не навязывает поведение любой программы в любых других обстоятельствах.

...