How does the memory layout of a struct of 2 floats differ from an array float[2][1]?

102 Views Asked by At

I can successfully cast a pointer to a struct of 2 floats to a void pointer (used to discard incompatible pointer types warning), and then cast that to a float array and use that normally, however it doesn't seem to work in reverse with memcpy().

Say I have the following

struct {
    float x;
    float y;
} s;

float f[2][1] = {{6}, {3}};

I notice that if I use memcpy() to copy f to s

memcpy(&s, f, sizeof(f));

f[0][0] and f[1][0] are both 0. Intuitively I would expect it to do the same as

s.x = f[0][0];
s.y = f[1][0];

Why does this not work?

EDIT: I have been asked for more complete code:

void add(const float a[2][1], float b, float dest[2][1])
{
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 1; j++)
        {
            dest[i][j] = a[i][j] + b;
        }
    }
}

void predict(struct State *x, float u)
{
    float buffer[2][1];
    add(x, u, buffer); // Incompatible pointer types, but it works.
    x->x = buffer[0][0];
    x->y = buffer[1][0];
    //memcpy(x, buffer, sizeof(buffer)); // doesn't work
}
2

There are 2 best solutions below

6
Support Ukraine On

How does the memory layout of a struct of 2 floats differ from an array float[2][1]?

The array layout is

 ---------------
| float | float |
 ---------------

The struct layout is

 -----------------------------------
| float | padding | float | padding |
 -----------------------------------

How big the padding fields are in bytes depends on the compiler.

The padding may be zero bytes (and typically will be zero bytes) in which case the layout is the same.

But from a standard point of view your code has undefined behavior because the array and the struct may have different size.

0
John Bollinger On

I can successfully cast a pointer to a struct of 2 floats to a void pointer (used to discard incompatible pointer types warning), and then cast that to a float array and use that normally,

Interposing an extra cast may silence the warning, but that does not mean the issue your compiler was warning about is solved. You've basically told your compiler "don't bother me, I know what I'm doing", even though you do not know what you are doing in this regard (else you would not need to pose the present question).

Since you cannot cast to an array type, I presume you mean that you cast to a pointer to an array. In other words,

// probably ...
float (*ap)[1] = (float (*)[1]) s;

// or maybe ...
float (*ap)[2][1] = (float (*)[2][1]) s;

If you then access the struct via the resulting array pointer, you violate the strict aliasing rule, and thereby obtain undefined behavior. Even if the layout of the struct is exactly the same as the layout of an array of two float. That may appear to work as you expect, but that doesn't mean it's ok, nor that it is without adverse effect.

however it doesn't seem to work in reverse with memcpy().

In general, there's no particular reason why it should, though the seeming success of your experiment with casting might give you reason to think so. That's because the language does not answer the title question,

How does the memory layout of a struct of 2 floats differ from an array float[2][1]?

, with any specificity. The members of a struct are required to be arranged in memory in the order they are declared (disregarding bitfields for our purposes), and the first member must appear at the beginning of the struct. As far as C is concerned, however, there may be arbitrary padding after any member or members. On the other hand, the elements of an array are consecutive in memory, and there is no trailing padding, either.

HOWEVER, if the representation of your struct type happens to be the same as that of an array of two float, then memcpy() is a valid way to copy the contents of such an array into the struct.

For example, the SYSV x86_64 ABI used by 64-bit x86_64 Linux does specify that your struct will be laid out the same as an array of two float. On my 64-bit Linux system, this program ...

#include <stdio.h>
#include <string.h>

int main(void) {
    float arr[2][1] = {{6}, {3}};
    printf("%f %f\n", arr[0][0], arr[1][0]);

    struct { float x, y; } s;
    memcpy(&s, arr, sizeof s);
    printf("%f %f\n", s.x, s.y);
}

... compiles fine and outputs exactly what I expect:

jbolling@Lucky:~/tmp> ./example
6.000000 3.000000
6.000000 3.000000