我可以将 "number of bytes" 设置为零并调用 memcpy() 和 memmove() 吗?

132

当我实际上没有要移动/复制的内容时,我需要将memmove()/memcpy()视为边缘情况吗?

int numberOfBytes = ...
if( numberOfBytes != 0 ) {
    memmove( dest, source, numberOfBytes );
}

还是直接调用函数而不进行检查?

int numberOfBytes = ...
memmove( dest, source, numberOfBytes );

在前面的代码片段中,这个检查是必要的吗?


6
这个问题让我想起了在像free这样的函数上检查空指针的做法。虽然不是必需的,但我会在那里加上注释来表明你已经考虑过了。 - Toad
12
@Toad: 除了让代码变得混乱外,它还有什么作用呢?当我阅读某人的代码时,我不需要知道原始程序员“思考过进行这个其实不必要的操作,但由于它是不必要的,所以我没有这样做”。如果我看到一个指针被释放,我知道它可以为空,所以我不需要知道原始程序员在“我应该检查空值吗”这个问题上的想法。同样的情况也适用于使用memcpy复制0字节的情况。 - jalf
10
在Stack Overflow上提出问题会让人们对其产生疑问。因此,添加评论可能对你没有帮助,但可以帮助那些知识水平较低的人。 - Toad
2
@Toad 是的,原则上明确指出为什么一个看起来必要的检查实际上并不需要的评论是有价值的。另一方面,这个特定的例子是一个常见的情况,涉及到一个标准库函数,每个程序员只需要学习一次答案;然后他们就可以在阅读任何程序时认识到这些检查是不需要的。因为这个原因,我会省略这些注释。一个有多个这样调用的代码库要么需要将注释复制粘贴到每个调用中,要么在某些调用中任意使用它们,这两种方法都很丑陋。 - Mark Amery
2个回答

174
根据C99标准(7.21.1/2)规定,如果一个函数的参数声明为size_t n,表示该数组的长度,那么在调用该函数时,n可以为零。除非在本子句中某个特定函数的描述中明确说明,否则,在这样的调用中,指针参数仍然必须有有效值,如7.1.4所述。在这样的调用中,查找字符的函数将找不到任何匹配项,比较两个字符序列的函数返回零,复制字符的函数将复制零个字符。
因此答案是否定的,不需要进行检查(或者说是肯定的,您可以传递零)。

3
如果一个指针指向数组的最后一个元素之后的位置,那么在这样的函数中,这个指针是否被认为是“有效”的?这样的指针不能被合法地间接引用,但可以安全地执行其他一些指针相关的操作,例如从中减去一个。 - supercat
1
@supercat:是的,指向数组末尾后面一个位置的指针在与该数组内(或末尾后面一个位置)其他指针进行指针算术运算时是有效的,但不能被解引用。 - Mike Seymour
@MikeSeymour:引用不应该暗示相反的答案吗:检查是必要的,您不能使用空指针传递零? - neverhoodboy
7
@neverhoodboy说:“不,引用清楚地表明“n”可以有零值。”你是正确的,不能传递空指针,但这不是问题所问的。 - Mike Seymour
3
我的错。非常抱歉。问题涉及尺寸而不是指针。 - neverhoodboy
你说的没错,不能传递空指针,但这不是问题所在。OP没有说明source是什么。在这种长度为0的情况下,被复制的地址通常是NULL。在这种情况下,标准要求进行检查,但通常可以不进行检查,因为memmove不会检查,而且不存在将NULL作为陷阱值的实现(据我所知)。 - Jim Balter

6
正如@You所说,标准规定memcpy和memmove应该可以处理这种情况,因为它们通常是以某种方式实现的。
void *memcpy(void *_dst, const void *_src, size_t len)
{
    unsigned char *dst = _dst;
    const unsigned char *src = _src;
    while(len-- > 0)
        *dst++ = *src++;
    return _dst;
}

你甚至不应该有任何性能惩罚,除了函数调用;如果编译器支持此类函数的内部实现 / 内联,额外的检查可能会使代码稍微慢一点,因为检查已经在 while 中完成。


1
我认为这个函数可能是用汇编语言编写的,因为在汇编语言中,你可以比在C语言中更好地优化内存传输。 - Toad
实际上,我看到的几乎所有实现都是用汇编语言编写的,并尝试使用本地字大小(例如x86上的uint32_t)复制大多数位,但这并不改变答案的实质:这是一个while循环,在开始之前不需要进行大量计算,因此检查已经完成。 - Matteo Italia
12
这个典型的实现方式与是否使用零参数调用这些函数(这些函数甚至可能不是以 C 函数的形式实现)并无关系。 - R.. GitHub STOP HELPING ICE
4
其他回答已经涵盖了它是有效的C代码这一事实,正如我在我的答案开头所说的:“正如@You所说,‘标准’规定memcpy和memmove应该可以处理这种情况。” 我只是加入了我的观点,认为你甚至不应该因为性能原因而担心调用len = 0的memcpy,因为在这种情况下,它几乎没有成本。 - Matteo Italia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接