0

Consider this simple program:

#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;
    }
}

If I compile and link it with GCC 6.3.1 like this:

g++ -O0 -std=gnu++14 -fsanitize=undefined example.cpp

and run it like this:

a.out 5 98

I don't get any warnings, even though I am writing and reading one element past the end of 'arr' (the array ends at index 4).

If I run:

a.out 6 98

I receive a warning that 'index 6 out of bounds for type 'int [5]'. In this case I am writing and reading two elements past the end of 'arr'.

Why doesn't the off-by-one case throw an error? I guess because one element past the end of an array is a valid memory address (i.e. iterators can point to it?). Can you suggest any other tool that can reliably detect out of bounds access by one element?

EDIT

I can also run:

g++ -O0 -std=gnu++14 -fsanitize=bounds-strict example.cpp

And I see the same behaviour, i.e. out of bounds access by one element does not trigger a warning, but, out of bounds by two elements does. This is the part I'm trying to understand.

nickos556
  • 337
  • 3
  • 16
  • 1
    It doesn't throw an error because in c++ the motto is usually *don't pay for what you don't need*. There are no bounds checking on arrays and vectors by default. It's your job writing the program to make sure you are in bounds. Going out of bounds is UB, which means that the program can appear to work, not work at all crash or whatever it likes really. There are no guarantees. – super May 24 '20 at 11:25
  • You can use `std::vector`s `at` method if you want bounds checking at run-time. – super May 24 '20 at 11:27
  • 1
    Try `-fsanitize=address`. – n. m. could be an AI May 24 '20 at 11:28

2 Answers2

4

Why doesn't the off-by-one case throw an error?

Because -fsanitize=undefined doesn't detect out of bounds accesses.

Can you suggest any other tool that can reliably detect out of bounds access by one element?

I suggest gcc with -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)
...

The options are documented in gcc instrumentations options.

Side note: In one of my projects, I use -fsanitize=address -fsanitize=undefined -fsanitize=leak -fsanitize=pointer-subtract -fsanitize=pointer-compare -fno-omit-frame-pointer -fstack-protector-all -fstack-clash-protection -fcf-protection.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • I thought -fsanitize=undefined included -fsanitize=bounds-strict? So why didn't this option catch the out-of-bounds by one error? – nickos556 May 24 '20 at 11:47
  • I guess this is because `arr[5]` is a valid address. It can't be written to nor read from, but the address itself is valid, because it's the address of "one past the end" element. So `-fsanitize=bounds-strict` handles only if the index is valid, while `-fsanitize=address` handles read/write operations. – KamilCuk May 24 '20 at 14:19
0

If you want bounds check use 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;
        }
    }
}

Output:

Out of bounds                                                                                                                 
arr[0] = 0                                                                                                                    
arr[1] = 1                                                                                                                    
arr[2] = 2                                                                                                                    
arr[3] = 3                                                                                                                    
arr[4] = 4 
Out of bounds 
srilakshmikanthanp
  • 2,231
  • 1
  • 8
  • 25