在 C 中使用 memcpy 将数组复制到结构,反之亦然
Using memcpy for copying array to struct and vice versa in C
假设我有结构:
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(只需删除 "restrict" 即可看到差异)。如果 NumEl 小于 18,它将把整个事情展开为每次迭代一次加载和一次存储。
是的,您可以使用 memcpy,但有几点需要注意:
- 数组和结构的布局相同,这意味着编译器不会对齐数组中的项或结构中的条目。
- 与结构和数组关联的内存大小相同。
- 您不关心对其他架构的可移植性(这可能会改变 #1 and/or #2 的答案)。
- 这不是一种理想的编程技术,因为它有一些潜在的缺陷,如上所述。
如果在上面的操作之后你还想这样做,下面的代码应该可以解决问题:
/* NOTE: sizeof(ComplexStructVec) === sizeof(ComplexArrVec) */
memcpy((void *) ComplexStructVec,
(void *) ComplexArrVec,
sizeof(ComplexStructVec)*NumEl);
这样做的目的是,由于您在这两种情况下都使用向量(数组),因此您只需使用它们的名称即可获得它们的地址。 memcpy
将目标地址和源地址定义为 void *
,因此我转换了参数。要复制的字节数是结构或数组(请参阅注释)的大小(以字节为单位)乘以向量中的条目数。可能不需要 (void *)
转换。这取决于编译器、语言标准级别和其他编译时限定符。
另请注意,我故意没有放置 return 值的位置,它是指向目标的指针。如果您需要此信息,请小心,因为将其保存到 ComplexStructVec
可能会导致编译器(或更糟糕的 运行 时间)问题,具体取决于它是如何分配的(由编译器分配或在 运行-次).
一个更完整的例子:
void copy(ComplexStruct* ComplexStructVec, ComplexArr* ComplexArrVec)
{
unsigned NumEl = 1000;
memcpy(ComplexStructVec, ComplexArrVec, sizeof(ComplexStruct)*NumEl);
}
最可移植的方法是循环,如您的示例所示。这有时被称为结构的 serializing/de-serializing。
结构的问题是不能保证它们具有一致的内存布局,就像数组一样。为了避免对齐问题,编译器可以随意在任何地方添加填充字节。如果结构只包含 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);
这是 "reasonably portable" 所有现实世界的系统。
另一种选择是通过添加 union
为结构体提供两个不同的变量表示形式,如下所示:
typedef union {
struct // C11 anonymous struct
{
double re;
double im;
};
double arr[2];
}ComplexStruct;
内部结构可能仍然有填充,所以你应该仍然添加一个正式的静态断言。但这使您可以灵活地将数据内容用作单个成员或数组。
最后,C 实际上具有对复数的语言支持。 double _Complex
是标准的C,complex.h
是标准化的复杂库。参见 How to work with complex numbers in C?
假设我有结构:
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(只需删除 "restrict" 即可看到差异)。如果 NumEl 小于 18,它将把整个事情展开为每次迭代一次加载和一次存储。
是的,您可以使用 memcpy,但有几点需要注意:
- 数组和结构的布局相同,这意味着编译器不会对齐数组中的项或结构中的条目。
- 与结构和数组关联的内存大小相同。
- 您不关心对其他架构的可移植性(这可能会改变 #1 and/or #2 的答案)。
- 这不是一种理想的编程技术,因为它有一些潜在的缺陷,如上所述。
如果在上面的操作之后你还想这样做,下面的代码应该可以解决问题:
/* NOTE: sizeof(ComplexStructVec) === sizeof(ComplexArrVec) */
memcpy((void *) ComplexStructVec,
(void *) ComplexArrVec,
sizeof(ComplexStructVec)*NumEl);
这样做的目的是,由于您在这两种情况下都使用向量(数组),因此您只需使用它们的名称即可获得它们的地址。 memcpy
将目标地址和源地址定义为 void *
,因此我转换了参数。要复制的字节数是结构或数组(请参阅注释)的大小(以字节为单位)乘以向量中的条目数。可能不需要 (void *)
转换。这取决于编译器、语言标准级别和其他编译时限定符。
另请注意,我故意没有放置 return 值的位置,它是指向目标的指针。如果您需要此信息,请小心,因为将其保存到 ComplexStructVec
可能会导致编译器(或更糟糕的 运行 时间)问题,具体取决于它是如何分配的(由编译器分配或在 运行-次).
一个更完整的例子:
void copy(ComplexStruct* ComplexStructVec, ComplexArr* ComplexArrVec)
{
unsigned NumEl = 1000;
memcpy(ComplexStructVec, ComplexArrVec, sizeof(ComplexStruct)*NumEl);
}
最可移植的方法是循环,如您的示例所示。这有时被称为结构的 serializing/de-serializing。
结构的问题是不能保证它们具有一致的内存布局,就像数组一样。为了避免对齐问题,编译器可以随意在任何地方添加填充字节。如果结构只包含 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);
这是 "reasonably portable" 所有现实世界的系统。
另一种选择是通过添加 union
为结构体提供两个不同的变量表示形式,如下所示:
typedef union {
struct // C11 anonymous struct
{
double re;
double im;
};
double arr[2];
}ComplexStruct;
内部结构可能仍然有填充,所以你应该仍然添加一个正式的静态断言。但这使您可以灵活地将数据内容用作单个成员或数组。
最后,C 实际上具有对复数的语言支持。 double _Complex
是标准的C,complex.h
是标准化的复杂库。参见 How to work with complex numbers in C?