31

For example I have this struct

struct  A {
    float x;
    float y;
    float z;
};

Can I do this? A a; float* array = (float*)&a; And use a as float array?

Semyon Tikhonenko
  • 3,872
  • 6
  • 36
  • 61
  • 11
    No, there might be a padding between variables. – kocica Aug 26 '17 at 17:53
  • Put a `static_assert` to make sure there is no padding. It may or may not remain UB after that, but it should work. – HolyBlackCat Aug 26 '17 at 18:19
  • 4
    I mean place a `static_assert(sizeof (A) == sizeof (float) * 3, "blah")` somewhere. Then, if your compiler decides to add padding, you will get a nice error. But I'm 99% sure it won't do so. – HolyBlackCat Aug 26 '17 at 18:21
  • 4
    @HolyBlackCat - It may cause more errors then you'd think. On a 64 bit machine, with 4 byte floats, the compiler may very well add padding at the end of the structure to align the structure on a machine word boundary. – StoryTeller - Unslander Monica Aug 26 '17 at 18:37
  • 1
    @StoryTeller I think there are no problems if compiler adds padding only at the end, are they? – Semyon Tikhonenko Aug 26 '17 at 18:39
  • 1
    @SemyonTikhonenko - It is a problem, because that static assertion will fire. Even though all the "ad-hoc" guys will be correct in saying your hack will work. – StoryTeller - Unslander Monica Aug 26 '17 at 18:39
  • A `static_assert` that avoids this issue would be `static_assert(sizeof (float) * 3 == offsetof(A, c) + sizeof (float) - offsetof(A, a))`, although I don't know how common support for [the offsetof macro](http://en.cppreference.com/w/cpp/types/offsetof) is across compilers. – ApproachingDarknessFish Aug 26 '17 at 19:10
  • @ApproachingDarknessFish - It's a standard well defined macro, so long as the type in question is a standard layout type (which the OP's type is). So all's good with your static assertion, I think. – StoryTeller - Unslander Monica Aug 26 '17 at 19:12
  • 1
    Some compiler offer pragmas that force structure packing to be equivalent to that of arrays which will also get practical operation on some subset of platforms even if the code is formally problematic. – dmckee --- ex-moderator kitten Aug 26 '17 at 21:23
  • The question must be asked. @SemyonTikhonenko what are you trying to achieve with this? What were you trying to do originally that cannot be done by writing an array in the first place? In a worst case scenario, might I suggest overloading the `[]` operator for this struct. – Mr Lister Aug 27 '17 at 08:26
  • @MrLister This struct is from a third party lib. And I want to pass it to another lib, which requires array. And I wonder if I can optimize the code without copying this struct into array. – Semyon Tikhonenko Aug 27 '17 at 08:29

5 Answers5

27

No, typecasting a struct to an array will not work. Compilers are allowed to add padding between members.

Arrays have no padding between members.

Note: there is nothing stopping you from casting, however, using value after cast results in undefined behavior.

W.F.
  • 13,888
  • 2
  • 34
  • 81
Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • 5
    In a general sense you may be right, but from a practical point of view this will work on virtually all compilers under windows, linux and embedded platforms like PowerPC and arm I work with. I would certainly like to see an example where it fails :) – Sil Aug 26 '17 at 18:31
  • 1
    @Sil Typecasting the struct to an array may work. To Thomas's point though, I wouldn't call doing that **safe** and the OP did in part ask if it can safely be done. – Louis Langholtz Aug 26 '17 at 18:42
  • 4
    Incidentally, I wish Thomas had said that typecasting this way wasn't safe instead of saying that it wouldn't work. – Louis Langholtz Aug 26 '17 at 18:43
  • @Louis he asked if he can do that.... from one point of view he can, but in the general/strictly/formal sense, no he can't. the same way you can ask "is 1/x defined on the real numbers?" well, most of the time it is (my answer). But a strict mathematical answer would be no, it isn't (your answer) just because of the 0 value in the see of all other values :P Also in our case you can't find the 0, can you? (unless you do some pragma pack 8, then it will fail :P) – Sil Aug 26 '17 at 18:47
  • 1
    @Sil It's called "undefined behaviour". The compiler can go postal on you, with strong support by Mr. Murphy. But only if you turn on the optimiser for a production version of your code. So it will work fine on your development machine, and take down production. Or destroy all data of your most important customer. – gnasher729 Aug 26 '17 at 22:48
  • @Sil: It might work on the compilers/machines using today, but do you really want to depend on it working on future hardware? Not to mention that you're going to confuse the next programmer who has to maintain your code. – jamesqf Aug 27 '17 at 05:00
  • @jamesqf Totally agree, but then again if you constrain yourself to write only 100% portable code you'll lose the advantages of some "hacks". In his case he could easily check if it'll work using something in the likes of static_assert(sizeof(A) == 3*sizeof(float)) – Sil Aug 27 '17 at 13:04
  • @Sil: Agreed, there are places where machine-specific code is useful, even necessary, though I don't think this is it. But you've got me curious: is it even a requirement that elements of a struct are always stored in the order of the definition? Or could an optimizing compiler choose to re-order them for better packing? – jamesqf Aug 28 '17 at 03:30
  • @jamesqf The order is enforced by the standard. "For non-union class types, members with the same member access are always allocated so that the members declared later have higher addresses within a class object." – Sil Aug 30 '17 at 08:47
27

In a practical sense, yes you can do that and it will work in all the mostly used architectures and compilers.

See "Typical alignment of C structs on x86" section on Wikipedia.

