问题 C别名规则和memcpy


在回答另一个问题时,我想到了以下示例:

void *p;
unsigned x = 17;

assert(sizeof(void*) >= sizeof(unsigned));
*(unsigned*)&p = 17;        // (1)
memcpy(&p, &x, sizeof(x));  // (2)

第1行打破了别名规则。然而,第2行是好的。别名规则。问题是:为什么?编译器是否具有关于memcpy等函数的特殊内置知识,还是有一些其他规则可以使memcpy正常运行?有没有办法在标准C中实现类似memcpy的函数而不破坏别名规则?


10157
2017-07-18 11:41


起源



答案:


C标准 很清楚。命名对象的有效类型 p 是 void*,因为它有一个声明的类型,请参阅 6.5/6。 C99中的别名规则适用于读取  写道,写入 void* 通过 unsigned 左值 (1) 根据未定义的行为 6.5/7

相比之下, memcpy 的 (2) 很好,因为 unsigned char* 可以别名任何对象(6.5/7)。标准定义 memcpy 在 7.21.2/1 如

对于本子条款中的所有函数,每个字符都应解释为它具有unsigned char类型(因此每个可能的对象表示都是有效的并且具有不同的值)。

memcpy函数将s2指向的对象中的n个字符复制到s1指向的对象中。如果在重叠的对象之间进行复制,则行为未定义。

但是如果有使用的话 p 之后,这可能会导致未定义的行为,具体取决于bitpattern。如果没有发生这样的使用,那么该代码在C中就可以了。


根据 C ++标准,我认为在这个问题上还很不清楚,我认为以下情况如下。请不要将此解释作为唯一可能的解释 - 模糊/不完整的规范留下了很大的猜测空间。

线 (1) 是有问题的,因为对齐 &p 可能不适合 unsigned 类型。它会更改存储的对象的类型 p 成为 unsigned int。只要您以后不再访问该对象 p,别名规则不会被破坏,但对齐要求可能仍然存在。

线 (2) 但是没有对齐问题,因此只要您不访问就有效 p 之后作为 void*,这可能会导致未定义的行为取决于如何 void* type解释存储的bitpattern。我不认为对象的类型因此而改变。

有一段很长的时间 GCC Bugreport 这也讨论了通过这样一个演员表示的指针写入的含义以及与placement-new的区别(该列表中的人不同意它是什么)。


13
2017-07-18 11:47



请在评论中看到Marcelo的回答中的问题。对此有何评论? - zvrba
@zvrba,哦,没有什么不同。你刚刚投了 void* 介于两者之间,等于直接铸造。如果你想“模仿”memcpy,你必须这样做 unsigned char *pc = (unsigned char*)&p, *i = (unsigned char*)&x; *pc = *x; *pc++ = *x++; ...这就是它的作用。 - Johannes Schaub - litb
如果srcptr和destptr相等,那么memcpy()所涉及的对象是否被认为是“重叠”,或者是否有任何语言可以明确允许该场景? - supercat


答案:


C标准 很清楚。命名对象的有效类型 p 是 void*,因为它有一个声明的类型,请参阅 6.5/6。 C99中的别名规则适用于读取  写道,写入 void* 通过 unsigned 左值 (1) 根据未定义的行为 6.5/7

相比之下, memcpy 的 (2) 很好,因为 unsigned char* 可以别名任何对象(6.5/7)。标准定义 memcpy 在 7.21.2/1 如

对于本子条款中的所有函数,每个字符都应解释为它具有unsigned char类型(因此每个可能的对象表示都是有效的并且具有不同的值)。

memcpy函数将s2指向的对象中的n个字符复制到s1指向的对象中。如果在重叠的对象之间进行复制,则行为未定义。

但是如果有使用的话 p 之后,这可能会导致未定义的行为,具体取决于bitpattern。如果没有发生这样的使用,那么该代码在C中就可以了。


根据 C ++标准,我认为在这个问题上还很不清楚,我认为以下情况如下。请不要将此解释作为唯一可能的解释 - 模糊/不完整的规范留下了很大的猜测空间。

线 (1) 是有问题的,因为对齐 &p 可能不适合 unsigned 类型。它会更改存储的对象的类型 p 成为 unsigned int。只要您以后不再访问该对象 p,别名规则不会被破坏,但对齐要求可能仍然存在。

线 (2) 但是没有对齐问题,因此只要您不访问就有效 p 之后作为 void*,这可能会导致未定义的行为取决于如何 void* type解释存储的bitpattern。我不认为对象的类型因此而改变。

有一段很长的时间 GCC Bugreport 这也讨论了通过这样一个演员表示的指针写入的含义以及与placement-new的区别(该列表中的人不同意它是什么)。


13
2017-07-18 11:47



请在评论中看到Marcelo的回答中的问题。对此有何评论? - zvrba
@zvrba,哦,没有什么不同。你刚刚投了 void* 介于两者之间,等于直接铸造。如果你想“模仿”memcpy,你必须这样做 unsigned char *pc = (unsigned char*)&p, *i = (unsigned char*)&x; *pc = *x; *pc++ = *x++; ...这就是它的作用。 - Johannes Schaub - litb
如果srcptr和destptr相等,那么memcpy()所涉及的对象是否被认为是“重叠”,或者是否有任何语言可以明确允许该场景? - supercat