简体   繁体   中英

Bound checking of std::array in “Debug” version of GCC

Benefits of C++11 std::array s when programming have been explained by experts, but there is one thing I would like to get from the compiler. Ability to turn ON range check that is default when using .at() while compiling the code that uses [] .

It could be beneficial for checking range violations especially for multidimensional arrays because in that case it is less likely that range violation will cause segfault(because you often own memory around the inner array so [5000][-123] will still likely point to memory that you own).

So I would like to know if there is a switch that will compile into machine code that checks ranges:

    const uint32_t dim1=10*1000,dim2=3;
    std::array<std::array<int, dim2>, dim1> test_2Darray;
    int undefined_value=test_2Darray[dim2-1][dim1-1];
    std::cout<<"ouch ("<<undefined_value<<")"<<std::endl;
    int ok_value=test_2Darray[dim1-1][dim2-1];
    std::cout<<"OK   ("<<ok_value<<")"<<std::endl;
    //  test_2Darray.at(dim2-1).at(dim1-1); -->terminate called after throwing an instance of 'std::out_of_range'
    //      what():  array::at

If you ask why I don't switch to .at() - I might need the performance, also I have a lot of code with [] already written and I'm not smart enough to smart to do replace for 1D let alone 2D arrays.

I use GCC 4.6

It looks like the array that comes with gcc 4.6 doesn't have a debug mode yet. Understandable since C++11 support is still experimental.

There is a flag _GLIBCXX_DEBUG which is usually used to turn on debug mode. If you look at /usr/include/c++/4.6/debug/vector:313 you'll see operator[] has:

__glibcxx_check_subscript(__n);

Now, this may be uber-evil (and I mean really evil) but it looks like we can conditionally add this to array. Change lines 148-154 of /usr/include/c++/4.6/array from:

reference
operator[](size_type __n)
{ return _M_instance[__n]; }

const_reference
operator[](size_type __n) const
{ return _M_instance[__n]; }

to:

reference
operator[](size_type __n)
{
#ifdef _GLIBCXX_DEBUG
    __glibcxx_check_subscript(__n);
#endif
    return _M_instance[__n];
}

const_reference
operator[](size_type __n) const
{
#ifdef _GLIBCXX_DEBUG
    __glibcxx_check_subscript(__n);
#endif
    return _M_instance[__n];
}

This means you can enable bounds checking for array the same way you do for vector and other stl debugging - by adding -D_GLIBCXX_DEBUG to your compile line. Eg:

g++ someAwesomeProgram.cpp -D_GLIBCXX_DEBUG

I just had a look at gcc trunk and apparently there is no reference to _GLIBCXX_DEBUG for array yet :(. http://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/include/std/array

Hopefully it's not too far away. I imagine we will have safe iterators and all that for array in debug mode soon enough. In the meantime, this can be our little secret :-).

template<class T, std::size_t N>
T const& at(std::array<T,N> const& arr, std::size_t pos){
#ifndef NDEBUG
  // debug versions, automatically range checked
  return arr.at(pos);
#else
  // release version, unchecked
  return arr[pos];
#endif
}    

template<class T, std::size_t N>
T& at(std::array<T,N>& arr, std::size_t pos){
  typedef std::array<T,N> const& const_array;
  // const_cast of the return is safe here because be pass a non-const array
  // const_cast for the argument is needed to avoid infinite recursion
  return const_cast<T&>(at(const_cast<const_array>(arr), pos));
}

Should do the job. Just use at(arr, pos) consistently throughout the codebase.

You can emulate the behaviour you desire:

#include <array>
#include <cassert>
#include <iostream>

#ifndef NDEBUG
template <typename T, std::size_t N>
struct my_array : std::array<T,N> {
 T& operator[](std::size_t n) {
   assert(n < N);
   return (*static_cast<std::array<T,N>*>(this))[n];
 }
 const T& operator[](std::size_t n) const {
   assert(n < N);
   return (*static_cast<const std::array<T,N>*>(this))[n];
 }
};
#else
// I would use Alias templates here, but isn't supported on my compiler yet!
template <typename T, std::size_t N>
struct my_array : std::array<T,N> {
};
#endif

It doesn't perfectly match std::array , but that could be fixed if it matters to you. Then replace all reference to std::array with my_array and you'll get range checked operator[] for debug builds.

(I'd have used template aliases to simplify the NDEBUG code, but I can't actually test that yet on my compiler)

It is not so much gcc as libstdc++ the Standard Library implementation that comes with gcc (you are free to use another implementation if you wish).

libstdc++ has a preprocessor flag that can be used for debugging -D_GLIBCXX_DEBUG , however you should note that this debug mode changes the ABI of the types, and thus you need to link with libraries that have also been compiled with this debug mode enabled. It can be painful.

libc++ is another implementation (nigh C++11 compliant) that is first aimed at Clang but should work on any compliant compiler. It aims at maintaining ABI compatibility whether debugging is enabled or not. It's not fully stable outside of OS X though (mostly because of locale) so might not be usable in your environment.

Note that both those libraries are Free Software, so if the check is not implemented, you can perfectly submit a patch.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM