84

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

我想知道它是否按标准保证memcpy(0,0,0)是安全的。

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

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

4

4 回答 4

73

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

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

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

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

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

memcpy(0, 0, 0)

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

也就是说,memcpy如果你这样做了,如果有任何实际的 break 实现,我都会感到非常惊讶,因为如果你说复制零字节,我能想到的大多数直观实现都不会做任何事情。

于 2011-03-09T08:24:36.010 回答
24

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

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) 有时是对这些标准的有用扩展。

于 2014-07-12T05:32:12.423 回答
0

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

请参阅René Scharfe ( ) 的提交168e635 ( 2017 年 7 月 16 日)和提交 1773664提交 f331ab9提交 5783980(2017 年 7 月 15 日(由Junio C Hamano 合并——提交 32f9025中,2017 年 8 月 11 日)rscharfe
gitster

它使用一个辅助宏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));
}

例子

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

它使用断言构建时依赖关系的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))
于 2017-08-12T08:30:45.453 回答
-3

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

于 2019-01-10T20:04:08.030 回答