11

I have a struct for holding a 4D vector

struct {
    float x;
    float y;
    float z;
    float w;
} vector4f

And I'm using a library that has some functions that operate on vectors but take float pointers as their arguments.

Is it legal to call something like doSomethingWithVectors( (float *) &myVector)?

spencewah
  • 2,196
  • 4
  • 20
  • 31
  • 4
    This is always LEGAL, as casts can always be used -- the real question is whether the resulting behavior is well defined, implementation defined, or undefined. – Chris Dodd Dec 28 '09 at 01:14

8 Answers8

11

It might work but it is not portable, the compiler is free to align things so that one float does not neccessarily immediately follow the other.

President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
  • That's true about arrays, too. The question is, is there any reason to expect the alignment to be different when the compiler packs an array vs. when it packs a structure? I can't see one, but agree that the posted code makes me uncomfortable. – Aidan Cully Dec 27 '09 at 22:56
  • 2
    -1 it will work, and it is portable. structure packing and array packing follow the same rules. – John Knoeller Dec 27 '09 at 23:15
  • 17
    @John Knoeller: Incorrect. Firstly, C language has no "packing rules" that would be the same for arrays and structs. In fact, the rules are explicitly different: structs can add padding, arrays cannot. Secondly, this code would be broken for more reasons than just the potential packing difference. Strict aliasing violation, for example, is even more important problem than packing. – AnT stands with Russia Dec 27 '09 at 23:25
  • 2
    @Aidan, arrays cannot have padding between members. If I have `T a[N];`, then `a` and `a+1` are `sizeof(T)` bytes apart. – Alok Singhal Dec 28 '09 at 00:08
  • 4
    For those who (like me) didn't know about "strict aliasing" in C, there's actually a question about that which explains it nicely: http://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule – sleske Dec 28 '09 at 00:39
  • @AdrianT: nonsense. "ideal" compliers may not have such rules. Real compilers have to or no-one would use them. – John Knoeller Dec 28 '09 at 01:00
  • 3
    John: all compilers must follow the language rule of `&a[n] + 1 == &a[n + 1]`. There is no such rule for struct members. If you want to use compiler-specific #pragmas or similar to control struct layout, that's fine; but I see none of that in the mentioned code. Finally, compiler-specifics are obviously *not* portable. –  Dec 28 '09 at 01:25
11

You can write code that would make an attempt to treat it as an array, but the language makes no guarantees about the functionality of that code. The behavior is undefined.

In C language taking a storage region occupied by a value of one type and reinterpreting it as another type is almost always illegal. There are a few exceptions from that rule (which is why I said "almost"), like you can reinterpret any object as a char array, but in general it is explicitly illegal.

Moreover, the possible dangers are not purely theoretical, and it is not just about the possible alignment differences between arrays and structs. Modern compilers might (and do) rely on the aforementioned language rule in order to perform aliasing optimizations (read about strict aliasing semantics in GCC, for one example). In short, the compler is allowed to translate code under the assumption that memory occupied by a struct can never overlap memory occupied by an array of float. This often leads to unexpected results when people start using tricks like in your post.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Nice, also the bit about aliasing. – Alok Singhal Dec 28 '09 at 00:31
  • Nice explanation. But it's irrelevant to the question. `myfunc(&myVector.x)` is a pointer to a float, and the code on the other side IS permitted to treat that as an array of floats. There is no aliasing violation. – John Knoeller Dec 28 '09 at 01:09
  • @John: Good point. Yet the reinterpreting code in the original qustion looks differently. – AnT stands with Russia Dec 28 '09 at 01:53
  • John: That's also irrelevant. The code on the other side is permitted to treat that as an array of floats, only if that array has length 1. This shouldn't be surprising and is true for any object: `void f(int* p); int n; f(&n);` or `struct { /*...*/ int n; /*...*/ } s; f(&s.n)`, f may treat p as pointing to the first item in an `int[1]`. –  Dec 28 '09 at 02:20
6

Whoa. There are a lot of answers saying it will work. It is not guaranteed by the C standard. In response to a comment asking for a real-world example, there is this excellent post by Chris Torek from comp.lang.c. The final quote is: The lesson here is the same as always: "If you lie to the compiler, it will get its revenge."

Definitely a must read for those who think it's OK to treat a struct of homogeneous members as an array.

