32

这个问题是在前面关于 的定义性的问题之后进行的memcpy(0, 0, 0),最终确定为未定义的行为。

如链接问题所示,答案取决于 C11 第 7.1.4:1 条的内容

除非在随后的详细描述中另有明确说明,否则以下每个语句都适用: 如果函数的参数具有无效值(例如函数域之外的值,或程序地址空间之外的指针,或空指针,[…]) […] 行为未定义。[…]

标准函数memcpy()需要指向voidand的指针const void,如下所示:

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);

这个问题之所以值得问,是因为标准中有两个“有效”指针的概念:有些指针可以通过指针算术有效地获得,并且可以有效地与<,>与同一对象内的其他指针进行比较。并且有些指针对取消引用有效。前一个类包括“过去”指针,例如以下代码段中的&a + 1&b + 1,而后一个类不包括这些为有效的。

char a;
const char b = '7';
memcpy(&a + 1, &b + 1, 0);

如果将上述代码段视为已定义的行为,鉴于 的参数memcpy()被键入为void无论如何的指针,因此它们各自的有效性问题不能与取消引用它们有关。或者应该&a + 1并且&b + 1被认为是“在程序的地址空间之外”?

这对我很重要,因为我正在将标准 C 函数的效果形式化。memcpy()我已经编写了as的一个前提条件requires \valid(s1+(0 .. n-1));,直到有人指出 GCC 4.9 已经开始积极优化此类库函数调用,超出了上面公式中表达的内容(确实如此)。这个特定规范语言中的公式\valid(s1+(0 .. n-1))等价于truewhen nis 0,并且不捕获 GCC 4.9 依赖于优化的未定义行为。

4

2 回答 2

18

C11 说:

(C11, 7.24.2.1p2) “memcpy 函数将 n 个字符从 s2 指向的对象复制到 s1 指向的对象中。”

&a + 1它本身是一个指向整数加法的有效指针,但&a + 1不是指向对象的指针,因此调用会调用未定义的行为。

于 2014-08-19T18:39:45.220 回答
5

虽然根据标准的“正确”答案似乎不同意,但我发现在 int a[6]; 之后只是虚伪。诠释 b[6];所有的

memcpy(a+0, b+0, 6);
memcpy(a+1, b+1, 5);
memcpy(a+2, b+2, 4);
memcpy(a+3, b+3, 3);
memcpy(a+4, b+4, 2);
memcpy(a+5, b+5, 1);

应该是有效的(并复制一个在数组末尾结束的区域)而

memcpy(a+6, b+6, 0);

根据计数而不是地址有效。这是复制区域的同一端!

就个人而言,我倾向于定义 memcpy(0,0,0) 也是有效的(理由是只要求有效的指针但没有对象)但至少这是一个单一的情况,而“数组结束”的情况是复制数组末尾区域的其他常规模式的实际例外。

于 2014-08-19T21:47:26.190 回答