我对C标准不是很熟悉,请见谅。
我想知道是否有保证,按照标准,memcpy(0,0,0)
是安全的。
我找到的唯一限制是如果内存区域重叠,则行为是未定义的...
但是我们可以认为这里的内存区域重叠吗?
我对C标准不是很熟悉,请见谅。
我想知道是否有保证,按照标准,memcpy(0,0,0)
是安全的。
我找到的唯一限制是如果内存区域重叠,则行为是未定义的...
但是我们可以认为这里的内存区域重叠吗?
我有一份C标准(ISO/IEC 9899:1999)的草稿版本,它对于memcpy
函数的调用有一些有趣的说法。就像(§7.21.1/2)中提到的那样:
当参数声明为
size_t
n时,在函数中n指定数组长度,但在函数调用时,n的值可以为零。除非在本分语言描述中特别说明,否则在这种情况下,对于在该调用中作为指针参数的变量仍然应该有有效值,如7.1.4所述。在这种情况下,查找字符的函数将不会找到字符,比较两个字符序列的函数将返回零,复制字符的函数将复制零个字符。
这里指示的引用指向这里:
如果一个函数的参数具有无效值(例如函数域外的值、程序地址空间外的指针、或者空指针,或者当相应的参数未被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)
有时是标准的有用扩展。
char *p = 0; int i=something;
,即使i
为零,表达式(p+i)
的求值也会产生未定义行为。 - supercatintptr_t
,对生成的值执行适当的计算并进行转换来以实现定义(而不是未定义)的方式。我认为,在C语言中指定p-q的三种情况将会有很大的优势... - supercatp = realloc(q);
,p-q将保持任意非零值。 - supercat你也可以考虑在 Git 2.14.x (Q3 2017) 中看到的 memmove
的使用方法。
请查看提交 168e635(2017年7月16日),以及提交 1773664, 提交 f331ab9, 提交 5783980(2017年7月15日)由René Scharfe (rscharfe
)提交。
(由Junio C Hamano -- gitster
--在提交 32f9025中合并,2017年8月11日)
MOVE_ARRAY
,该宏基于我们指定的元素数量计算大小,并在该数字为零时支持NULL
指针。NULL
的原始memmove(3)
调用可能会导致编译器(过度地)优化后续的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))
BUILD_ASSERT_OR_ZERO
宏不再必要,因为我们有static_assert()
函数。 - undefined
memcpy(0,0,0)
是我见过的C代码中最奇怪的部分。 - templatetypedefmemcpy(outp, inp, len)
这种情况呢?并且这可能发生在动态分配outp
和inp
并且最初为0
的代码中。例如,在p
和len
为0
时,p = realloc(p, len+n)
可以这样工作。我自己使用过这样的memcpy
调用 -- 尽管技术上属于未定义行为,但我从未遇到过不是空操作的实现,也永远不会期望遇到。 - Jim Baltermemcpy(0, 0, 0)
很可能是用于表示动态调用,而不是静态调用...也就是说,这些参数值不需要是字面量。 - Jim Balter