Off by one
"Off by one" 是一种常见的编程错误,通常指的是在程序中错误地访问了数组、循环或其他数据结构的边界。这个错误的名字源于计数通常是从1开始而不是从0开始的情况,导致程序在边界处出现偏差。下面将介绍如何避免它。
1.数组越界
"Off by one" 错误中最常见的形式是数组越界。在C++中,数组的索引是从0开始的,因此如果我们错误地使用了数组长度作为索引,就会导致数组越界。
#include <iostream> int main() { int arr[5] = {1, 2, 3, 4, 5}; // 错误:访问了数组越界 for (int i = 0; i <= 5; ++i) { std::cout << arr[i] << " "; } return 0; }
在这个例子中,循环条件中使用了 <=,导致在 i 变成5时访问了 arr[5],而数组的有效索引只有0到4。这种错误可能会导致程序崩溃或产生不可预测的结果。
2.循环条件错误
另一种"Off by one"错误发生在循环条件中。如果循环条件的边界设置不当,也可能导致多次或少次迭代。
#include <iostream> int main() { // 错误:循环条件导致了多次迭代 for (int i = 0; i < 5; ++i) { std::cout << i << " "; } return 0; }
在这个例子中,循环条件应为 i < 5,但由于错误地设置成 i <= 5,导致循环多次迭代,输出了不必要的值。
3.字符串处理错误
"Off by one"错误也常见于字符串处理,特别是在C-style字符串(以null结尾的字符数组)中。
#include <iostream> #include <cstring> int main() { char str[5]; strcpy(str, "Hello, World!"); // 错误:字符串长度不够,未能包含null终止符 for (int i = 0; i <= 5; ++i) { std::cout << str[i]; } return 0; }
在这个例子中,字符数组 str 的长度为5,但字符串 "Hello, World!" 的长度超过了5,导致未能包含null终止符。这可能导致未定义的行为。
4.避免"Off by one"错误的方法
a.注意数组索引: 确保正确使用数组的索引,不要超出数组的边界。
b.仔细设置循环条件: 在编写循环时,仔细检查循环条件,确保它不会导致多次或少次迭代。
c.使用标准库函数: 在字符串处理时,尽量使用C++标准库中提供的字符串类和函数,如 std::string,以避免手动处理字符串时可能出现的"Off by one"错误。
d.审查代码: 定期审查代码,特别关注循环、数组和字符串处理的部分,检查是否存在潜在的"Off by one"错误。
5.off-by-one 利用思路
1.溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。也可使用 NULL 字节溢出的方法
2.溢出字节为 NULL 字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得 prev_in_use 位被清,这样前块会被认为是 free 块。
(1) 这时可以选择使用 unlink 方法(见 unlink 部分)进行处理。
(2) 另外,这时 prev_size 域就会启用,就可以伪造 prev_size ,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照 prev_size 找到的块的大小与prev_size 是否一致。