fbpx
维基百科

差一错误

差一错误(英語:Off-by-one error,缩写OBOE)是在计数时由于边界条件判断失误导致结果多了一或少了一的错误,通常指计算机编程循环多了一次或者少了一次的程序错误,属于逻辑错误的一种。比如,程序员在循环中进行比较的时候,本该使用「小于等于」,但却使用了「小于」,或者是程序员没有考虑到一个序列是从0而不是1开始(许多程序语言的数组下标都是这样)。在数学领域,此错误也时有发生。

遍历数组

假设现在有一堆物品,按mn(含)依次编号。那么这堆物品的总数是多少?我们可能会直觉地认为有(n - m)个物品,但这就和正确答案差了一,犯了栅栏错误。正确答案应该是(n - m + 1)个物品。

因此,计算机领域中,涉及范围的时候通常用半开区间来表示,从mn(含)的范围就表示成从mn + 1(不含),以避免栅栏错误。例如,一个迭代五次的循环可以写成0到5的半开区间:

for (i = 0; i < 5; i++)  {  /* 循环体 */  /* 可以在循环体內執行其他程序 */ } 

循环体首次执行时,i等于0,接着i依次变为1、2、3、4。最后,i会变为5,因此i < 5为假(不成立),循环结束。然而,如果在比较中用的是<=(小于等于),循环体则会执行六次:i依次为0、1、2、3、4、5。同样,如果i的初值是1而不是0,那么循环体只会执行四次:i依次为1、2、3、4。这些情况都能产生差一错误。

还有一种情况就是该用while循环的地方却用了do-while循环(反之亦然)。Do-while的循环体至少会执行一次。

程序语言间的差异也会产生混淆。从0开始计数是程序语言最为常见的做法,而有些语言中却以1起始。Pascal语言中可以自定义数组下标的起始值。

栅栏错误

 
n个间隔的直栅栏有n+1条栅栏柱。

栅栏错误(有时也称为电线杆错误或者灯柱错误)是差一错误的一种。如以下问题:

建造一条直栅栏(即不围圈),长30米、每条栅栏柱间相隔3米,需要多少条栅栏柱?

最容易想到的答案10是错的。这个栅栏有10个间隔,11条栅栏柱。

反过来,柱子数给定时,我们也很容易认为间隔数也是这么多。实际上间隔数要比柱子数少一个。

广义上这个问题可以这么表述:

n个电线杆之间有多少个间隔?

如果这一列电线杆不组成一个圈,那么正确答案是n-1,如果在此前提之下,首尾两端也计入间隔,那么正确答案是n+1,如果电线杆围成一圈,那么答案则是n。思考之前必须先明确问题的定义,一个情况下的答案可能并不适用于其他情况。栅栏错误往往就来源于在数物体还是数间隔的选择上出了差错。

除了计算长度,栅栏错误还会发生在其他单位中。例如,时间金字塔是一个每10年放置1块石块,总共要放置120块的公共艺术作品,完成这个作品需要花费1190年,而不是1200年。早期的栅栏错误也与时间有关,儒略历最开始计算闰年的方式不正确,导致每三年会有一次闰年(正常情况应该是每四年,即每三年)。

大数的差一错误一般都不会引起大问题。正是在小数字上,尤其是精确度要求很高的时候,差一错误可能造成灾难。如果负责计算的人员「重蹈覆辙」,差一错误甚至可以累积。

安全隐患

不当使用C标准库中的strncat()函数常常会导致差一错误和安全问题。程序员经常认为strncat()在写入字符串结束符时不会超过最大长度。事实上strncat()会在指定的最大长度之后一字节的位置写入字符串结束符。如下代码:

