2

I found some code and want to make sure that I understand this correctly. The usecase is an packed image that is represented by an array of values. In this example three values represent one pixel.

The code I found goes something like this:

struct Pixel{ 
  int[3] data
  int x(){return data[0];}
  int y(){return data[1];}
  int z(){return data[2];}
};

void main(){

  std::vector<int> img(300);
  Pixel* access = reinterpret_cast<Pixel*>(img.data()+3*5);
  foo(access->x());
}

As I understand from reading POD and standard layout, I think the code example is valid, because we only use the first member of Pixel? Then replacing Pixel with

struct Pixel2{
  int red;
  int green;
  int blue;
};

will result in undefined behaviour?

Edit: I work with cuda and found another example: casting and unsigned char pointer (an array) to an uchar3 pointer. The uchar3 type definition is equal to the second pixel definiton. Does this mean the second is also valid? Or does this only works for code compiled by nvcc? If the second Pixel definition is valid, then why?

Edit: To further emphasis what the code is trying to do I renamed some fields above: I have an array of raw data. In my case that is an packed image. I want to have an nice way to access the pixel and their values. So I can do something like this:

void bar(int* data,size_t size)
{
   Pixel2* img = reinterpret_cast<Pixel*>(data);
   std::cout << "Pixel 13 has blue value: " << img[13].blue;
}

I have seen code using this in cuda and it worked, but I want to know if it is allways okay, since it seems not covered by what I read about POD. Do I just missed something about POD or is this something that can fail?

Edit: is There a difference between:

  foo(access->x());
  foo(access->data[0]);

I thought second should be legal since for POD-types the first member variable has the same address as the object?

Edit: What I take from the answers is: that is UB in all cases I mentioned. The way to go would then be an random-access-iterator that gives me the access I would like.

  • 1
    I think you should call a placement new to be correct, however, `std::vector` solves the complete problem – JVApen Nov 28 '18 at 07:39
  • 1
    @JVApen call placementy new to do what exactly? To create a new pixel in a space provided by the vector? That's not what this code attempts to do. – n. m. could be an AI Nov 28 '18 at 08:46
  • 1
    To solve your actual problem, you should probably wrap `data` and `size`into a class instance, with methods giving you the desired access, and avoid the `reinterpret_cast` entirely. In general, if you can do it without `reinterpret_cast`, you probably should. Alternatively, code in C (or use relevant GNU extension to C++ to allow this), and use type punning with unions or something... – hyde Nov 28 '18 at 09:29
  • Also see https://en.cppreference.com/w/cpp/language/reinterpret_cast. – Trass3r Nov 28 '18 at 09:34
  • @hyde thinking about the usecases, that class instance would have to be a random_access_iterator. I would have to have two different Pixel-like-classes: one that uses data, which it knows by pointer and returned by the iterator. The other that holds the data on its' own, that can be used to save an Pixel. –  Nov 28 '18 at 10:30
  • Yes indeed, it creates an object, it's needed because you ain't allowed to access an object that wasn't created. – JVApen Nov 28 '18 at 11:35

2 Answers2

1

Calling a non-static member function on a nonexistent object and performing a class member access for a non-static data member on a nonexistent object are both undefined behavior.

Nothing in your code creates a Pixel or Pixel2 object.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • So if I have a Pixel p, then I can reinterpret_cast it to the first member, since it's a POD-type and in this case the first member has the same address as the object. The otherway around is undefined? –  Nov 29 '18 at 06:30
  • It's undefined to access an object that is not there. A `Pixel` contains three `int`s, so if you have a `Pixel`, you have those three `int`s. If you have three `int`s, that doesn't mean you have a `Pixel`. – T.C. Nov 29 '18 at 12:15
0

I could be totally wrong here, but to me it seems both of your struct actually are UB. However, it is unlikely to ever happen.

The (unlikely) use case it could do some harm is when the alignment of your vector (which could be provided from a library for example) and the structs differ. This should not happen if the code is compiled with the same compiler, and the same settings, except you align it yourself. Consider the alignment of the vector is different, both structs will result in UB. Or your struct alignment differs, same here, UB. The undefined behavior, however, does not come from the alignment, but from the fact reinterpret_cast does know nothing about it.

As a quick and dirty example:

struct Pixel2 {
    alignas(8) int red;
    alignas(8) int green;
    alignas(8) int blue;
};

Will give you wrong values for your pixels. Same could be done with the struct where you use an int array.

See this example where you can play around. Here both structs fail to get the correct values. For a variant where the vector is differently aligned, replace comments on line 69/70 (replace std::vector<int> data; with static_vector<int, 128> data;).

Some mentionable SO answers:

user1810087
  • 5,146
  • 1
  • 41
  • 76
  • UB does not depend on alignment. UB essentially allows for the possibility of it, and has other consequences relating to strict aliasing and optimisations and blah blah. So this is either UB or it is not UB. – Lightness Races in Orbit Nov 28 '18 at 10:38
  • @LightnessRacesinOrbit I don't know how to phrase it correct in this case. I would say it is UB, but very unlike ever happen... – user1810087 Nov 28 '18 at 10:44
  • Wording's better now. UB is one thing; the potential practical consequences of your program having UB are another thing :) – Lightness Races in Orbit Nov 28 '18 at 10:48
  • 1
    With modern, zealously optimising compilers, any UB is dangerous UB. – hyde Nov 28 '18 at 11:32