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

u2nhd7ah  于 2023-11-16  发布在  其他
关注(0)|答案(3)|浏览(147)

我不太熟悉C标准,所以请原谅我。
我想知道,根据标准,memcpy(0,0,0)是否是安全的。
我能找到的唯一限制是,如果内存区域重叠,那么行为是未定义的。
但我们能不能认为记忆区域在这里重叠了呢?

hmtdttj4

hmtdttj41#

我有一个C标准(ISO/IEC 9899:1999)的草案版本,它有一些关于这个调用的有趣的事情要说。
如果声明为size_t n的参数指定函数的数组长度,则在调用该函数时,n可以具有值零。除非在本小节中的特定函数描述中另有明确说明,否则此类调用的指针参数仍应具有有效值,如7.1.4中所述。在此类调用中,定位字符的函数没有找到匹配项,比较两个字符序列的函数返回零,复制字符的函数复制零个字符。
此处所指的是:
如果一个函数的参数有一个无效的值(比如一个在函数定义域之外的值,或者一个在程序地址空间之外的指针,或者一个空指针,或者一个指向不可修改的存储的指针,当相应的参数不是常量限定的时候)或者一个类型(在提升之后)不是一个具有可变数量参数的函数所期望的,行为是未定义的
因此,根据C规范,

  1. memcpy(0, 0, 0)

字符串
导致未定义的行为,因为空指针被认为是“无效值”。
也就是说,如果你这样做,任何memcpy的实际实现都会崩溃,我会非常惊讶,因为如果你说复制零字节,我能想到的大多数直观实现都不会做任何事情。

v440hwme

v440hwme2#

为了好玩,gcc-4.9的发行说明指出,它的优化器利用了这些规则,例如,可以在

  1. int copy (int* dest, int* src, size_t nbytes) {
  2. memmove (dest, src, nbytes);
  3. if (src != NULL)
  4. return *src;
  5. return 0;
  6. }

字符串
然后,当调用copy(0,0,0)时,它会给出意外的结果(参见https://gcc.gnu.org/gcc-4.9/porting_to.html)。
我对gcc-4.9的行为有些矛盾;行为可能符合标准,但能够调用memmove(0,0,0)有时是对这些标准的有用扩展。

5q4ezhmt

5q4ezhmt3#

您也可以考虑在Git 2.14.x(Q3 2017)中看到的memmove的这种用法。
参见commit 168e635(2017年7月16日)和commit 1773664commit f331ab9commit 5783980(2017年7月15日)by René Scharfe ( rscharfe )
(由Junio C Hamano -- gitster --合并至commit 32f9025,2017年8月11日)
它使用一个辅助宏MOVE_ARRAY,它根据指定的元素数量为我们计算大小,并在该数量为零时支持NULL指针。
使用NULL进行的原始memmove(3)调用可能会导致编译器(过于急切地)优化以后的NULL检查。
MOVE_ARRAY添加了一个安全方便的助手,用于移动可能重叠的数组条目范围。
它推断元素大小,自动安全地相乘以获得字节大小,通过比较元素大小进行基本的类型安全检查,与memmove(3)不同,它支持NULL指针,当0个元素要移动时。

  1. #define MOVE_ARRAY(dst, src, n) move_array((dst), (src), (n), sizeof(*(dst)) + \
  2. BUILD_ASSERT_OR_ZERO(sizeof(*(dst)) == sizeof(*(src))))
  3. static inline void move_array(void *dst, const void *src, size_t n, size_t size)
  4. {
  5. if (n)
  6. memmove(dst, src, st_mult(size, n));
  7. }

字符串
示例如下:

  1. - memmove(dst, src, (n) * sizeof(*dst));
  2. + MOVE_ARRAY(dst, src, n);


它使用宏BUILD_ASSERT_OR_ZERO来Assert构建时依赖关系,作为表达式(@cond是编译时条件,必须为真)。
如果条件不为真,或者编译器无法计算,编译将失败。

  1. #define BUILD_ASSERT_OR_ZERO(cond) \
  2. (sizeof(char [1 - 2*!(cond)]) - 1)


范例:

  1. #define foo_to_char(foo) \
  2. ((char *)(foo) \
  3. + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))


正如user16217248在评论中指出的那样:
在C11中,BUILD_ASSERT_OR_ZERO宏不是必需的,因为我们有static_assert()

展开查看全部

相关问题