如何检测 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