在 C 中使用 memcpy 将数组复制到结构体,反之亦然



>假设我有结构:

typedef struct {
double re;
double im;
}ComplexStruct;

和阵列:

typedef double ComplexArr[2];// [0] -> real, [1] -> imag

今天我使用简单的for循环从ComplexStruct复制到ComplexArr,反之亦然:

//ComplexArr to  ComplexStruct
for (i = 0 ; i < NumEl; i++)
{
ComplexStructVec[i].re = ComplexArrVec[i][0];
ComplexStructVec[i].im = ComplexArrVec[i][1];
}
//ComplexStruct to ComplexArr
for (i = 0 ; i < NumEl; i++)
{
ComplexArrVec[i][0] = ComplexStructVec[i].re;
ComplexArrVec[i][1] = ComplexStructVec[i].im;
}

有没有办法安全地使用至少一个方向的memcpy?有没有另一种方法会比循环for更快?

编译器中的优化器应该可以很好地处理该代码,您无需进行太多更改即可使其达到最佳状态。 但是,如果要将 ComplexStructVec 和 ComplexArrVec 传递到函数中,则应将它们标记为restrict,以便编译器知道没有混叠发生。 喜欢这个:

void copy(ComplexStruct* restrict ComplexStructVec, const ComplexArr* ComplexArrVec)
{
unsigned NumEl = 1000;
for (unsigned i = 0 ; i < NumEl; i++)
{
ComplexStructVec[i].re = ComplexArrVec[i][0];
ComplexStructVec[i].im = ComplexArrVec[i][1];
}
}

通过这样做,您可以消除一大堆生成的代码,因为它不需要处理两个参数重叠的可能性。

演示:https://godbolt.org/z/F3DUaq(只需删除那里的"限制"即可看到区别(。 如果 NumEl 小于 18,它将把整个事情展开为每次迭代的一个加载和一个存储。

是的,您可以使用memcpy,但需要注意一些:

数组
  1. 和结构的布局是相同的,这意味着编译器不会对齐数组中的项或结构中的条目。
  2. 与结构和数组关联的内存大小相同。
  3. 您不关心可移植性到其他体系结构(这可能会更改 #1 和/或 #2 的答案(。
  4. 这不是
  5. 一种理想的编程技术,因为它有一些潜在的陷阱,如上所述(。

如果在上述之后您仍然想执行此操作,则以下代码应该可以解决问题:

/* NOTE: sizeof(ComplexStructVec) === sizeof(ComplexArrVec) */
memcpy((void *) ComplexStructVec,
(void *) ComplexArrVec,
sizeof(ComplexStructVec)*NumEl);

这样做的目的是,由于您在这两种情况下都使用向量(数组(,因此您只需使用它们的名称即可获得它们的地址。memcpy将目标地址和源地址定义为void *,所以我投射了参数。 要复制的字节数是结构或数组的大小(以字节为单位((请参阅注释(乘以向量中的条目数。 可能不需要(void *)石膏。 这取决于编译器、语言标准级别和其他编译时限定符。

另请注意,我故意没有返回值的位置,返回值是指向目的地的指针。 如果需要此信息,请小心,因为将其保存到ComplexStructVec可能会导致编译器(或更糟糕的运行时(问题,具体取决于它的分配方式(由编译器或在运行时(。

一个更完整的示例:

void copy(ComplexStruct* ComplexStructVec, ComplexArr* ComplexArrVec)
{
unsigned NumEl = 1000;
memcpy(ComplexStructVec, ComplexArrVec, sizeof(ComplexStruct)*NumEl);
}

最便携的方式是循环,如您的示例所示。这有时称为结构的序列化/反序列化。

结构的问题在于它们不能像数组那样保证具有一致的内存布局。为了避免对齐问题,编译器可以在任何地方自由添加填充字节。如果结构只包含8个字节double,填充是极不可能的。但是,从形式上讲,它仍然不是便携式的。

但是,您可以相当安全地执行以下操作:

_Static_assert(sizeof(ComplexStruct) == sizeof(double[2]), 
"Weird systems not supported");
ComplexStruct cs;
double arr[2];
memcpy(&cs, arr, sizeof arr);
memcpy(arr, &cs, sizeof arr);

这是"合理可移植"到所有现实世界的系统。

另一种选择是通过添加一个union来为结构体提供两种不同的变量表示,如下所示:

typedef union {
struct // C11 anonymous struct
{
double re;
double im;
};
double arr[2];
}ComplexStruct;

内部结构可能仍然有填充,因此您应该仍然添加一个正式的静态断言。但这使您可以灵活地将数据内容用作单个成员或数组。

最后,C 实际上支持复数的语言。double _Complex是标准 C,complex.h是一个标准化的复杂库。请参阅如何在 C 中使用复数?

最新更新