Alok Singhal
  • 93,253
  • 21
  • 125
  • 158
  • compiler writers are purists. But end the end, they _don't_ take revenge for constructions like this, if they did noone would use their compilers. – John Knoeller Dec 28 '09 at 00:57
  • 4
    John, it's not that compiler writers are out there to get revenge. But, if you have unreasonable expectations, things *will* go wrong. There are much more cleaner and safer ways of doing what he wants to do. – Alok Singhal Dec 28 '09 at 17:25
3

Let's just throw all arguments about the Right Way™ to do something out the window for a minute.

Does it work to treat that struct as an array? Yes. Will it work in all cases, across all compilers and platforms? No.

Floats tend to be 32-bit, and even on my 64-bit machine, they get aligned on 32-bit word boundaries.

#include <stdio.h>

struct {
        float x;
        float y;
        float z;
        float w;
} vector4f;

float array4f[4];

int main(int argc, char **argv) {
        printf("Struct: %lu\n", sizeof(vector4f)); // 16
        printf(" Array: %lu\n", sizeof(array4f));  // 16

        return (0);
}
Mak Kolybabi
  • 344
  • 1
  • 8
  • Yes, but keep in mind that the main problem with this code is not the potential misalignment (even thouh this issue seems to gets most attention here), but rather the strict aliasing violation. The code will not generally work as expected in GCC (for one example), regardless of how the data is aligned. – AnT stands with Russia Dec 27 '09 at 23:32
  • Anyone got an example of a compiler/architecture were it won't work? I've been coding in C for more than 20 years and I've never seen one or even read about one. – John Knoeller Dec 27 '09 at 23:38
  • 1
    @John: Even if there is no packing, it is not guaranteed. See my post for details. – Alok Singhal Dec 28 '09 at 00:28
  • 1
    John: Very few people here are saying straight out that it won't work. In fact, we really don't care if his exact example works or not; if we did, we'd tell him to "check your specific implementation, verify, and move on". Everyone mentioning potential issues does so in order for him to be aware of the language rules, so when (not if) he tries to generalize what he learned here, he won't do it incorrectly, and will be a better programmer because of it. (If I've mischaracterized someone's motivation and you're just here for rep, then I apologize. :P) –  Dec 28 '09 at 01:37
  • @Alok: I read it. It is interesting, but does not apply to this example. To get things to break, Chris had to take the address of a member of the struct, AND to use another intermediate pointer. – John Knoeller Dec 28 '09 at 01:39
  • 1
    @JohnKnoeller: You have 20+ years of experience and you still use "it works for me" as an excuse...? – GManNickG Jun 06 '13 at 15:34
  • @JohnKnoeller I tried to use example code from a chip manufacturer, which used a struct to generate a packet and then send it over the network as an array, in my 8-bit MCU, and it didn't work because the endianness of 16-bit values within the struct was reversed. Does that count? – endolith Sep 23 '22 at 16:35
1

No, it is not legal. And almost any time you find yourself using a cast, you should suspect there is something deeply wrong with your code. I suspect that someone will shortly suggest using a union, but that will not be legal either.

Of course, legal or not, either the approach you suggest in your question or the use of a union will probably "work" - but that is not what you asked.

  • Could you explain what you mean by "legal"? For me, a piece of C code is legal iff it compiles without errors. This should compile without errors or warnings and it will probably even do what it's supposed to do, assuming appropriate alignment/structure packing settings. – Niki Dec 27 '09 at 23:00
  • 1
    Not legal as in the standard makes no guarantee about its behavior. It may compile on gcc with the proper settings but that doesnt make it valid C code. – Juan Dec 27 '09 at 23:04
  • C code is legal iff it would compile and execute correctly with any standard-compliant compiler. The assumption of alignment/structure packing would violate that rule, as the compilers are specifically allowed to vary their packing of structures as desired. – Chris Arguin Dec 27 '09 at 23:04
  • 1
    @nikie A C program is legal if it adheres to the C Standard. I can (and have) write a something I call a C compiler which can compile any old crap. –  Dec 27 '09 at 23:15
  • nikie: There are issues for which the C standard does not require any diagnostic. Common expression in certain (student) circles: "omg omg, IT WORKS!! ... I mean it compiles, what is a segfault?" –  Dec 28 '09 at 01:28
0

If you want an array why don't you put an array inside of your struct and use that? I suppse you could add pointers if you still wanted to access it in the same way using vector4f.x vector4f.y vector4f.z

struct {
    float vect[4];
    float* x;
    float* y;
    float* z;
    float* w;
} vector4f

Of course that would make your struct bigger and more complicated initialization.

Derek Litz
  • 10,529
  • 7
  • 43
  • 53
-1

If I had this situation, this is what I would do: C++ member variable aliases?

In your case it would look like this:

struct {
        float& x() { return values[0]; }
        float& y() { return values[1]; }
        float& z() { return values[2]; }
        float& w() { return values[3]; }

        float  operator [] (unsigned i) const { return this->values_[i]; }
        float& operator [] (unsigned i)       { return this->values_[i]; }
        operator float*() const { return this->values_; }

private:
        float[4] values_;
} vector4f;

However, this doesn't address the question of treating a struct as an array if that's specifically what you wanted to know about.

Community
  • 1
  • 1
Ray Hidayat
  • 16,055
  • 4
  • 37
  • 43
-1

Yes, this will work on any compiler that follows the C99 standard. It will also probably work with compilers using earlier, less clear standards.

Here's why:

6.2.5.20 defines arrays as 'contiguously allocated' elements, while structs are 'sequentially allocated' members, and as long as all the struct members are the same type, this is really the same thing.

6.5.2.3 guarentees that if the types are used in a union visible currently, you can use things that are laid out in the same order transparently.

6.5 makes it clear that the 'effective type' for any access to an element of the array, or a field of the struct is 'float', so any two accesses may alias. An access to the struct as a whole is actually an access to each member in turn, so the same aliasing rules apply.

The 'union' requirement is most odd, but since types declared where a union is visible must be compatable with types defined identically in another compilation unit without the union, the same rules end up applying even where the union is not visible.

Edit

The key point here is the distinction between things that are undefined vs things that are implementation defined. For undefined things, the compiler might to anything, and might do different things when recompiling the same source file. For implementation defined things, you might not know exactly what it does, but it must do the same thing consistently across compilation units. So even where the spec doesn't exactly spell out how something must be done, the interaction between different things and the fact that things must be consistent across compilation units with compatable type declarations greatly restricts what the compiler can do here.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • No, even the first bit is already incorrect. C99 clearly states that struct elements can have unspecified amount of padding added between them. No restrictions is made on whether the members have the same type or different types. That's the critical difference between "sequential" and "contiguous" in the above quote. – AnT stands with Russia Dec 28 '09 at 01:29
  • @Andrey: You are clearly wrong here. It would be impossible to write drivers, or parse binary file formats if C compilers didn't provide a way to gurantee that struct fields were packed. They may not be required by the standard to do that. But they are required by the world. If packed structures were simply _unavailable_ the compiler would be useless for the sort of problems that make programmers choose C over other languages. – John Knoeller Dec 28 '09 at 01:50
  • @Andry: Can you quote where in the spec it says 'unspecified'. Its implementation defined, but an implementation must always be consistent with itself across all compilation units... – Chris Dodd Dec 28 '09 at 01:51
  • @Roger: sorry, a typo -- I meant 6.2.5.20 – Chris Dodd Dec 28 '09 at 01:53
  • 6.5.2.3/5 says ".. if a union contains several structures that share a common initial sequence .."; however it's not clear to me that "structures" here includes arrays, and I'd be heavily inclined to say it does not, more so after reading the examples from 6.5.2.3/6-7. –  Dec 28 '09 at 01:54
  • I understand completely how you can interpret 6.2.5/20 that way, but I find no support for it in C99, and sequentially does not, in general, mean contiguously. –  Dec 28 '09 at 01:58
  • Your edit about undefined vs implementation-defined is exactly the issue at hand. Structure padding is implementation-defined, and thus "this will work on any compiler that follows the C99 standard" is just plain wrong. –  Dec 28 '09 at 02:06
  • 2
    @John: No. It *is* impossible to write drivers in C language. It *is* impossible to write operating systems in C language. There's nothing wrong with it. C language in its current form is not intended to be used in any of these applications. Modern C is designed with considerable level of portability in mind, while the above application areas are explictly non-portable. Yes, in practice people do use *C compilers* to implement drivers. It just so happend historically that C compilers are often used for the tasks very loosely related to *C language*, like writing drivers. – AnT stands with Russia Dec 28 '09 at 18:38
  • 2
    @John: It is a well-known fact that you can use C compilers and some heavily hacked-up dialect of some very platform-dependent C-like language to write some low level platform-dependent code, like drivers. Great. Everybody knows that. But why keep polluting discussions labelled as "C" with this information is not clear to me. – AnT stands with Russia Dec 28 '09 at 18:41
  • @Chris Dodd: I din't indend to use the word "unspecified" as a standard term for unspecified behavior. I just wanted to say that at the abstract C language level there's no specification for the amount of padding that can be added in a struct. – AnT stands with Russia Dec 28 '09 at 18:43