void foo (char *s)  {  char buf[15];  memset(buf, 0, sizeof(buf));  strncat(buf, s, sizeof(buf)); // 最后一个参数应为 sizeof(buf)-1 } 

差一错误之所以经常在使用C标准库的时候出现,是因为C标准库在需不需要减去一字节这个问题上标准不统一。fgets()strncpy()这些函数在写入的时候不会超过最大长度(fgets()会自行把长度减一,只取回(长度 - 1)字节),而像strncat()这些函数则会越过最大长度。所以程序员必须牢记哪些函数需要减去一。

在某些系统上(小端序架构),差一错误可导致帧指针的最低字节被覆盖,从而使攻击者能够劫持调用例程的局部变量,给漏洞攻击敞开大门。

要避免这类问题,可以使用这些函数的其他改进版本,如strlcat()strlcpy(),这些改进版在写入时会考虑缓冲区能容纳的大小,因此更加「安全」。(上面代码的相应部分改成strlcat(buf, s, sizeof(buf))就能解决问题)

参见

  • 边值分析英语Boundary-value analysis
  • 鸽巢原理
  • 植樹問題日语植木算

参考来源

  • Dijkstra, Edsger Wybe. Why numbering should start at zero (EWD 831). E. W. Dijkstra Archive. University of Texas at Austin. May 2, 2008 [2011-03-16]. (原始内容于2021-02-21). 

差一错误, 此條目已列出參考文獻, 但因為沒有文內引註而使來源仍然不明, 2015年5月31日, 请加上合适的文內引註来改善这篇条目, 英語, error, 缩写oboe, 是在计数时由于边界条件判断失误导致结果多了一或少了一的错误, 通常指计算机编程中循环多了一次或者少了一次的程序错误, 属于逻辑错误的一种, 比如, 程序员在循环中进行比较的时候, 本该使用, 小于等于, 但却使用了, 小于, 或者是程序员没有考虑到一个序列是从0而不是1开始, 许多程序语言的数组下标都是这样, 在数学领域, 此错误也时有发生, . 此條目已列出參考文獻 但因為沒有文內引註而使來源仍然不明 2015年5月31日 请加上合适的文內引註来改善这篇条目 差一错误 英語 Off by one error 缩写OBOE 是在计数时由于边界条件判断失误导致结果多了一或少了一的错误 通常指计算机编程中循环多了一次或者少了一次的程序错误 属于逻辑错误的一种 比如 程序员在循环中进行比较的时候 本该使用 小于等于 但却使用了 小于 或者是程序员没有考虑到一个序列是从0而不是1开始 许多程序语言的数组下标都是这样 在数学领域 此错误也时有发生 目录 1 遍历数组 2 栅栏错误 3 安全隐患 4 参见 5 参考来源遍历数组 编辑假设现在有一堆物品 按m到n 含 依次编号 那么这堆物品的总数是多少 我们可能会直觉地认为有 n m 个物品 但这就和正确答案差了一 犯了栅栏错误 正确答案应该是 n m 1 个物品 因此 计算机领域中 涉及范围的时候通常用半开区间来表示 从m到n 含 的范围就表示成从m到n 1 不含 以避免栅栏错误 例如 一个迭代五次的循环可以写成0到5的半开区间 for i 0 i lt 5 i 循环体 可以在循环体內執行其他程序 循环体首次执行时 i等于0 接着i依次变为1 2 3 4 最后 i会变为5 因此i lt 5为假 不成立 循环结束 然而 如果在比较中用的是 lt 小于等于 循环体则会执行六次 i依次为0 1 2 3 4 5 同样 如果i的初值是1而不是0 那么循环体只会执行四次 i依次为1 2 3 4 这些情况都能产生差一错误 还有一种情况就是该用while循环的地方却用了do while循环 反之亦然 Do while的循环体至少会执行一次 程序语言间的差异也会产生混淆 从0开始计数是程序语言最为常见的做法 而有些语言中却以1起始 Pascal语言中可以自定义数组下标的起始值 栅栏错误 编辑 n个间隔的直栅栏有n 1条栅栏柱 栅栏错误 有时也称为电线杆错误或者灯柱错误 是差一错误的一种 如以下问题 建造一条直栅栏 即不围圈 长30米 每条栅栏柱间相隔3米 需要多少条栅栏柱 最容易想到的答案10是错的 这个栅栏有10个间隔 11条栅栏柱 反过来 柱子数给定时 我们也很容易认为间隔数也是这么多 实际上间隔数要比柱子数少一个 广义上这个问题可以这么表述 n个电线杆之间有多少个间隔 如果这一列电线杆不组成一个圈 那么正确答案是n 1 如果在此前提之下 首尾两端也计入间隔 那么正确答案是n 1 如果电线杆围成一圈 那么答案则是n 思考之前必须先明确问题的定义 一个情况下的答案可能并不适用于其他情况 栅栏错误往往就来源于在数物体还是数间隔的选择上出了差错 除了计算长度 栅栏错误还会发生在其他单位中 例如 时间金字塔是一个每10年放置1块石块 总共要放置120块的公共艺术作品 完成这个作品需要花费1190年 而不是1200年 早期的栅栏错误也与时间有关 儒略历最开始计算闰年的方式不正确 导致每三年会有一次闰年 正常情况应该是每四年 即每隔三年 大数的差一错误一般都不会引起大问题 正是在小数字上 尤其是精确度要求很高的时候 差一错误可能造成灾难 如果负责计算的人员 重蹈覆辙 差一错误甚至可以累积 安全隐患 编辑不当使用C标准库中的strncat 函数常常会导致差一错误和安全问题 程序员经常认为strncat 在写入字符串结束符时不会超过最大长度 事实上strncat 会在指定的最大长度之后一字节的位置写入字符串结束符 如下代码 void foo char s char buf 15 memset buf 0 sizeof buf strncat buf s sizeof buf 最后一个参数应为 sizeof buf 1 差一错误之所以经常在使用C标准库的时候出现 是因为C标准库在需不需要减去一字节这个问题上标准不统一 fgets 和strncpy 这些函数在写入的时候不会超过最大长度 fgets 会自行把长度减一 只取回 长度 1 字节 而像strncat 这些函数则会越过最大长度 所以程序员必须牢记哪些函数需要减去一 在某些系统上 小端序架构 差一错误可导致帧指针的最低字节被覆盖 从而使攻击者能够劫持调用例程的局部变量 给漏洞攻击敞开大门 要避免这类问题 可以使用这些函数的其他改进版本 如strlcat 和strlcpy 这些改进版在写入时会考虑缓冲区能容纳的大小 因此更加 安全 上面代码的相应部分改成strlcat buf s sizeof buf 就能解决问题 参见 编辑边值分析 英语 Boundary value analysis 鸽巢原理 植樹問題 日语 植木算 参考来源 编辑Dijkstra Edsger Wybe Why numbering should start at zero EWD 831 E W Dijkstra Archive University of Texas at Austin May 2 2008 2011 03 16 原始内容存档于2021 02 21 取自 https zh wikipedia org w index php title 差一错误 amp oldid 68229851, 维基百科,wiki,书籍,书籍,图书馆,

文章

,阅读,下载,免费,免费下载,mp3,视频,mp4,3gp, jpg,jpeg,gif,png,图片,音乐,歌曲,电影,书籍,游戏,游戏。