3

Can multi-dimensional std::array be treated as a continuous block of data? That is, is this legal:

#include <array>
#include <iostream>
#include <cstring>

// just to show what's happening, otherwise unrelated
template<typename T>
void print_array(const T &data)
{
    for(const auto &row : data) {
        for(const auto &item: row) {
           std::cout << static_cast<char>('a' + item);
        }
        std::cout << '\n';
    }
    std::cout.flush();
}

// function to fill a block of bytes
void filler(uint8_t *data, size_t size) {
    while(size != 0) {
        *(data++) = size % 27; // just a changing number
        --size;
    }
}

// test code
int main()
{
    std::array<std::array<uint8_t, 30>, 20> data;
    std::memset(&data, 0, sizeof(data)); // use &data, is this legal?
    print_array(data); // seems to print all 'a' ok...
    std::cout << std::endl;
    filler(data.begin()->begin(), 30*20); // use pointer to first item, legal?
    print_array(data); // seems to print alphabet pattern ok...
}

Print-out is what one might expect, first a character rectangle of letter a, then a rectangle filled with characters a-z.

The same subject is discussed at least here and here, but I think multi-dimensional arrays may be a different case.


Additionally, even if static_assert(sizeof(data) == sizeof(uint8_t)*20*30) holds, so the data is continous in memory with no gaps, does even this guarantee anything? Or is using the same pointer to access different arrays through indexing illegal anyway?

hyde
  • 60,639
  • 21
  • 115
  • 176
  • 1
    The comments on [an answer to one of the questions you linked](https://stackoverflow.com/a/11205298/364696) seem to answer you with a "it currently works, but there's no guarantee of layout compatibility", while also suggesting a form of `static_assert` you could tweak to verify if it's valid on your compiler's implementation. – ShadowRanger Jun 16 '22 at 11:10
  • The short answer is "no". The standard requires that `std::array::size()` returns `N` and doesn't say anything about result of `sizeof` when applied to (an instance of) that type. If you want contiguity of elements, one option is to implement your array as a `std::array` and convert a pair of indices into an appropriate index of the array. – Peter Jun 16 '22 at 12:28
  • Even if contiguous, pointer arithmetic is only valid inside the array, `&data[0][0] + 42` is out of bound of `data[0]` (it would go in `data[1]`), so whereas `memset` is "magical" and work, equivalent code as `filler` won't. – Jarod42 Jun 17 '22 at 07:51

1 Answers1

2

You may want to take a look into this question and its accepted answer. Basically, implementations will have a plain array T[N] as the first member of the std::array but nothing stops them putting members after that, in which case sizeof(data) != 30 * 20.
This might seem a bit farfetched, but there were reports of this happening: link. So if you want your code to be "legal", you will have to treat each inner array independently and iterate over them instead, possibly jumping some space at the end of each. If this is something you really need to do, which I highly doubt, you could do a static_assert to see if the size of std::array is what it should be.

  • *"you could do a static_assert to see if the size of std::array is what it should be"* But does even this guarantee anything. Or is it Undefined Behavior, and compiler might do funny things, even if it is a continuous memory block? I'll add this to the question as well, as I think it will just clarify the question by highlighting this one issue. – hyde Jun 16 '22 at 12:41