Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Array subscript out of bounds when struct has zero length array as only member

Tags:

c++

gcc10

In the following code:

#include <cstring>

template <unsigned len>
struct CharArray {
  CharArray() {
    memset(data_, 0, len);
  }
  char data_[len];
};

struct Foobar {
  CharArray<5> a;
  CharArray<3> b;
  CharArray<0> c;
};

int main() {
  Foobar f;
}

The type CharArray<0> ends up having a zero-sized array as its only member. I'm aware of this being a GCC extension and unsafe practice in general. The question is not about that.

When I compile the code with gcc 10.2.0, I get the following warning:

<source>: In function 'int main()':
<source>:5:3: warning: array subscript 8 is outside array bounds of 'Foobar [1]' [-Warray-bounds]
    5 |   CharArray() {
      |   ^~~~~~~~~
<source>:18:10: note: while referencing 'f'
   18 |   Foobar f;
      |          ^

With gcc9 and earlier there's no warning.

Question: Where does the subscript 8 come from? And what is the Foobar [1] mentioned there? It looks like there's an array of one Foobars and we're trying to access element 8 in that array. Not sure how that could happen. If somebody knows the details, I'd appreciate it if you could explain it.

This happens when compiling with gcc++-10 in Ubuntu 20.04 with -O3 -Wall -Wextra as options. If I don't pass any optimization flag, there won't be any warning. Also: if I take the constructor away, the warning will also disappear.

like image 878
Olavi Avatar asked Dec 22 '20 08:12

Olavi


2 Answers

It seems the issue is somehow related to the memset(): as avoiding it using a condition (len != 0) doesn't work it seems the compiler recognizes that the start address of CharArray<0>'s object is produced by the intialization of CharArray<3> and warns about that. This theory can be tested by conditionally not initializing CharArray<3> with memset() or specializing that type as that makes the warning go way:

CharArray() { if (len != 3) memset(data_, 0, len); }

or

template <>
struct CharArray<3> {
  CharArray(): data_() { } 
  char data_[3];
};  

The warning is probably spurious. It seems by the time the address of the zero sized array is used the compiler has "forgotten" that it was produced by accessing a different array's member. The easiest approach to avoid the warning seems to correctly initialize the data in the initializer list and not using memset() at all:

template <unsigned len>
struct CharArray {
  CharArray(): data_() {}
  char data_[len];
};  
like image 172
Dietmar Kühl Avatar answered Nov 12 '22 20:11

Dietmar Kühl


Doing anything to a zero length C-like array is higly suspicious. Including even defining one in my opinion.

However, you can specialise the constructor to NOT do anything to the zero length array:

template<> CharArray<0>::CharArray() {}

In order to not even define a zero sized array (which I think should not be an obstacle to anythign you might want to achieve with the class in general...), you would have to specialise the whole class. (credits to Dietmar Kühl for this addition)

like image 45
Yunnosch Avatar answered Nov 12 '22 20:11

Yunnosch