差一错误
差一错误(,缩写OBOE)是在计数时由于边界条件判断失误导致结果多了一或少了一的错误,通常指计算机编程中循环多了一次或者少了一次的程序错误,属于逻辑错误的一种。比如,程序员在循环中进行比较的时候,本该使用「小于等于」,但却使用了「小于」,或者是程序员没有考虑到一个序列是从0而不是1开始(许多程序语言的数组下标都是这样)。在数学领域,此错误也时有发生。
遍历数组.
假设现在有一堆物品,按"m"到"n"(含)依次编号。那么这堆物品的总数是多少?我们可能会直觉地认为有("n" - "m")个物品,但这就和正确答案差了一,犯了栅栏错误。正确答案应该是("n" - "m" + 1)个物品。
因此,计算机领域中,涉及范围的时候通常用半开区间来表示,从"m"到"n"(含)的范围就表示成从"m"到"n" + 1(不含),以避免栅栏错误。例如,一个迭代五次的循环可以写成0到5的半开区间:
for (i = 0; i
建造一条直栅栏(即不围圈),长30米、每条栅栏柱间相隔3米,需要多少条栅栏柱?
最容易想到的答案10是错的。这个栅栏有10个间隔,11条栅栏柱。
反过来,柱子数给定时,我们也很容易认为间隔数也是这么多。实际上间隔数要比柱子数少一个。
广义上这个问题可以这么表述:
"n"个电线杆之间有多少个间隔?
如果这一列电线杆不组成一个圈,那-{么正确答案是"n"-1,如果在此前提之下,首尾两端也计入间隔,那-{么正确答案是"n"+1,如果电线杆围成一圈,那么答案则是"n"。思考之前必须先明确问题的定义,一个情况下的答案可能并不适用于其他情况。栅栏错误往往就来源于在数物体还是数间隔的选择上出了差错。
除了计算长度,栅栏错误还会发生在其他单位中。例如,时间金字塔是一个每10年放置1块石块,总共要放置120块的公共艺术作品,完成这个作品需要花费1190年,而不是1200年。早期的栅栏错误也与时间有关,儒略历最开始计算闰年的方式不正确,导致每三年会有一次闰年(正常情况应该是每四年,即每隔三年)。
大数的差一错误一般都不会引起大问题。正是在小数-{字上,尤其是精确度要求很高的时候,差一错误可能造成灾难。如果负责计算的人员「重蹈覆辙」,差一错误甚至可以累积。
安全隐患.
不当使用C标准库中的codice_1函数常常会导致差一错误和安全问题。程序员经常认为codice_1在写入字符串结束符时不会超过最大长度。事实上codice_1会在指定的最大长度之后一字节的位置写入字符串结束符。如下代码:
void foo (char *s)
char buf[15];
memset(buf, 0, sizeof(buf));
strncat(buf, s, sizeof(buf)); // 最后一个参数应为 sizeof(buf)-1
差一错误之所以经常在使用C标准库的时候出现,是因为C标准库在需不需要减去一字节这个问题上标准不统一。codice_4和codice_5这些函数在写入的时候不会超过最大长度(codice_4会自行把长度减一,只取回(长度 - 1)字节),而像codice_1这些函数则会越过最大长度。所以程序员必须牢记哪些函数需要减去一。
在某些系统上(小端序架构),差一错误可导致帧指针的最低字节被覆盖,从而使攻击者能够劫持调用例程的局部变量,给漏洞攻击敞开大门。
要避免这类问题,可以使用这些函数的其他改进版本,如codice_8和codice_9,这些改进版在写入时会考虑缓冲区能容纳的大小,因此更加「安全」。(上面代码的相应部分改成codice_10就能解决问题)
生成维基百科快照图片,大概需要3-30秒!