【问题标题】:Is it guaranteed to be safe to perform memcpy(0,0,0)?执行 memcpy(0,0,0) 是否保证安全?
【发布时间】:2011-07-11 17:02:49
【问题描述】:

我对C标准不是很精通,所以请多多包涵。

我想知道根据标准是否可以保证memcpy(0,0,0) 是安全的。

我能找到的唯一限制是,如果内存区域重叠,则行为未定义...

但是我们可以认为内存区域在这里重叠吗?

【问题讨论】:

  • 数学上两个空集的交集是空的。
  • 我想检查一下你想要 (x)libC 为你做的事情,但正如 it's asm(此处为 elibc/glibc),这对于清晨来说有点太复杂了 :)
  • +1 我喜欢这个问题,既因为它是一个奇怪的边缘案例,又因为我认为memcpy(0,0,0) 是我见过的最奇怪的 C 代码之一。
  • @eq 您是否真的想知道,或者您是否暗示没有任何情况您想要它?您是否考虑过实际呼叫可能是memcpy(outp, inp, len)?这可能发生在outpinp 动态分配且最初为0 的代码中?例如,当 plen0 时,这适用于 p = realloc(p, len+n)。我自己也使用过这样的memcpy 调用——虽然它在技术上是 UB,但我从未遇到过它不是无操作的实现,也从未期望过。
  • @templatetypedef memcpy(0, 0, 0) 很可能是为了表示动态调用,而不是静态调用……也就是说,这些参数值不必是文字。

标签: c memcpy language-lawyer null-pointer


【解决方案1】:

我有一个 C 标准 (ISO/IEC 9899:1999) 的草案版本,关于这个调用有一些有趣的事情要说。对于初学者,它提到(§7.21.1/2)关于memcpy

声明为size_tn 的参数指定数组的长度 函数,在调用该函数时,n 的值可以为零。除非明确说明 否则,在本小节中对特定函数的描述中,指针参数 如 7.1.4 中所述,在此类调用中仍应具有有效值。在这样的呼叫中, 定位一个字符的函数没有发现任何出现,一个比较两个字符的函数 字符序列返回零,复制字符的函数复制零 字符。

这里指出的参考指向这一点:

如果函数的参数具有无效值(例如值 函数域外,或程序地址空间外的指针, 或者一个空指针,或者一个指向不可修改存储的指针,当对应的 参数不是 const 限定的)或函数不期望的类型(提升后) 使用可变数量的参数,行为未定义

所以它看起来像根据 C 规范,调用

memcpy(0, 0, 0)

导致未定义的行为,因为空指针被认为是“无效值”。

也就是说,如果您这样做,如果 memcpy 的任何实际实现发生故障,我会感到非常惊讶,因为如果您说要复制零字节,我能想到的大多数直观实现根本不会做任何事情。

【讨论】:

  • 我可以肯定,标准草案中引用的部分在最终文件中是相同的。这样的调用应该没有任何问题,但它仍然是您所依赖的未定义行为。所以“是否有保证”的答案是“不”。
  • 您将在生产中使用的任何实现都不会为此类调用产生除无操作之外的任何内容,但允许并合理的实现...例如,C 解释器或带有错误检查的增强编译器会拒绝调用,因为它不符合要求。当然,如果标准确实允许调用,那将是不合理的,就像realloc(0, 0) 一样。用例相似,我都使用过它们(请参阅问题下的评论)。标准制作这个 UB 是毫无意义和不幸的。
  • “如果你这样做了,如果 memcpy 的任何实际实现发生故障,我会感到非常惊讶” - 我使用了一个;实际上,如果您使用有效指针传递长度 0,它实际上复制了 65536 个字节。 (它的循环减少了长度,然后进行了测试)。
  • @MattMcNabb 该实现已损坏。
  • @MattMcNabb:也许将“正确”添加到“实际”。我认为我们都对旧的贫民区 C 库有不那么美好的回忆,我不确定我们中有多少人会欣赏那些被召回的回忆。 :)
【解决方案2】:

只是为了好玩,gcc-4.9 的发行说明表明它的优化器利用了这些规则,例如可以删除条件 in

int copy (int* dest, int* src, size_t nbytes) {
    memmove (dest, src, nbytes);
    if (src != NULL)
        return *src;
    return 0;
}

