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.

6
  • @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. Commented Dec 22, 2020 at 9: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. Commented Dec 22, 2020 at 9: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 godbolt.org/z/hjWYe1) Commented Dec 22, 2020 at 9: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. Commented Dec 22, 2020 at 9: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... Commented Dec 22, 2020 at 9:36

2 Answers 2

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];
};  
Sign up to request clarification or add additional context in comments.

2 Comments

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: sure, the zero sized array plays into it. It seems gcc merges the different memsets 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.
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)

3 Comments

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.
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
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.