我对C标准不是很精通,所以请多多包涵。
我想知道它是否按标准保证memcpy(0,0,0)
是安全的。
我能找到的唯一限制是,如果内存区域重叠,那么行为是未定义的......
但是我们可以认为内存区域在这里重叠吗?
我对C标准不是很精通,所以请多多包涵。
我想知道它是否按标准保证memcpy(0,0,0)
是安全的。
我能找到的唯一限制是,如果内存区域重叠,那么行为是未定义的......
但是我们可以认为内存区域在这里重叠吗?
我有一个 C 标准的草案版本(ISO/IEC 9899:1999),关于这个调用有一些有趣的事情要说。memcpy
对于初学者来说,它提到了(§7.21.1/2)
如果声明为
size_t
n 的参数指定函数数组的长度,则 n 在调用该函数时可以具有零值。除非在本小节中对特定函数的描述中另有明确说明,否则此类调用上的指针参数仍应具有有效值,如 7.1.4 中所述。在这样的调用中,定位字符的函数找不到出现,比较两个字符序列的函数返回零,复制字符的函数复制零字符。
这里指出的参考指向这一点:
如果函数的参数具有无效值(如函数域外的值,或程序地址空间外的 指针,或空指针,或对应参数时指向不可修改存储的指针不是 const 限定的)或具有可变数量参数的函数不期望的类型(提升后),行为是 undefined。
所以它看起来像根据 C 规范,调用
memcpy(0, 0, 0)
导致未定义的行为,因为空指针被认为是“无效值”。
也就是说,memcpy
如果你这样做了,如果有任何实际的 break 实现,我都会感到非常惊讶,因为如果你说复制零字节,我能想到的大多数直观实现都不会做任何事情。
只是为了好玩,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) 有时是对这些标准的有用扩展。
您还可以考虑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))
不,memcpy(0,0,0)
不安全。标准库可能不会在该调用中失败。但是在测试环境中,memcpy() 中可能存在一些额外的代码来检测缓冲区溢出和其他问题。而那个特殊版本的 memcpy() 对 NULL 指针的反应是,嗯,未定义。