含双float的结构体与float[2][1]数组的内存布局差异及memcpy失效原因
问题回顾
我能把含两个float的结构体指针转成void*再转成float数组指针正常使用,但反向用memcpy()却失效了。示例代码:
struct { float x; float y; } s; float f[2][1] = {{6}, {3}};
尝试执行以下复制操作:
memcpy(&s, f, sizeof(f));
结果f[0][0]和f[1][0]全部变成0。我原本认为这等价于:
s.x = f[0][0]; s.y = f[1][0];
请问这是为什么?
补充的完整代码:
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); // 指针类型不兼容,但可运行 x->x = buffer[0][0]; x->y = buffer[1][0]; //memcpy(x, buffer, sizeof(buffer)); // 该语句失效 }
另外,包含两个float的结构体与float[2][1]数组的内存布局有什么差异?
问题解答
1. memcpy失效的直接原因
你搞反了memcpy的参数顺序!memcpy的第一个参数是目标地址,第二个是源地址。你写的memcpy(&s, f, sizeof(f))是把s的未初始化内容(大概率是0)复制到f里,所以f的元素被覆盖成0了。
正确写法应该是:
memcpy(&s, f, sizeof(s)); // 用sizeof(f)也可以,因为两者内存大小一致
补充代码里的memcpy(x, buffer, sizeof(buffer))参数顺序是对的(x是目标,buffer是源),如果仍失效,大概率是结构体存在内存对齐填充,或者struct State的定义与示例匿名结构体不一致(比如多了其他成员)。
2. 结构体与float[2][1]的内存布局差异
- 结构体:
struct { float x; float y; }在无内存填充的情况下,内存是连续的两个float,布局为[x][y],两个变量紧挨着。 - float[2][1]二维数组:本质是包含2个元素的数组,每个元素是1个
float的数组。内存布局为[f[0][0]][f[1][0]]——由于单个元素的数组不会有额外填充,从字节层面看,它和float[2](以及无填充的结构体)的内存分布完全一致,都是连续的8字节(假设float占4字节)。
两者的差异仅出现在编译器对结构体进行内存对齐填充时:比如编译器默认对齐为8字节,若结构体因某些规则在x和y之间插入填充字节,结构体的内存大小会大于8字节,与float[2][1]的大小不一致,此时memcpy会复制错误内容。
你可以打印地址验证布局是否一致:
printf("&s.x: %p, &s.y: %p\n", &s.x, &s.y); printf("&f[0][0]: %p, &f[1][0]: %p\n", &f[0][0], &f[1][0]);
如果两个地址的差值均为sizeof(float),说明布局完全一致;若差值更大,说明结构体存在填充。
3. 补充代码里的隐患
add(x, u, buffer)的指针类型不兼容属于未定义行为——C标准不允许将结构体指针直接转为二维数组指针,当前编译器能运行只是巧合,换环境或编译器可能出问题。正确做法是将结构体成员转为数组再传递:
float temp[2][1] = {{x->x}, {x->y}}; add(temp, u, buffer);
这样就不存在类型兼容问题了。
内容的提问来源于stack exchange,提问作者TheRedstoneHive

