Ошибка «off-by-one» или «off-by-one bug» (известная под аббревиатурами OBOE , OBO , OB1 и OBOB ) — это логическая ошибка , которая включает число, которое отличается от предполагаемого значения на +1 или −1. Ошибка «off-by-one» иногда может появляться в математическом контексте. Она часто возникает в компьютерном программировании , когда цикл повторяется на один раз больше или меньше, что обычно вызвано использованием нестрогого неравенства (≤) в качестве условия завершения, когда следовало бы использовать строгое неравенство (<), или наоборот. Ошибки «off-by-one» также возникают из-за путаницы с нумерацией, начинающейся с нуля .
Рассмотрим массив элементов, и элементы от m до n (включительно) должны быть обработаны. Сколько элементов? Интуитивный ответ может быть n − m , но он отклоняется на единицу, показывая ошибку fencepost; правильный ответ n − m + 1 .
По этой причине диапазоны в вычислениях часто представляются полуоткрытыми интервалами ; диапазон от m до n (включительно) представлен диапазоном от m (включительно) до n + 1 (исключительно) для избежания ошибок столба. Например, цикл , который повторяется пять раз (от 0 до 4 включительно), можно записать как полуоткрытый интервал от 0 до 5:
for ( index = 0 ; index < 5 ; index ++ ) { /* Тело цикла */ }
Тело цикла выполняется в первую очередь с индексом , равным 0; затем индекс становится 1, 2, 3 и, наконец, 4 на последовательных итерациях. В этот момент индекс становится 5, поэтому индекс < 5 ложно, и цикл заканчивается. Однако, если бы использованное сравнение было <= (меньше или равно), цикл был бы выполнен шесть раз: индекс принимает значения 0, 1, 2, 3, 4 и 5. Аналогично, если бы индекс был инициализирован как 1, а не как 0, было бы только четыре итерации: индекс принимает значения 1, 2, 3 и 4. Обе эти альтернативы могут вызвать ошибки на единицу.
Другая подобная ошибка может возникнуть, если цикл do-while используется вместо цикла while (или наоборот). Цикл do-while гарантированно выполнится хотя бы один раз.
Связанная с массивами путаница может также возникнуть из-за различий в языках программирования. Нумерация с 0 является наиболее распространенной, но некоторые языки начинают нумерацию массивов с 1. В Pascal есть массивы с определяемыми пользователем индексами. Это позволяет моделировать индексы массивов в соответствии с проблемной областью.
Ошибка столба (иногда называемая телеграфным столбом, фонарным столбом или частоколом ) — это особый тип ошибки «на единицу». Раннее описание этой ошибки появляется в работах Витрувия . [1] Следующая задача иллюстрирует ошибку:
Если вы строите прямой забор длиной 30 футов со столбами, расположенными на расстоянии 3 футов друг от друга, сколько столбов вам понадобится?
Обычный ответ 10 столбов неверен. Этот ответ получается путем деления длины забора на расстояние между каждым столбом, причем частное ошибочно классифицируется как количество столбов. На самом деле забор имеет 10 секций и 11 столбов.
В этом сценарии забор с n секциями будет иметь n + 1 столбов. И наоборот, если забор содержит n столбов, он будет содержать n − 1 секций. Это соотношение важно учитывать при работе с обратной ошибкой. Обратная ошибка возникает, когда известно количество столбов, а количество секций предполагается одинаковым. В зависимости от конструкции забора это предположение может быть верным или неверным.
Следующая задача демонстрирует обратную ошибку:
Если у вас n постов, сколько разделов между ними?
Интерпретация конструкции забора меняет ответ на эту задачу. Правильное количество секций для забора равно n − 1 , если забор представляет собой отдельно стоящий отрезок линии, ограниченный столбом на каждом из его концов (например, забор между двумя проходами), n , если забор образует одну полную отдельно стоящую петлю (например, ограждение, доступное для преодоления, такое как боксерский ринг), или n + 1, если столбы не встречаются на концах забора, похожего на отрезок линии (например, забор между двумя зданиями и закрепленный на стене). Точное определение задачи должно быть тщательно продумано, так как настройка для одной ситуации может дать неправильный ответ для других ситуаций. Ошибки столбов забора возникают из-за подсчета вещей, а не пространств между ними, или наоборот, или из-за пренебрежения тем, следует ли подсчитывать один или оба конца ряда.
Ошибки столбов могут также возникать в единицах, отличных от длины. Например, Пирамида времени , состоящая из 120 блоков, размещенных с интервалом в 10 лет между блоками, должна была построиться за 1190 лет (а не за 1200) от установки первого блока до последнего. Одна из самых ранних ошибок столбов была связана со временем, когда юлианский календарь изначально неправильно рассчитывал високосные годы из-за подсчета включительно, а не исключительно, что давало високосный год каждые три года, а не каждые четыре.
В больших числах ошибка на единицу часто не является серьезной проблемой. Однако в меньших числах и в особых случаях, когда точность имеет первостепенное значение, совершение ошибки на единицу может иметь катастрофические последствия. Иногда такая проблема также будет повторяться и, следовательно, усугубляться, если кто-то передаст неправильный расчет, если следующий человек снова совершит ту же ошибку (конечно, ошибка также может быть отменена).
Примером такой ошибки может служить вычислительный язык MATLAB с функцией linspace()
линейной интерполяции , параметры которой и не . Программист, который неправильно понимает третий параметр как число приращений, может надеяться, что он получит последовательность, но вместо этого получит .(lower value, upper value, number of values)
(lower value, upper value, number of increments)
linspace(0,10,5)
[0, 2, 4, 6, 8, 10]
[0, 2.5, 5, 7.5, 10]
Распространенная ошибка off-by-one, которая приводит к ошибке, связанной с безопасностью, вызвана неправильным использованием стандартной библиотечной strncat
процедуры C. Распространенное заблуждение заключается в strncat
том, что гарантированное завершение null не будет записывать за пределами максимальной длины. В действительности он запишет завершающий нулевой символ на один байт дальше указанной максимальной длины. Следующий код содержит такую ошибку:
void foo ( char * s ) { char buf [ 15 ]; memset ( buf , 0 , sizeof ( buf )); strncat ( buf , s , sizeof ( buf )); // Конечный параметр должен быть: sizeof(buf)-1 }
Ошибки не на единицу распространены при использовании библиотеки C, поскольку она не последовательна в отношении того, нужно ли вычитать 1 байт — функции вроде fgets()
и strncpy
никогда не будут записывать больше указанной им длины ( fgets()
вычитает 1 сам и извлекает только (длина − 1) байт), тогда как другие, вроде strncat
будут записывать больше указанной им длины. Поэтому программист должен помнить, для каких функций нужно вычитать 1.
В некоторых системах ( в частности, в архитектурах с прямым порядком байтов ) это может привести к перезаписи младшего байта указателя кадра . Это может привести к эксплуатируемому состоянию, когда злоумышленник может перехватить локальные переменные для вызывающей процедуры.
Один из подходов, который часто помогает избежать таких проблем, заключается в использовании вариантов этих функций, которые вычисляют, сколько нужно записать, на основе общей длины буфера, а не максимального количества символов для записи. Такие функции включают strlcat
и strlcpy
, и часто считаются «более безопасными», поскольку они позволяют избежать случайной записи за пределами конца буфера. (В примере кода выше вызов strlcat(buf, s, sizeof(buf))
вместо этого устранил бы ошибку.)