是否保证执行memcpy(0,0,0)是安全的?
我对C标准不太了解,请耐心等待。
我想知道是否可以保证memcpy(0,0,0)
是安全的。
唯一的限制,我可以find的是,如果内存区域重叠,那么行为是未定义的…
但是我们可以认为这里的内存区域是重叠的吗?
我有一个C标准草案(ISO / IEC 9899:1999),对这个呼叫有一些有趣的东西要说。 对于初学者来说,它提到了memcpy
(§7.21.1/ 2)
如果声明为
size_t
n的参数指定函数的数组长度,那么在调用该函数时,n的值可以为零。 除非本小节中对特定函数的描述中另有明确说明,否则这种调用的指针参数仍应具有有效值,如7.1.4所述 。 在这样的调用中,find字符的函数没有发生,比较两个字符序列的函数返回0,而复制字符的函数复制零个字符。
这里所指的参考文献指出:
如果一个函数的参数有一个无效的值(比如函数的域之外的值,或者程序的地址空间之外的指针 ,或者空指针 ,或者指向相应参数的不可修改存储的指针不是const限定的)或者是一个types(在提升之后),如果函数的参数数量是可变的, 那么这个行为是不确定的 。
所以看起来像根据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季度)
请参阅提交168e635 (2017年7月16日),并提交1773664 , 提交f331ab9 , 提交5783980 (2017年7月15日) RenéScharfe( rscharfe
) 。
(由Junio C gitster
合并- gitster
-在2017年8月11日的gitster
提交 )
它使用一个辅助macrosMOVE_ARRAY
,它根据我们指定的元素数量来计算大小,当这个数字为零时,它支持NULL
指针。
原始的memmove(3)
调用NULL
可以导致编译器(过分急切地)优化以后的NULL
检查。
MOVE_ARRAY
添加一个安全和方便的助手来移动可能重叠的数组项。
它推断元素的大小,自动和安全地乘以字节大小,通过比较元素的大小进行基本的types安全检查,而不像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);
它使用macrosBUILD_ASSERT_OR_ZERO
作为expression式(其中@cond
是编译时条件,必须为真)来声明构build时依赖关系。
如果条件不正确或编译器无法评估,编译将失败。
#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))