执行memcpy(0,0,0)是否保证安全?

问题描述 投票:0回答:3

我不太熟悉 C 标准,所以请耐心等待。

我想知道是否按照标准保证

memcpy(0,0,0)
是安全的。

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

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

c memcpy language-lawyer null-pointer
3个回答
82
投票

我有 C 标准 (ISO/IEC 9899:1999) 的草案版本,其中有一些关于该调用的有趣内容。首先,它提到(§7.21.1/2)关于

memcpy

声明为

size_t
n 的参数指定 a 的数组长度 函数,n 可以在调用该函数时具有零值。除非明确说明 否则,在本子条款中特定函数的描述中,指针参数 此类调用中的 仍应具有有效值,如 7.1.4 中所述。在这样的通话中, 查找某个字符未出现的函数、比较两个字符的函数 字符序列返回零,复制字符的函数复制零 角色。

此处指出的参考指向此:

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

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

memcpy(0, 0, 0)

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

也就是说,如果你这样做的话,如果

memcpy
的任何实际实现出现问题,我会感到非常惊讶,因为如果你说复制零字节,我能想到的大多数直观实现都不会执行任何操作。


27
投票

只是为了好玩,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) 有时是对这些标准的有用扩展。


0
投票

您还可以考虑 Git 2.14.x(2017 年第 3 季度)中看到的

memmove
的用法

参见 commit 168e635(2017 年 7 月 16 日)、commit 1773664commit f331ab9commit 5783980(2017 年 7 月 15 日),作者:René Scharfe (

rscharfe
)
(由 Junio C Hamano --
gitster
--
合并于 commit 32f9025,2017 年 8 月 11 日)

它使用一个 helper 宏

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))

正如user16217248评论中指出的:

在 C11 中,

BUILD_ASSERT_OR_ZERO
宏不是必需的,因为我们有
static_assert()

© www.soinside.com 2019 - 2024. All rights reserved.