7

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.

Yunnosch
  • 26,130
  • 9
  • 42
  • 54
Olavi
  • 71
  • 4
  • @Yunnosch Even with if constexpr len for memset the warning comes up. In my eyes all this is implementation specific and not really helpful to discuss, as it is not about C++ but the given compiler version. The code is invalid. So use valid code as already shown within your answer. – Klaus Dec 22 '20 at 09:20
  • @Yunnosch: interestingly, `if (len)` and specializing `CharArray<0u>` while still having an empty array (but obviously not setting the array) doesn't remove the warning. Only removing the array (while retaining `memset()`) makes the warning go away. The warning is produced by `CharArray<3>`'s initialization producing the address to the empty array. – Dietmar Kühl Dec 22 '20 at 09:25
  • "Where does the subscript 8 come from". It came from 5 + 3, you can verify this by changing 3 to 4, you will get 9. I was able to reproduce the warning using online GCC 10.2 in godbolt (see https://godbolt.org/z/hjWYe1) – Suma Dec 22 '20 at 09:32
  • @DietmarKühl Thanks for noting (my online compiler did not complain, but it seems plausble). I will add your contribution to my answer on specialising for 0, assuming that you do not mind, because your answer has a different approach. – Yunnosch Dec 22 '20 at 09:33
  • @Yunnosch: I didn’t see the warning on Compiler Explorer, either. However, I got it when testing locally. Fell free to use what I’m suggesting in your answer... – Dietmar Kühl Dec 22 '20 at 09:36
  • @DietmarKühl Thanks. I will take what matches my approach, letting you have the reward for the work of researching locally. – Yunnosch Dec 22 '20 at 09:38

2 Answers2

3

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];
};  
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Dietmar, if you do not mind, and because you having setup the reproducing environment, I propose to try sizes 5,3,1 and 5,4,1 in addition to your good experiment of 5,4,0 and the original 5,3,0, because I still feel attracted to the 0 being the culprit or at least being involved. The 0 is the most suspicious thing in that code, everything else looks like "should work". Trying to narrow down what migh tbe a compiler bug. – Yunnosch Dec 22 '20 at 10:29
  • @Yunnosch: sure, the zero sized array plays into it. It seems gcc merges the different `memset`s into just one `memset`. It doesn't seem to bleed into the empty `struct` with the zero sized array, though, based on what I can see from the generated assembly. It still seems the compiler warns about the odd pointer being produced. – Dietmar Kühl Dec 22 '20 at 13:27
2

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)

Yunnosch
  • 26,130
  • 9
  • 42
  • 54
  • Note that specializing the constructor alone won't make the warning go away as it isn't the zero sized object's constructor which produced it! It is the constructor of the 3 element object which produces the warning. – Dietmar Kühl Dec 22 '20 at 09:30
  • I will try to follow your assertion of 3 being the cause. I admit that it might take me some time, especially if I have to go local. @DietmarKühl – Yunnosch Dec 22 '20 at 09:36
  • The point is that the `memset()` in the constructor preceding the zero sized array produces the address of the zero sized array - and that seems to cause the warning. – Dietmar Kühl Dec 22 '20 at 09:38