当调用copy(0,0,0) 时会产生意想不到的结果(参见https://gcc.gnu.org/gcc-4.9/porting_to.html)。

我对 gcc-4.9 的行为有些矛盾;该行为可能符合标准,但能够调用 memmove(0,0,0) 有时是对这些标准的有用扩展。

【讨论】:

  • 有趣。我理解你的矛盾,但这是 C 中优化的核心:编译器假设开发人员遵循某些规则,因此 推断某些优化是有效的(如果遵守规则)。
  • @tmyklebu:给定char *p = 0; int i=something;,即使i 为零,表达式(p+i) 的计算也会产生未定义行为。
  • @tmyklebu:在空指针陷阱上进行所有指针算术(比较除外)将恕我直言是件好事;是否应该允许memcpy() 在确保非零计数之前对其参数执行任何指针运算是另一个问题[如果我正在设计标准,我可能会指定如果p 为空,p+0 可以陷阱,但memcpy(p,p,0) 什么都不做]。恕我直言,一个更大的问题是大多数未定义行为的开放性。虽然有些事情真的应该代表未定义的行为(例如调用free(p)...
  • ...然后执行p[0]=1;) 有很多东西应该被指定为产生不确定的结果(例如,不相关的指针之间的关系比较不应该被指定为与任何其他的一致比较,但应指定为产生 0 或 1),或应指定为产生比实现定义稍宽松的行为(应要求编译器记录所有可能的后果,例如整数溢出,但不指定哪个后果在任何特定情况下都会发生)。
  • 有人请告诉我,为什么我没有得到一个 stackoverflow 徽章“开始了一场火焰战争”:-)
【解决方案3】:

您还可以考虑在 Git 2.14.x(2017 年第三季度)中看到的 memmove 的这种用法

参见commit 168e635(2017 年 7 月 16 日)和commit 1773664commit f331ab9commit 5783980(2017 年 7 月 15 日)René Scharfe (rscharfe)
(由@987654327 中的Junio C Hamano -- gitster -- 合并@,2017 年 8 月 11 日)

它使用helper macro MOVE_ARRAY 来计算大小 基于为我们指定的元素数量并支持NULL 当该数字为零时的指针。
原始memmove(3)NULL 通话可以 导致编译器(过分急切地)稍后优化NULL 检查。

MOVE_ARRAY 添加了一个安全方便的助手,用于移动可能重叠的数组条目范围。
它推断元素大小,自动安全地相乘以获得以字节为单位的大小,通过比较元素大小进行基本类型安全检查,与 memmove(3) 不同,它支持 NULL 指针,如果要移动 0 个元素。

#define MOVE_ARRAY(dst, src, n) move_array((dst), (src), (n), sizeof(*(dst)) + \
    BUILD_ASSERT_OR_ZERO(sizeof(*(dst)) == sizeof(*(src))))
static inline void move_array(void *dst, const void *src, size_t n, size_t size)
{
    if (n)
        memmove(dst, src, st_mult(size, n));
}

Examples:

- memmove(dst, src, (n) * sizeof(*dst));
+ MOVE_ARRAY(dst, src, n);

它使用macro BUILD_ASSERT_OR_ZERO 断言构建时依赖项作为表达式(@cond 是编译时条件,必须为真)。
如果条件不成立,或者编译器无法评估,则编译将失败。

#define BUILD_ASSERT_OR_ZERO(cond) \
(sizeof(char [1 - 2*!(cond)]) - 1)

例子:

#define foo_to_char(foo)                \
     ((char *)(foo)                     \
      + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))

【讨论】:

  • 认为“聪明”和“愚蠢”是反义词的优化器的存在使得对 n 的测试是必要的,但是在保证 memmove(any,any, 0) 将是无操作的。除非编译器可以用对 memmoveAtLeastOneByte() 的调用替换对 memmove() 的调用,否则防止聪明/愚蠢的编译器“优化”的解决方法通常会导致编译器无法消除的额外比较。
【解决方案4】:

不,memcpy(0,0,0) 不安全。标准库可能不会在该调用中失败。但是在测试环境中,memcpy() 中可能存在一些额外的代码来检测缓冲区溢出和其他问题。而那个特殊版本的 memcpy() 对 NULL 指针的反应是,嗯,未定义。

【讨论】:

  • 我看不到您的答案添加到现有答案中的任何内容,这些答案已经指出这是不安全的,摘自标准。
  • 我写了一个原因,为什么 memcpy 的某些实现(“在测试环境中”)可能会导致 memcpy(0, 0, 0) 出现问题。我认为,这是新的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-01
  • 2016-06-01
  • 2016-05-08
  • 1970-01-01
  • 1970-01-01
  • 2018-07-11
相关资源
最近更新 更多