More details:

  • floats are 4 bytes and no padding will be inserted (in practically all cases).

  • also most compilers have the option to specify the packing of structures and you can enforce that no padding is inserted( i.e. #pragma pack from visual studio )

  • arrays are guarantied to be contiguous in memory.

Can you guarantee that it will work in all CPUs in the world with all the compilers? No.. but I would definitely like to see a platform where this fails :)

EDIT: adding static_assert(sizeof(A) == 3*sizeof(float)) will make this code not compile if there are padding bytes. So then you'll be sure it works when it compiles.

Sil
  • 817
  • 8
  • 18
  • 7
    I like this answer because it prefers pragmatism over pedantic application of the spec. – aroth Aug 27 '17 at 03:50
  • I could imagine one of the more obscure 64-bit platforms doing strange things for alignment reasons, and you can [have issues if any of the data is uninitialized](https://blogs.msdn.microsoft.com/oldnewthing/20040119-00/?p=41003/) (yes, even if you turn off sNaNs globally). You *certainly* cannot assume that "typical alignment on x86" is going to describe any architecture other than x86, of course. – Kevin Aug 27 '17 at 05:44
  • +1. It might be undefined behavior, but you will find *a lot* of old C code written that way. Microsoft even includes example code like that in the VC++ documentation. – Niki Aug 27 '17 at 07:03
  • @Kevin nice article (i'm not familiar with ia64) but it doesn't apply here. Adding a static_assert(sizeof(A) == 3*sizeof(float)) will make the code portable if it compiles :) – Sil Aug 27 '17 at 13:01
  • @Sil - That particular static assert is broken. See my comment to the OP. You make far too many assumptions. If you are going to propose intentionally adding UB to code, don't present it so lightheartedly. – StoryTeller - Unslander Monica Aug 30 '17 at 05:38
  • 1
    More clear version of the `static_assert`: `static_assert(sizeof(A) == sizeof(float[3]))` – Ben Voigt Nov 07 '19 at 17:35
19

This is a strict aliasing violation, plain and simple. Any access with that pointer, to any element but the first, is undefined behavior in your case. And in more complex cases it's just plain undefined behavior regardless of which element you access.

If you need an array, use an array. std::array also has an overload for for std::get, so you can use that to name each individual array member:

using A = std::array<float, 3>;

enum AElement { X, Y, Z };

int main() {
  A a;
  get<X>(a) = 3.0f; // sets X;

  float* array = a.data(); // perfectly well defined
}
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 3
    I don't think this is a strict aliasing violation since both the pointer and the struct elements are `floats` – Jeff Aug 26 '17 at 18:08
  • 1
    @Jeff - This isn't a matter of opinion. The C++ standard mentions that only certain type may alias each other, and all other aliasing is UB unless otherwise specified. The case of the first member variable here is only one exception, because it's first byte is coincidentally the first byte of the structure (in this case). – StoryTeller - Unslander Monica Aug 26 '17 at 18:11
  • Right, per the standard a pointer to a type can access `an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),`. The struct here qualifies. There are other problems accessing the other members in this way but it's not because of strict aliasing rules. – Jeff Aug 26 '17 at 18:17
  • 2
    @Jeff - Erm, no. Accessing the **first** member, is only allowed because this is a standard layout type. That clause doesn't mean one may treat structures as arrays, even when there is no padding. Of relevance here is the description of `reinterpret_cast`, which is the cast going on here. – StoryTeller - Unslander Monica Aug 26 '17 at 18:29
  • @StoryTeller The strict aliasing rule as formulated in the standard only says accessing an object with a glvalue of different type (other than the exceptions) is UB. If the pointer indeed points to the address of a `float`, wouldn't that be fine? – Passer By Aug 26 '17 at 19:45
  • 3
    @PasserBy - Hence my point about the first member being okay due to the standard layout type guarantees. But that's as far as the standard itself will take you. – StoryTeller - Unslander Monica Aug 26 '17 at 20:03
  • 1
    @PasserBy: The standard lets you take a pointer to that first member, and further allows you to treat any pointer like a one-element array. But OP doesn't want a one-element array, they want a three-element array. Nothing in the standard allows for that, because now we're not talking about the *first* member any more. – Kevin Aug 27 '17 at 05:49
7

For g++ you can use attribute for your struct, like this:

struct  A {
    float x;
    float y;
    float z;
}__attribute__((__packed__));

It's disable struct alignment.

Pavel
  • 277
  • 2
  • 13
4

You can do that, until the compiler starts optimising, and then things go wrong.

Accessing any but the first element of a struct by using a pointer to the first element is undefined behaviour. "Undefined behaviour" means that anything can happen. The compiler may assume that there is no undefined behaviour.

There are many consequences that the compiler can deduce from that: If the compiler knows that your float* points to the first element of the struct, then it can deduce that every index to that array equals 0 (because anything else is undefined behaviour). If the compiler doesn't know this, then it can deduce that the array pointer cannot point to the struct, and changing array elements can't change struct elements and vice versa.

Can you see how this will go wrong?

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • 1
    One of the things that allowed C to win the "language wars" in the 1980s and 1990s is that implementations supported useful semantics which weren't available in other languages. Unfortunately, because implementations where such semantics would be useful supported them in the obvious way even though the Standard didn't require it, the authors of the Standard saw no need to define an "official" way to demand such semantics. I think your post might benefit from a more specific and realistic example, however, such as assuming that it can keep the value of one structure member cached... – supercat Aug 26 '17 at 23:28
  • 1
    ...in a register across an access to the same storage using an address computed from a different member. C and C++ have somewhat different rules, but since C++ was intended to be a superset of C, the same historical issues remain relevant in C++. – supercat Aug 26 '17 at 23:30