0

Is flattening an array of structs that contain an array like in the example below undefined behavior according to the C++ standard, even if there is no padding in the struct S?

#include <iostream>

struct S
{
    S() : v{1,2,3,4,5}
    {}
    int v[5];
};

static_assert(sizeof(S) == 5 * sizeof(int));

void print_array(int* begin, int* end)
{
    while (begin != end)
        std::cout << *begin++ << " ";
    std::cout << "\n";
}

int main(int, char**)
{
    S s[3];
    int* p = s[0].v;
    // treat p as a flat array of 5*3 ints - is this UB?
    print_array(p, p + 5*3);
    return 0;
}

It works in practice with gcc and msvc, but I wonder if it is guaranteed to work.

umbert
  • 51
  • 5
  • 1
    It is UB, but it will work in practice. – Passer By Oct 26 '22 at 08:16
  • 2
    Handling an struct of same type elements as an array used to be a common idiom, as was handling a 2D array as a flat 1D array, so most compilers will accept it in order not to break legacy code. But this is not specified by the language standard so *by definition* it is UB. Maybe you should add the language-lawyer tag if you want more details related to the standard... – Serge Ballesta Oct 26 '22 at 08:53
  • @PasserBy *" it will work in practice..."* UB is not "work in practice". The program is still in error even if it does not say so explicitly. – Jason Oct 26 '22 at 09:15
  • @JasonLiam: UB only means that the behaviour is not specified by the standard. It does not prevent an implementation to consistently provide the expected behaviour. So it is possible that in some implementations, the program will run without errors. Simply it is not valid C++... – Serge Ballesta Oct 26 '22 at 09:22
  • Which would mean the code might not be portable to another platform/compiler. But why do this if there is other alternatives that are not UB? – Pepijn Kramer Oct 26 '22 at 09:23
  • @SergeBallesta Yes that's exactly what my point is. The C++ program is still in error even if it does not say so. What an implementation does is beyond C++ standard's scope. The c++ standard allows anything to happen. That is, if a program has UB, it does not restrict any implementation. – Jason Oct 26 '22 at 09:30
  • @JasonLiam and what Passer By means "will work in practice" is that popular implementations *just so happen* to consistently provide the expected behaviour. – Caleth Oct 26 '22 at 09:33
  • @Caleth Yeah i get that point which is that many implementations may exploit UB to give the expected behavior. – Jason Oct 26 '22 at 09:38
  • 2
    Dup of [May I treat a 2D array as a contiguous 1D array?](https://stackoverflow.com/questions/7269099/may-i-treat-a-2d-array-as-a-contiguous-1d-array) – Language Lawyer Oct 26 '22 at 09:49
  • @JasonLiam _"beyond C++ standard's scope"_ I'm not talking about the standard when I say "in practice". _"UB is not work in practice"_, note the conjunction "but". – Passer By Oct 26 '22 at 12:07
  • @PasserBy The question is asked from C++'s perspective and not from implementation's perspective. Note the *"I wonder if it is guaranteed to work."* in the question. – Jason Oct 26 '22 at 12:15

1 Answers1

0

Short answer: this is not defined in the standard so it is UB, and you have no guarantee that it will work

Long answer:

The standard defines undefined behavior as (3.64 [defns.undefined]):

undefined behavior
behavior for which this document imposes no requirements

You are there... The standard never says that a struct can be browsed as an array except as an array of characters to access its representation. Furthermore, pointer arithmetics is only defined inside an array, which can be explicitely declared or dynamically allocated (still with the exception for character pointers).

So even if we all know that, as you controlled that no padding is involved, the representation of the structure will be consecutive representations of integer objects, this is not a declared array, so per standard you are not allowed to perform pointer arithmetics inside it.

This is often called formal UB. All current well known implementations will correctly translate this code, and the resulting program will produce expected results. But another compiler, or a later version of a currently working compiler could perfectly decide differently...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • It's probably safe in this example; I don't expect a compiler to use AVX for this. (not enough math to bother vectorizing). But once AVX kicks in, the compiler can start to make alignment assumptions. I've recently had to debug an AVX alignment crash, and gdb is really unhelpful - it prints a null pointer instead of the unaligned address. I wouldn't want to say "go ahead, all well-known implementations will do what you intended" when you're so close to the edge of nasty bugs. – MSalters Oct 26 '22 at 09:54