【问题标题】:memcpy vs assignment in C -- should be memmove?memcpy 与 C 中的分配——应该是 memmove 吗?
【发布时间】:2011-07-21 16:38:01
【问题描述】:

正如an answer to this question 中所指出的,编译器(在本例中是 gcc-4.1.2,是的,它很旧,不,我无法更改它)可以在它认为合适的地方用 memcpy 替换结构分配。

我在 valgrind 下运行一些代码并收到有关 memcpy 源/目标重叠的警告。当我查看代码时,我看到了这个(释义):

struct outer
{
    struct inner i;
    // lots of other stuff
};

struct inner
{
    int x;
    // lots of other stuff
};

void frob(struct inner* i, struct outer* o)
{
    o->i = *i;
}

int main()
{
    struct outer o;

    // assign a bunch of fields in o->i...

    frob(&o.i, o);
    return 0;
}

如果 gcc 决定用 memcpy 替换该分配,那么这是一个无效的调用,因为源和目标重叠。

显然,如果我将frob 中的赋值语句改为调用memmove,那么问题就消失了。

但这是一个编译器错误,还是该赋值语句在某种程度上无效?

【问题讨论】:

  • 我很确定你的意思是写 frob(..., &o),而不是 (..., 0)。
  • 这是一个编译器错误。整个 gcc 4.x 系列充满了这样的废话,包括打破 gcc 1.0 之前的 LIST_ENTRY / LIST_HEAD 类型双关语。加入 gcc-4 抵抗!
  • @Andy - 我的胖手指。谢谢。
  • @Heath:整个 4.x系列?你是认真地建议我们回到 3.x 吗?或者您是否发明了一台时间机器并且您可以使用令人敬畏的未来派 5.x 系列?
  • 接受 Jens 的回答,因为我认为它最清楚地定义了问题。 R.. 认为这是一个 gcc 错误(至少是一个潜伏的错误)可能是正确的,但在我的特定组合中它不是问题。至少我把它改成了 memmove 让 valgrind 闭嘴。感谢您的回答。

标签: c optimization gcc memcpy memmove


【解决方案1】:

我认为您混淆了级别。 gcc 通过调用任何它喜欢的库函数来替换赋值操作是完全正确的,只要它可以保证正确的行为。

这不是“调用”memcpy 或标准意义上的任何东西。它只是使用它的库中的一个函数,它可能有额外的信息来保证正确性。标准中描述的memcpy 的属性是被视为程序员接口的属性,而不是编译器/环境实现者的接口。

memcpy 在相关实现中是否实现了使其对赋值操作有效的行为是另一个问题。检查甚至检查代码应该不难。

【讨论】:

  • 对。该错误(如果有)在 valgrind 中 - 不允许在那里使用 memcpy(),但实现是,因为它是提供 memcpy() 的那个。
  • 所以你基本上是说,在这种情况下,gcc 知道它使用的memcpy 的特定实现是重叠安全的?
  • @bstpierre,不,我是说它应该知道,并且使用它本身并不违反标准。它是否有效地重叠安全(或它们在赋值运算符中的使用)应该接受测试。
  • 在实践中,如果对象完全重叠,memcpy 的任何合理实现都可以。只有当它们部分重叠时,才需要特别考虑。这可能是 gcc 假设它正常工作的理由。
  • 但是 memcpy 符号来自 libc,比​​如 glibc。编译器不能为重叠参数假设任何特定行为?
【解决方案2】:

据我所知,这是一个编译器错误。根据别名规则,i 被允许为&o.i 别名,因为类型匹配并且编译器无法证明o.i 的地址以前不能被占用。当然,使用重叠(或相同)指针调用 memcpy 会调用 UB。

请注意,在您的示例中,o->i 是无稽之谈。你的意思是o.i 我想...

【讨论】:

  • "invokes UB" - 好吧,如果一个程序这样做了。可以想象,gcc 可能依赖于 glibc 定义结果的某种保证。也许我记错了,但我想我以前见过内核 cmets,大意是对齐的 memcpy 保证是正向副本或其他东西。或者可能是 memmove 的实现中的 cmets?不过,可能根本就不是 GNU。
  • glibc 是我的 C 实现的一部分。因此,当您检查时,我希望您非常仔细地检查 gcc 是如何记录在其记录的行为与 glibc 的记录行为不同的 C 库实现中的行为,因为如果有一些相关的保证,它变得非常重要。
  • 尽管如此,编译器本身不能调用“未定义的行为”。正确实现标准取决于编译器和标准库的组合。如果您将 gcc 与 memcpy() 在这种情况下导致问题的 C 库一起使用,那么您将有一个不合格的实现 - 无论这是 gcc 的错误还是库的错误,都是 gcc 和库作者争论的问题他们之间。
  • @SteveJessop 编译器如何知道链接器将如何解析 memcpy?我可能会换入我自己的符合 C 标准并在重叠时爆炸的 memcpy(或完整的 libc)。
  • @not-a-user:两个不同的东西。首先,标准允许编译器调用memcpy。这一切都很好,但编译器不一定使用该权限。相反,“as-if”规则允许实现做任何它喜欢的事情,只要结果是正确的。如果 glibc 保证结果是正确的,那么编译器可以使用 glibc。如果编译器要求您链接其代码的任何标准库必须提供与 glibc 相同的超标准保证,那么是您通过链接破坏了它。这就是为什么我说要检查手册。
【解决方案3】:

我想有一个错字:“&o”而不是“0”。 在这个假设下,“重叠”实际上是一个严格的覆盖:memcpy(&o->i,&o->i,sizeof(o->i))。在这种特殊情况下,memcpy 的行为正确。

【讨论】:

  • 怎么样?行为未定义。
  • 是与否...当源和目标相同时,gcc 所依赖的 memcpy 的实现可以正常工作:它将每个字节/字复制到自身上。
  • 实现不是 gcc 的一部分,对其进行假设也不是 gcc 的业务。这些假设甚至可能随着不同的库版本而改变。例如,看看 Adob​​e Flash Player 在假定 glibc memcpy 按地址递增顺序复制时发生了什么......
猜你喜欢
  • 1970-01-01
  • 2011-05-23
  • 2014-11-09
  • 2015-01-13
  • 1970-01-01
  • 1970-01-01
  • 2017-12-11
  • 2023-04-02
  • 2021-04-03
相关资源
最近更新 更多