一个包含两个浮点数的结构体的内存布局与一个float[2][1]数组有何不同?

huangapple go评论69阅读模式
英文:

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

问题

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().

我可以成功地将指向包含两个浮点数的结构体的指针强制转换为void指针(用于消除不兼容的指针类型警告),然后将其强制转换为浮点数数组并正常使用,但使用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()f复制到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

f[0][0]f[1][0]都是0。直观地,我希望它执行与以下相同的操作

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
}

Hope this helps! 希望这有所帮助!

英文:

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(&amp;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 &lt; 2; i++)
    {
        for (int j = 0; j &lt; 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-&gt;x = buffer[0][0];
    x-&gt;y = buffer[1][0];
    //memcpy(x, buffer, sizeof(buffer)); // doesn&#39;t work
}

答案1

得分: 3

The memory layout of a struct of 2 floats differs from an array float[2][1] as follows:

Array layout:

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

Struct layout:

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

The size of the padding fields 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. However, from a standard point of view, your code has undefined behavior because the array and the struct may have different sizes.

英文:

> 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.

答案2

得分: 3

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,

将指向包含2个浮点数的结构体的指针成功地转换为void指针(用于消除不兼容的指针类型警告),然后将其转换为浮点数数组并正常使用,

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;

由于你无法将其转换为数组类型,我推测你是指你将其转换为指向数组的指针。换句话说,

// 可能是这样...
float (*ap)[1] = (float (*)[1]) s;

// 也可能是这样...
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.

如果你随后通过生成的数组指针访问结构体,你将违反严格别名规则,从而获得未定义行为。即使结构体的布局与包含两个float的数组的布局完全相同。这可能看起来符合你的期望,但这并不意味着它是正确的,也不意味着它没有不良影响。

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

然而,似乎在使用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]?

结构体包含2个浮点数的内存布局与数组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.

具体来说,这个问题没有明确的答案。结构体的成员在内存中必须按照它们声明的顺序排列(不考虑位域),并且第一个成员必须出现在结构体的开头。然而,就C语言而言,任何成员后面都可能有任意的填充。另一方面,数组的元素在内存中是连续的,也没有尾部填充。

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.

然而,如果你的结构体类型的表示恰好与包含两个float的数组相同,那么memcpy()是将这种数组的内容复制到结构体的有效方式。

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 ...

例如,64位x86_64 Linux使用的SYSV x86_64 ABI确实规定了你的结构体将与包含两个float的数组的布局相同。在我的64位Linux系统上,这个程序...

#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

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

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

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

... 编译正常,并输出了我所期望的内容:

jbolling@Lucky:~/tmp&gt; ./example
6.000000 3.000000
6.000000 3.000000
英文:

> 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 &lt;stdio.h&gt;
#include &lt;string.h&gt;

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

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

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

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

huangapple
  • 本文由 发表于 2023年7月6日 19:19:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/76628269.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定