views:

325

answers:

9

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)?

+6  A: 

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.

GregS
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
-1 it will work, and it is portable. structure packing and array packing follow the same rules.
John Knoeller
@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.
AndreyT
@Aidan, arrays cannot have padding between members. If I have `T a[N];`, then `a` and `a+1` are `sizeof(T)` bytes apart.
Alok
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
@AdrianT: nonsense. "ideal" compliers may not have such rules. Real compilers have to or no-one would use them.
John Knoeller
John: all compilers must follow the language rule of ` but I see none of that in the mentioned code. Finally, compiler-specifics are obviously *not* portable.
Roger Pate
+1  A: 

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.

anon
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.
nikie
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
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
@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.
anon
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?"
Roger Pate
+6  A: 

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.

AndreyT
Nice, also the bit about aliasing.
Alok
John Knoeller
@John: Good point. Yet the reinterpreting code in the original qustion looks differently.
AndreyT
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(
Roger Pate
A: 

There are often good reasons to want to do this in real code. The compiler certainly doesn't care. you could also use

doSomethingWithVectors(&myVector.x)

It is theoretically possible for structures to lay out in memory in a different order than arrays. But don't get too worried about that. No-one actually does this because it would break a significant fraction of all programs ever written.

In my opinion the main reason to use C over other languages is that you CAN do things like this when you need to. Code that reads binary file formats often does this sort of thing.

In the real world the only reason why this trick won't work is structure member packing. You need to make sure that your compiler is not inserting space between your structure members to align them to some defined packing boundary.

the MS C compiler provides #pragma pack to give you the ability to specify the packing boundary. But In your specific example the default packing for a structure containing nothing but floats should be the same as an array of floats.

if that didn't work than unions like this wouldn't either

union {
  float a[4];
  struct {
     float x;
     float y;
     float z;
     float w;
  };
};

I've seen FFT code that depends on this sort of union working. To name just one example.

John Knoeller
No, you cannot do things like this, even if you need to. And even when you do, it has nothing to do with C language. Just because you can drive nails in with a microscope doesn't mean that a microscope is also a hammer.
AndreyT
As for the "significant fracton of programs ever written". These programs either get ported to real C language, or get thrown into a garbage bin where they belong otherwise.
AndreyT
I have seen structs with union types do this kind of thing, something where one branch is a 4 long float array, and the other is the 4 seperate float variable. Maybe John is right, I think I remember the standard ip_address as looking like that.
GregS
exactly, the world is full of C code that implicitly or explicitly makes the assumption that a struct and an array lay out in memory the same way. This compiler writers may hate it, but it's there and they have to deal with it.
John Knoeller
"Of course 90% of C programs are crud, 90% of everything is crud." (paraphrased) If your real answer is to tell him to use #pragma pack to guarantee struct member alignment, then you should just say that.
Roger Pate
+2  A: 

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
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.
AndreyT
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
@John: Even if there is no packing, it is not guaranteed. See my post for details.
Alok
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)
Roger Pate
@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
A: 

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
A: 

If I had this situation, this is what I would do: http://stackoverflow.com/questions/494597/c-member-variable-aliases/494630#494630

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.

Ray Hidayat
The question is about C, not C++.
Emerick Rogul
+4  A: 

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
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
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
A: 

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
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.
AndreyT
@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
@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
@Roger: sorry, a typo -- I meant 6.2.5.20
Chris Dodd
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.
Roger Pate
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.
Roger Pate
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.
Roger Pate
@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.
AndreyT
@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.
AndreyT
@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.
AndreyT