我不太熟悉 C 标准,所以请耐心等待。
我想知道是否按照标准保证
memcpy(0,0,0)
是安全的。
我能找到的唯一限制是,如果内存区域重叠,则行为未定义......
但是我们可以认为这里的内存区域是重叠的吗?
我有 C 标准 (ISO/IEC 9899:1999) 的草案版本,其中有一些关于该调用的有趣内容。首先,它提到(§7.21.1/2)关于
memcpy
声明为
n 的参数指定 a 的数组长度 函数,n 可以在调用该函数时具有零值。除非明确说明 否则,在本子条款中特定函数的描述中,指针参数 此类调用中的 仍应具有有效值,如 7.1.4 中所述。在这样的通话中, 查找某个字符未出现的函数、比较两个字符的函数 字符序列返回零,复制字符的函数复制零 角色。size_t
此处指出的参考指向此:
如果函数的参数具有无效值(例如 在函数域之外,或者在程序地址空间之外的指针, 或空指针,或指向不可修改存储的指针(当相应的 参数不是 const 限定的)或函数不期望的类型(提升后) 参数数量可变,行为未定义。
所以看起来根据 C 规范,调用
memcpy(0, 0, 0)
导致未定义的行为,因为空指针被视为“无效值”。
也就是说,如果你这样做的话,如果
memcpy
的任何实际实现出现问题,我会感到非常惊讶,因为如果你说复制零字节,我能想到的大多数直观实现都不会执行任何操作。
只是为了好玩,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) 有时是对这些标准的有用扩展。
您还可以考虑 Git 2.14.x(2017 年第 3 季度)中看到的
memmove
的用法
参见 commit 168e635(2017 年 7 月 16 日)、commit 1773664、commit f331ab9、commit 5783980(2017 年 7 月 15 日),作者:René Scharfe (
rscharfe
)。 gitster
-- 合并于 commit 32f9025,2017 年 8 月 11 日)
它使用一个 helper 宏
MOVE_ARRAY
来计算大小
基于我们指定的元素数量并支持NULL
当该数字为零时的指针。memmove(3)
调用 NULL
即可
导致编译器(过于急切地)优化稍后的 NULL
检查。
添加了一个安全且方便的助手,用于移动可能重叠的数组条目范围。MOVE_ARRAY
它推断元素大小,自动安全地相乘以获得以字节为单位的大小,通过比较元素大小进行基本类型安全检查,与不同,它支持memmove(3)
指针,前提是要移动 0 个元素。NULL
#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()