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


80

我不太了解C标准,所以请耐心等待。

我想知道标准是否可以保证它memcpy(0,0,0)是安全的。

我能找到的唯一限制是,如果内存区域重叠,则行为是不确定的...

但是我们可以认为这里的存储区域重叠吗?


7
数学上,两个空集的交集是空的。
Benoit

我想检查一下您想要的(x)libC是否为您完成,但是由于它是asm(此处为elibc / glibc),对于一个清晨来说,它有点太复杂了:)
凯文(Kevin)

16
+1我喜欢这个问题,既因为这是一个奇怪的边缘情况,又因为我认为这memcpy(0,0,0)是我所见过的最奇怪的C代码之一。
templatetypedef

2
@eq您是否真的想知道,或者您是否暗示在任何情况下都不需要?您是否考虑过实际电话可能是memcpy(outp, inp, len)?而且这可能发生在代码中,outp并且在哪里inp动态分配它们,并且最初是0?这适用于例如p = realloc(p, len+n)whenplenare 0。我本人已经使用过这样的memcpy调用-虽然从技术上讲它是UB,但我从未遇到过实现非空操作且从未期望过的实现。
Jim Balter

7
@templatetypedefmemcpy(0, 0, 0)很可能表示动态调用,而不是静态调用。也就是说,那些参数值不必是文字。
Jim Balter

Answers:


71

我有C标准(ISO / IEC 9899:1999)的草稿版本,关于该调用有一些有趣的事情要说。对于初学者来说,它提到的问候(§7.21.1/ 2)memcpy

如果声明为size_tn的参数指定函数数组的长度,则在调用该函数时n的值可以为零。除非在本节中对特定功能的描述中另有明确说明,否则此类调用上的指针参数仍应具有有效值,如7.1.4中所述。在这样的调用中,找不到字符的函数没有出现,比较两个字符序列的函数返回零,而复制字符的函数则复制零字符。

此处指出的参考指向此:

如果函数的参数具有无效值(例如,函数域之外的值,程序地址空间之外的 指针空指针或指向相应参数时不可修改存储的指针)不具有const限定)或参数数量可变的函数所不希望的类型(提升后),其行为是undefined

因此,看起来像根据C规范,

memcpy(0, 0, 0)

导致未定义的行为,因为空指针被认为是“无效值”。

就是说,memcpy如果您这样做,如果有任何实际的中断实现,我将感到非常惊讶,因为如果您说要复制零字节,那么我能想到的大多数直观的实现都将一事无成。


3
我可以肯定的是,标准草案中引用的部分在最终文档中是相同的。这样的调用应该不会有任何麻烦,但是这仍然是您所依赖的不确定行为。因此,“是否可以保证”的答案是“否”。
DevSolar 2011年

8
在生产中您将永远不会使用的实现不会为此类调用产生任何操作,但不会产生任何其他操作,但是允许否则实现的实现是合理的……例如,具有错误检查功能的C解释器或扩展编译器拒绝了请致电,因为它不符合要求。当然,如果Standard确实允许调用,那将是不合理的realloc(0, 0)。用例是相似的,并且我都用过它们(请参阅问题下方的评论)。该标准使该UB变得毫无意义,这是不幸的。
Jim Balter

5
“如果您这样做的话,如果任何实际的memcpy实现都失败了,我将感到非常惊讶”-我用过的会这样;实际上,如果您使用有效指针传递了长度0,则它实际上复制了65536个字节。(它的循环递减长度,然后进行测试)。
MM

14
@MattMcNabb该实现被破坏。
吉姆·巴尔特

3
@MattMcNabb:也许将“正确”添加到“实际”。我认为我们所有人对旧的贫民窟C库的回忆都不那么喜欢,而且我不确定我们当中有多少人喜欢那些被召回的回忆。:)
tmyklebu 2014年

24

只是为了好玩,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)有时是对这些标准的有用扩展。


2
有趣。我理解您的矛盾态度,但这是C语言中优化的核心:编译器假设开发人员遵循某些规则,因此推断出某些优化是有效的(如果遵循这些规则,它们就是有效的)。
Matthieu M.

2
@tmyklebu:给定char *p = 0; int i=something;(p+i)即使i为零,对表达式的求值也会产生未定义的行为。
supercat 2014年

1
@tmyklebu:在空指针陷阱上使用所有指针算法(比较除外)将是一件好事;memcpy()在确保非零计数之前是否应允许在其参数上执行任何指针算术是另一个问题[如果我正在设计标准,则可能会指定ifp为null,p+0可能会陷阱,但memcpy(p,p,0)不会执行任何操作]。恕我直言,更大的问题是大多数未定义行为的开放性。虽然有些事情确实应该代表未定义的行为(例如,调用free(p)...
supercat 2014年

1
...并随后执行p[0]=1;),有许多事情应指定为产生不确定的结果(例如,不相关的指针之间的关系比较不应指定为与任何其他比较一致,而应指定为产生0或1),或应指定为产生比实现定义略宽松的行为(应要求编译器记录所有可能的后果,例如整数溢出,但不指定在任何特定情况下会发生的后果)。
supercat 2014年

8
有人请告诉我,为什么我没有得到stackoverflow徽章“开始了一场火焰大战” :-)
user1998586 2015年

0

您还可以考虑memmove在Git 2.14.x(2017年第三季度)中看到的这种用法

提交168e635(2017年7月16日),以及提交1773664提交f331ab9提交5783980(2017年7月15日)由勒Scharfe( )rscharfe
(由Junio C gitsterHamano合并--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))

2
认为“聪明的”和“愚蠢的”是反义词的优化器的存在使对n的测试成为必要,但是在保证memmove(any,any,0)是no-op的实现上,更高效的代码通常是可能的。除非编译器可以将对memmove()的调用替换为对memmoveAtLeastOneByte()的调用,否则防范聪明/愚蠢的编译器“优化”的变通方法通常会导致编译器无法消除额外的比较。
超级猫

-3

不,memcpy(0,0,0)不安全。标准库可能不会在该调用上失败。但是,在测试环境中,memcpy()中可能存在一些额外的代码,以检测缓冲区溢出和其他问题。那个特殊版本的memcpy()对NULL指针的反应还不确定。


我看不到您的答案添加到现有答案中的任何内容,这些内容已经指出这是不安全的,摘录自标准。
Matthieu M.

我写了一个原因,为什么memcpy的某些实现(“在测试环境中”)可能导致memcpy(0,0,0)出现问题。我认为,这是新的。
Kai Petzke '19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.