【问题标题】:Is it safe to memcpy to a dynamic storage struct?memcpy 到动态存储结构是否安全?
【发布时间】:2018-07-11 14:38:28
【问题描述】:

上下文:

我正在查看一些从 IO 描述符接收数据到字符缓冲区的代码,对其进行一些控制,然后使用接收到的缓冲区的一部分来填充结构,突然想知道是否可能涉及严格的别名规则违规.

这是一个简化版

#define BFSZ 1024
struct Elt {
   int id;
   ...
};

unsigned char buffer[BFSZ];
int sz = read(fd, buffer, sizeof(buffer)); // correctness control omitted for brievety

// search the beginning of struct data in the buffer, and process crc control
unsigned char *addr = locate_and_valid(buffer, sz);

struct Elt elt;

memcpy(&elt, addr, sizeof(elt)); // populates the struct

// and use it
int id = elt.id;
...

到目前为止,一切都很好。如果缓冲区确实包含结构的有效表示 - 假设它是在同一平台上生成的,因此没有字节顺序或填充问题 - memcpy 调用已填充结构并且可以安全地使用它。

问题:

如果结构是动态分配的,则它没有声明的类型。让我们将最后几行替换为:

struct Elt *elt = malloc(sizeof(struct Element)); // no declared type here

memcpy(elt, addr, sizeof(*elt)); // populates the newly allocated memory and copies the effective type

// and use it
int id = elt->id;  // strict aliasing rule violation?
...

C 语言草案 n1570 在 6.5 表达式 §6 中说明

访问其存储值的对象的有效类型是声明的类型 对象,如果有的话。87) 如果一个值通过 左值的类型不是字符类型,则左值的类型变为 该访问和不修改的后续访问的对象的有效类型 存储的值。 如果一个值被复制到一个没有声明类型的对象中 memcpy 或 memmove,或复制为字符类型的数组,则为有效类型 为该访问和后续访问不修改 value 是从中复制值的对象的有效类型(如果有的话)

buffer 确实有一个有效的类型,甚至是一个声明的类型:它是一个unsigned char 的数组。这就是代码使用memcpy 而不是像这样的别名的原因:

struct Elt *elt = (struct Elt *) addr;

这确实是一个严格的别名规则违规(并且可能额外带来对齐问题)。但是如果memcpyelt 指向的区域提供了一个有效类型的无符号字符数组,那么一切都会丢失。

问题:

memcpy 从字符类型数组到没有声明类型的对象是否会给出有效的字符数组类型?

免责声明:

我知道它可以在所有常见编译器中正常工作而不会发出警告。我只想知道我对标准的理解是否正确


为了更好地展示我的问题,让我们考虑一个不同的结构 Elt2 与 sizeof(struct Elt2)sizeof(struct Elt),和

struct Elt2 actual_elt2 = {...};

对于静态或自动存储,我无法重用对象内存:

struct Elt elt;
struct Elt2 *elt2 = &elt;
memcpy(elt2, &actual_elt2, sizeof(*elt2));
elt2->member = ...           // strict aliasing violation!

虽然对于动态来说很好(关于它的问题there):

struct Elt *elt = malloc(sizeof(*elt));
// use elt
...
struct Elt2 *elt2 = elt;
memcpy(elt2, &actual_elt2, sizeof(*elt2));
// ok, memory now have struct Elt2 effective type, and using elt would violate strict aliasing rule
elt2->member = ...;        // fine
elt->id = ...;             // strict aliasing rule violation!

从 char 数组复制有什么不同?

【问题讨论】:

  • struct Elt elt;struct Elt *elt = malloc(sizeof *elt); 的区别只是结构的内存位置。就是这样。无论结构的内存是如何分配的,或者它位于何处,这两个结构都同样有效。
  • @Someprogrammerdude:这就是我几十年来的想法,但是声明类型与有效类型的问题让我害怕......
  • 您的编辑没有任何意义。 struct Elt2 *elt2 = elt; 不是有效的 C。您的原始问题没有任何违反约束的野指针转换。我不知道你在问什么了。投票结束,因为不清楚。
  • @Lundin:你是对的,这是一个不同的问题。我问了there
  • @chux :感谢您的关注。帖子已编辑...

标签: c struct malloc language-lawyer memcpy


【解决方案1】:

代码很好,没有严格的别名违规。指向的数据具有有效类型,因此粗体引用的文本不适用。此处适用的是您遗漏的部分,即 6.5/6 的最后一句:

对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。

所以指向对象的有效类型变为struct Elt。 malloc 的返回指针确实指向了一个没有 delcared 类型的对象,但是一旦你指向它,有效类型就变成了 struct 指针的类型。否则 C 程序根本无法使用 malloc。

使代码安全的原因还在于您将数据复制到该结构中。如果您只是分配了一个struct Elt* 来指向与addr 相同的内存位置,那么您将遇到严格的别名违规和UB。

【讨论】:

  • 问题是,如果我写 struct Elt2 elt2 = elt; memcpy(elt2, true_elt2_here); 其中Elt2 是适合分配内存的不同结构(sizeof(struct Elt2) sizeof(struct Elt)),该区域实际上将包含一个Elt2 对象,原来的elt 会丢失。这就是分配的内存没有声明类型的原因。我的问题确实是:char 数组中的 memcpy 是否不同?
  • @SergeBallesta 您的问题缺少一些东西,因为struct Elt2 elt2 = elt; 将是无效的初始化。如果您在问题中添加说明会更好。
  • @user694733:我刚刚用它编辑了我的问题。
  • @SergeBallesta 这个例子没有任何意义。您不能分配不同类型的对象。没有声明类型的对象意味着您以某种方式拥有一个指向原始数据的 void 指针。一般来说,指针别名根本不适用于制作数据的硬拷贝。
  • 因为 malloc() 返回一个指向内存的指针,该指针适合任何类型的对齐,只要您将结构的 memcpy 视为您从中复制的任何结构类型,该结构的 memcpy 到该缓冲区是非常安全的。然而,堆栈上的某些字符缓冲区可能并非如此。在任何情况下,严格的别名在这里都不是问题。
【解决方案2】:

Lundin 的回答是正确的;您正在做的事情很好(只要数据对齐并且具有相同的字节序)。

我想指出,这与其说是 C 语言规范的结果,不如说是 硬件 工作方式的结果。因此,没有一个权威的答案。 C 语言规范定义了 语言 的工作方式,而不是该语言在不同系统上的编译或实现方式。

这是一篇关于 SPARC 与 Intel 处理器上的内存对齐和严格别名的有趣文章(请注意,完全相同的 C 代码执行不同,并且在一个平台上工作时会在另一个平台上出错): https://askldjd.com/2009/12/07/memory-alignment-problems/

从根本上说,两个相同的结构,在具有相同字节序和内存对齐方式的同一系统上,必须通过 memcpy 工作。如果没有,那么计算机将无法执行任何操作。

最后,以下问题解释了有关系统内存对齐的更多信息,joshperry 的回答应该有助于解释为什么这是硬件问题,而不是语言问题: Purpose of memory alignment

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-05
    • 1970-01-01
    • 2021-01-25
    • 1970-01-01
    • 2011-07-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多