如何检测 C++ 代码中的差一错误 (OBOE)?
How can I detect off-by-one errors (OBOEs) in C++ code?
考虑这个简单的程序:
#include <array>
#include <iostream>
#include <cstdlib>
int main(int argc, char* argv[]) {
std::array<int, 5> arr = {0, 1, 2, 3, 4};
int idx = std::atoi(argv[1]);
int val = std::atoi(argv[2]);
arr[idx] = val;
for (auto i=0u; i <= idx; i++) {
std::cout << "arr[" << i << "] = " << arr[i] << std::endl;
}
}
如果我像这样用 GCC 6.3.1 编译并 link 它:
g++ -O0 -std=gnu++14 -fsanitize=undefined example.cpp
和运行是这样的:
a.out 5 98
我没有收到任何警告,即使我正在写入和读取超过 'arr' 末尾的一个元素(数组在索引 4 处结束)。
如果我运行:
a.out 6 98
我收到一条警告“索引 6 超出类型 'int [5]' 的范围”。在这种情况下,我正在写入和读取 'arr'.
末尾的两个元素
为什么差一的情况不会抛出错误?我猜是因为超出数组末尾的一个元素是有效的内存地址(即迭代器可以指向它?)。你能推荐任何其他可以可靠地检测一个元素越界访问的工具吗?
编辑
我也可以运行:
g++ -O0 -std=gnu++14 -fsanitize=bounds-strict example.cpp
而且我看到了相同的行为,即一个元素越界访问不会触发警告,但是两个元素越界会触发警告。这是我试图理解的部分。
如果你想要边界检查使用array::at
std::array<T,N>::at
:See
#include <array>
#include <iostream>
#include <cstdlib>
int main(int argc, char* argv[])
{
std::array<int, 5> arr = {0, 1, 2, 3, 4};
int idx = std::atoi(argv[1]);
int val = std::atoi(argv[2]);
try
{
arr.at(idx) = val;
}
catch(std::out_of_range var)
{
std::cout<<"Out of bounds"<<std::endl;
}
for (auto i=0u; i <= idx; i++)
{
try
{
std::cout << "arr[" << i << "] = " << arr.at(i) << std::endl;
}
catch(std::out_of_range var)
{
std::cout<<"Out of bounds"<<std::endl;
}
}
}
输出:
Out of bounds
arr[0] = 0
arr[1] = 1
arr[2] = 2
arr[3] = 3
arr[4] = 4
Out of bounds
Why doesn't the off-by-one case throw an error?
因为-fsanitize=undefined
不检测越界访问。
Can you suggest any other tool that can reliably detect out of bounds access by one element?
我建议 gcc
和 -fsanitize=address
。
$ g++ -O0 -std=gnu++14 -fsanitize=address 1.cpp
$ ./a.out 6 98
=================================================================
==119887==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd3b1aa4b8 at pc 0x560be273c4e9 bp 0x7ffd3b1aa450 sp 0x7ffd3b1aa440
WRITE of size 4 at 0x7ffd3b1aa4b8 thread T0
#0 0x560be273c4e8 in main (/tmp/a.out+0x14e8)
#1 0x7f9dafafe001 in __libc_start_main (/usr/lib/libc.so.6+0x27001)
#2 0x560be273c17d in _start (/tmp/a.out+0x117d)
...
这些选项记录在 gcc instrumentations options 中。
旁注:在我的一个项目中,我使用 -fsanitize=address -fsanitize=undefined -fsanitize=leak -fsanitize=pointer-subtract -fsanitize=pointer-compare -fno-omit-frame-pointer -fstack-protector-all -fstack-clash-protection -fcf-protection
。
考虑这个简单的程序:
#include <array>
#include <iostream>
#include <cstdlib>
int main(int argc, char* argv[]) {
std::array<int, 5> arr = {0, 1, 2, 3, 4};
int idx = std::atoi(argv[1]);
int val = std::atoi(argv[2]);
arr[idx] = val;
for (auto i=0u; i <= idx; i++) {
std::cout << "arr[" << i << "] = " << arr[i] << std::endl;
}
}
如果我像这样用 GCC 6.3.1 编译并 link 它:
g++ -O0 -std=gnu++14 -fsanitize=undefined example.cpp
和运行是这样的:
a.out 5 98
我没有收到任何警告,即使我正在写入和读取超过 'arr' 末尾的一个元素(数组在索引 4 处结束)。
如果我运行:
a.out 6 98
我收到一条警告“索引 6 超出类型 'int [5]' 的范围”。在这种情况下,我正在写入和读取 'arr'.
末尾的两个元素为什么差一的情况不会抛出错误?我猜是因为超出数组末尾的一个元素是有效的内存地址(即迭代器可以指向它?)。你能推荐任何其他可以可靠地检测一个元素越界访问的工具吗?
编辑
我也可以运行:
g++ -O0 -std=gnu++14 -fsanitize=bounds-strict example.cpp
而且我看到了相同的行为,即一个元素越界访问不会触发警告,但是两个元素越界会触发警告。这是我试图理解的部分。
如果你想要边界检查使用array::at
std::array<T,N>::at
:See
#include <array>
#include <iostream>
#include <cstdlib>
int main(int argc, char* argv[])
{
std::array<int, 5> arr = {0, 1, 2, 3, 4};
int idx = std::atoi(argv[1]);
int val = std::atoi(argv[2]);
try
{
arr.at(idx) = val;
}
catch(std::out_of_range var)
{
std::cout<<"Out of bounds"<<std::endl;
}
for (auto i=0u; i <= idx; i++)
{
try
{
std::cout << "arr[" << i << "] = " << arr.at(i) << std::endl;
}
catch(std::out_of_range var)
{
std::cout<<"Out of bounds"<<std::endl;
}
}
}
输出:
Out of bounds
arr[0] = 0
arr[1] = 1
arr[2] = 2
arr[3] = 3
arr[4] = 4
Out of bounds
Why doesn't the off-by-one case throw an error?
因为-fsanitize=undefined
不检测越界访问。
Can you suggest any other tool that can reliably detect out of bounds access by one element?
我建议 gcc
和 -fsanitize=address
。
$ g++ -O0 -std=gnu++14 -fsanitize=address 1.cpp
$ ./a.out 6 98
=================================================================
==119887==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd3b1aa4b8 at pc 0x560be273c4e9 bp 0x7ffd3b1aa450 sp 0x7ffd3b1aa440
WRITE of size 4 at 0x7ffd3b1aa4b8 thread T0
#0 0x560be273c4e8 in main (/tmp/a.out+0x14e8)
#1 0x7f9dafafe001 in __libc_start_main (/usr/lib/libc.so.6+0x27001)
#2 0x560be273c17d in _start (/tmp/a.out+0x117d)
...
这些选项记录在 gcc instrumentations options 中。
旁注:在我的一个项目中,我使用 -fsanitize=address -fsanitize=undefined -fsanitize=leak -fsanitize=pointer-subtract -fsanitize=pointer-compare -fno-omit-frame-pointer -fstack-protector-all -fstack-clash-protection -fcf-protection
。