我不太熟悉C标准,所以请原谅我。我想知道,根据标准,memcpy(0,0,0)是否是安全的。我能找到的唯一限制是,如果内存区域重叠,那么行为是未定义的。但我们能不能认为记忆区域在这里重叠了呢?
memcpy(0,0,0)
hmtdttj41#
我有一个C标准(ISO/IEC 9899:1999)的草案版本,它有一些关于这个调用的有趣的事情要说。如果声明为size_t n的参数指定函数的数组长度,则在调用该函数时,n可以具有值零。除非在本小节中的特定函数描述中另有明确说明,否则此类调用的指针参数仍应具有有效值,如7.1.4中所述。在此类调用中,定位字符的函数没有找到匹配项,比较两个字符序列的函数返回零,复制字符的函数复制零个字符。此处所指的是:如果一个函数的参数有一个无效的值(比如一个在函数定义域之外的值,或者一个在程序地址空间之外的指针,或者一个空指针,或者一个指向不可修改的存储的指针,当相应的参数不是常量限定的时候)或者一个类型(在提升之后)不是一个具有可变数量参数的函数所期望的,行为是未定义的。因此,根据C规范,
size_t
memcpy(0, 0, 0)
字符串导致未定义的行为,因为空指针被认为是“无效值”。也就是说,如果你这样做,任何memcpy的实际实现都会崩溃,我会非常惊讶,因为如果你说复制零字节,我能想到的大多数直观实现都不会做任何事情。
memcpy
v440hwme2#
为了好玩,gcc-4.9的发行说明指出,它的优化器利用了这些规则,例如,可以在
int copy (int* dest, int* src, size_t nbytes) { memmove (dest, src, nbytes); if (src != NULL) return *src; return 0;}
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)有时是对这些标准的有用扩展。
copy(0,0,0)
5q4ezhmt3#
您也可以考虑在Git 2.14.x(Q3 2017)中看到的memmove的这种用法。参见commit 168e635(2017年7月16日)和commit 1773664、commit f331ab9、commit 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个元素要移动时。
memmove
rscharfe
gitster
MOVE_ARRAY
NULL
memmove(3)
#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));}
#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);
- memmove(dst, src, (n) * sizeof(*dst));
+ MOVE_ARRAY(dst, src, n);
型它使用宏BUILD_ASSERT_OR_ZERO来Assert构建时依赖关系,作为表达式(@cond是编译时条件,必须为真)。如果条件不为真,或者编译器无法计算,编译将失败。
BUILD_ASSERT_OR_ZERO
@cond
#define BUILD_ASSERT_OR_ZERO(cond) \(sizeof(char [1 - 2*!(cond)]) - 1)
#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))
#define foo_to_char(foo) \
((char *)(foo) \
+ BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))
型正如user16217248在评论中指出的那样:在C11中,BUILD_ASSERT_OR_ZERO宏不是必需的,因为我们有static_assert()
static_assert()
3条答案
按热度按时间hmtdttj41#
我有一个C标准(ISO/IEC 9899:1999)的草案版本,它有一些关于这个调用的有趣的事情要说。
如果声明为
size_t
n的参数指定函数的数组长度,则在调用该函数时,n可以具有值零。除非在本小节中的特定函数描述中另有明确说明,否则此类调用的指针参数仍应具有有效值,如7.1.4中所述。在此类调用中,定位字符的函数没有找到匹配项,比较两个字符序列的函数返回零,复制字符的函数复制零个字符。此处所指的是:
如果一个函数的参数有一个无效的值(比如一个在函数定义域之外的值,或者一个在程序地址空间之外的指针,或者一个空指针,或者一个指向不可修改的存储的指针,当相应的参数不是常量限定的时候)或者一个类型(在提升之后)不是一个具有可变数量参数的函数所期望的,行为是未定义的。
因此,根据C规范,
字符串
导致未定义的行为,因为空指针被认为是“无效值”。
也就是说,如果你这样做,任何
memcpy
的实际实现都会崩溃,我会非常惊讶,因为如果你说复制零字节,我能想到的大多数直观实现都不会做任何事情。v440hwme2#
为了好玩,gcc-4.9的发行说明指出,它的优化器利用了这些规则,例如,可以在
字符串
然后,当调用
copy(0,0,0)
时,它会给出意外的结果(参见https://gcc.gnu.org/gcc-4.9/porting_to.html)。我对gcc-4.9的行为有些矛盾;行为可能符合标准,但能够调用memmove(0,0,0)有时是对这些标准的有用扩展。
5q4ezhmt3#
您也可以考虑在Git 2.14.x(Q3 2017)中看到的
memmove
的这种用法。参见commit 168e635(2017年7月16日)和commit 1773664、commit f331ab9、commit 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个元素要移动时。字符串
示例如下:
型
它使用宏
BUILD_ASSERT_OR_ZERO
来Assert构建时依赖关系,作为表达式(@cond
是编译时条件,必须为真)。如果条件不为真,或者编译器无法计算,编译将失败。
型
范例:
型
正如user16217248在评论中指出的那样:
在C11中,
BUILD_ASSERT_OR_ZERO
宏不是必需的,因为我们有static_assert()