我不太了解C标准,所以请耐心等待。
我想知道标准是否可以保证它memcpy(0,0,0)
是安全的。
我能找到的唯一限制是,如果内存区域重叠,则行为是不确定的...
但是我们可以认为这里的存储区域重叠吗?
我不太了解C标准,所以请耐心等待。
我想知道标准是否可以保证它memcpy(0,0,0)
是安全的。
我能找到的唯一限制是,如果内存区域重叠,则行为是不确定的...
但是我们可以认为这里的存储区域重叠吗?
memcpy(0,0,0)
是我所见过的最奇怪的C代码之一。
memcpy(outp, inp, len)
?而且这可能发生在代码中,outp
并且在哪里inp
动态分配它们,并且最初是0
?这适用于例如p = realloc(p, len+n)
whenp
和len
are 0
。我本人已经使用过这样的memcpy
调用-虽然从技术上讲它是UB,但我从未遇到过实现非空操作且从未期望过的实现。
memcpy(0, 0, 0)
很可能表示动态调用,而不是静态调用。也就是说,那些参数值不必是文字。
Answers:
我有C标准(ISO / IEC 9899:1999)的草稿版本,关于该调用有一些有趣的事情要说。对于初学者来说,它提到的问候(§7.21.1/ 2)memcpy
是
如果声明为
size_t
n的参数指定函数数组的长度,则在调用该函数时n的值可以为零。除非在本节中对特定功能的描述中另有明确说明,否则此类调用上的指针参数仍应具有有效值,如7.1.4中所述。在这样的调用中,找不到字符的函数没有出现,比较两个字符序列的函数返回零,而复制字符的函数则复制零字符。
此处指出的参考指向此:
如果函数的参数具有无效值(例如,函数域之外的值,程序地址空间之外的 指针,空指针或指向相应参数时不可修改存储的指针)不具有const限定)或参数数量可变的函数所不希望的类型(提升后),其行为是undefined。
因此,看起来像根据C规范,
memcpy(0, 0, 0)
导致未定义的行为,因为空指针被认为是“无效值”。
就是说,memcpy
如果您这样做,如果有任何实际的中断实现,我将感到非常惊讶,因为如果您说要复制零字节,那么我能想到的大多数直观的实现都将一事无成。
realloc(0, 0)
。用例是相似的,并且我都用过它们(请参阅问题下方的评论)。该标准使该UB变得毫无意义,这是不幸的。
只是为了好玩,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;
,(p+i)
即使i
为零,对表达式的求值也会产生未定义的行为。
memcpy()
在确保非零计数之前是否应允许在其参数上执行任何指针算术是另一个问题[如果我正在设计标准,则可能会指定ifp
为null,p+0
可能会陷阱,但memcpy(p,p,0)
不会执行任何操作]。恕我直言,更大的问题是大多数未定义行为的开放性。虽然有些事情确实应该代表未定义的行为(例如,调用free(p)
...
p[0]=1;
),有许多事情应指定为产生不确定的结果(例如,不相关的指针之间的关系比较不应指定为与任何其他比较一致,而应指定为产生0或1),或应指定为产生比实现定义略宽松的行为(应要求编译器记录所有可能的后果,例如整数溢出,但不指定在任何特定情况下会发生的后果)。
您还可以考虑memmove
在Git 2.14.x(2017年第三季度)中看到的这种用法
见提交168e635(2017年7月16日),以及提交1773664,提交f331ab9,提交5783980(2017年7月15日)由勒Scharfe( )rscharfe
。
(由Junio C gitster
Hamano合并--在commit 32f9025中,2017年8月11日)
它使用一个辅助宏MOVE_ARRAY
,该宏根据指定的元素数量为我们计算大小,并NULL
在该数量为零时支持指针。
进行原始memmove(3)
调用NULL
会导致编译器(过分急切地)优化以后的NULL
检查。
MOVE_ARRAY
添加了一个安全便捷的帮助器,用于移动可能重叠的数组条目范围。
它推断元素的大小,自动安全地相乘以得到字节大小,通过比较元素的大小进行基本类型的安全检查,并且不像要移动元素0那样memmove(3)
支持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
必须为true的编译时条件)。
如果条件不成立或编译器无法评估该条件,则编译将失败。
#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指针的反应还不确定。