为什么在嵌入式系统代码中通过memcpy复制结构?

Jee*_*tel 11 c embedded struct

在用于复制相同类型结构的嵌入式软件域中,人们不使用直接赋值,而是通过memcpy()函数或每个元素复制来执行.

举个例子来说

struct tag
{

int a;

int b;
};

struct tag exmple1 = {10,20};

struct tag exmple2;
Run Code Online (Sandbox Code Playgroud)

将exmple1复制到exmple2 ..而不是直接写

exmple2=exmple1;
Run Code Online (Sandbox Code Playgroud)

人们用

memcpy(exmple2,exmple1,sizeof(struct tag));
Run Code Online (Sandbox Code Playgroud)

要么

exmple2.a=exmple1.a; 
exmple2.b=exmple1.b;
Run Code Online (Sandbox Code Playgroud)

为什么????

Cli*_*ord 15

一种方式或其他没有什么具体的关于嵌入式系统,使这一危险,语言的语义对于所有平台是相同的.

C已经在嵌入式系统中使用了很多年,早期的C编译器在ANSI/ISO标准化之前不支持直接结构分配.许多从业者要么来自那个时代,要么是由那些曾经或正在使用这些从业者编写的遗留代码的人教授的.这可能是疑问的根源,但它不是ISO兼容实现的问题.在一些非常受资源限制的目标上,由于多种原因,可用的编译器可能不完全符合ISO标准,但我怀疑此功能是否会受到影响.

一个问题(适用于嵌入式和非嵌入式)是在分配结构时,实现不需要复制任何未定义的填充位的值,因此如果执行了结构赋值,然后执行了一个memcmp()而不是成员 -通过成员比较来检验平等,不能保证它们是平等的.但是,如果执行a memcpy(),则将复制任何填充位,以便memcmp()逐个成员比较将产生相等性.

因此,memcpy()在所有情况下(不仅仅是嵌入式)使用它可能更安全,但改进是微不足道的,并且不利于可读性.这是一个奇怪的实现,没有使用最简单的结构赋值方法,这很简单memcpy(),因此不太可能发生理论上的不匹配.

  • +1:我敢打赌,你在嵌入式代码中经常看到`memcpy()`的真正原因更多的是货物编程的结果,而不是确保你可以使用`memcmp()`进行比较. (7认同)

iam*_*ind 5

在您给定的代码中,即使您编写以下内容也没有问题:

example2 = example1;
Run Code Online (Sandbox Code Playgroud)

但假设将来struct定义会更改为:

struct tag
{
  int a[1000];
  int b;
};
Run Code Online (Sandbox Code Playgroud)

现在,如果您按照上面的方式执行赋值运算符,那么(某些)编译器可能会内联代码以进行逐字节(或逐个 int)复制。IE

example1.a[0] = example.a[0];
example1.a[1] = example.a[1];
example1.a[2] = example.a[2];
...
Run Code Online (Sandbox Code Playgroud)

这将导致代码段中的代码膨胀。这种内存错误并不容易被发现。这就是人们使用memcpy.

[但是,我听说现代编译器有足够的能力memcpy在遇到此类指令时在内部使用,特别是对于 POD。]

  • 这将是一个不寻常的 C 实现,它使用成员-成员复制(或在您的情况下元素-元素复制)进行结构分配。这真的是“答案”吗?这个问题对于嵌入式系统有何特殊性?这里不存在“危险”。 (3认同)

max*_*zig 5

通过复制 C 结构memcpy()通常被几十年前学习 C 且此后不再遵循标准化流程的程序员使用。他们只是不知道 C 支持结构体赋值(直接结构体赋值在所有 ANSI-C89 之前的编译器中并不可用)。

当他们了解此功能后,有些人仍然坚持这种memcpy()方式,因为这是他们的习惯。还有一些动机源于货物崇拜编程,例如,据称它memcpy只是更快 - 当然 - 却无法用基准测试用例来支持这一点。

一些新手程序员也会memcpy()使用结构,因为他们要么将结构赋值与结构指针赋值相混淆,要么只是过度使用memcpy()(他们通常还使用更合适的memcpy()地方)。strcpy()

还有memcmp()结构比较反模式,有时被一些程序员引用来memcpy()代替结构赋值。其背后的原因如下:由于C不会自动生成==结构体运算符,并且编写自定义结构体比较函数很繁琐,memcmp()因此用于比较结构体。在下一步中 - 避免比较结构的填充memset(...,0,...)位存在差异 -用于初始化所有结构(而不​​是使用 C99 初始化语法或单独初始化所有字段)并memcpy()用于复制结构!因为memcpy()还复制了填充位的内容......

但请注意,由于以下几个原因,这种推理是有缺陷的:

  • //使用memcpy()//引入新的错误可能性 - 例如提供错误的尺寸memcmp()memset()
  • 当结构包含整数字段时,memcmp()大端和小端架构之间的排序会发生变化
  • 大小在位置终止的nchar数组字段还必须随时将位置之后的所有元素清零 - 否则 2 个其他相等的结构比较不相等0xx
  • 从寄存器到字段的赋值也可能将相邻的填充位设置为不等于 0 的值,因此,与其他相等的结构进行比较后会产生不相等的结果

最后一点最好用一个小例子来说明(假设架构 X):

struct S {
  int a;  // on X: sizeof(int) == 4
  char b; // on X: 24 padding bits are inserted after b
  int c;
};
typedef struct S S;
S s1;
memset(&s1, 0, sizeof(S));
s1.a = 0;
s1.b = 'a';
s1.c = 0;
S s2;
memcpy(&s2, &s1, sizeof(S));
assert(memcmp(&s1, &s2, sizeof(S)==0); // assertion is always true
s2.b = 'x';
assert(memcmp(&s1, &s2, sizeof(S)!=0); // assertion is always true
// some computation
char x = 'x'; // on X: 'x' is stored in a 32 bit register
              // as least significant byte
              // the other bytes contain previous data
s1.b = x;     // the complete register is copied
              // i.e. the higher 3 register bytes are the new
              // padding bits in s1
assert(memcmp(&s1, &s2, sizeof(S)==0); // assertion is not always true
Run Code Online (Sandbox Code Playgroud)

最后一个断言的失败可能取决于代码重新排序、编译器的更改、编译器选项的更改等等。

结论

作为一般规则:为了提高代码正确性和可移植性,请使用直接结构赋值(而不是memcpy())、C99 结构初始化语法(而不是memset)和自定义比较函数(而不是memcmp())。