是不同对齐指针UB之间的memcpy?

时间:2016-09-07 22:32:19

标签: c language-lawyer memcpy c11

据我所知,以下代码在C11中表现出未定义的行为:

#include <string.h>

struct aaaa { char bbbb; int cccc; };

int main(void) {
    unsigned char buffer[sizeof(struct aaaa)] = { 0 };
    struct aaaa *pointer = &buffer[0];

    return (*pointer).cccc;
}

根据N1570第6.5.3.2节第4条,

  

如果为指针指定了无效值,则一元*运算符的行为未定义。

附有一个澄清

的脚注
  

由一元*运算符取消引用指针的无效值包括空指针,不正确地对齐指向的对象类型的地址,以及对象在其生命周期结束后的地址。

struct aaaa *unsigned char *不太可能具有相同的对齐方式,因此我们为pointer指定了无效值,因此使用*pointer会导致UB。

但是,我可以复制结构吗?

#include <string.h>

struct aaaa { char bbbb; int cccc; };

int main(void) {
    unsigned char buffer[sizeof(struct aaaa)] = { 0 };
    struct aaaa target;

    memcpy(&target, buffer, sizeof(struct aaaa));

    return target.cccc;
}

在这里,我们将struct aaaa *unsigned char *传递给memcpy。虽然这看起来和第一段代码一样糟糕,但我在C11中找不到任何规定此代码显示UB的措辞。 memcpy的这种用法会导致未定义的行为吗?

2 个答案:

答案 0 :(得分:5)

不,memcpy不对对齐做出任何假设。它在功能上等同于逐字节复制。

顺便说一句,通过不是字符类型的不同类型的左值访问auto对象会导致未定义的行为,无论是否对齐。这违反了有效类型规则,C11 6.5 p6和p7。

答案 1 :(得分:-1)

据我所知,两种情况都是UB(但不是因为调用了memcpy),因为编译器没有正确地强制对齐变量的起始偏移。您可以确保使用特定于编译器的属性强制对齐,但这当然是特定于平台的解决方案。

假设开始偏移是对齐的(这是来自实践的假设),就像编译器通常这样做以获得性能:

在第一个示例中,您首先分配缓冲区索引0. buffer通常是正确对齐的。 cccc也会对齐,因为struct没有打包。在这种情况下,它不应该导致问题。

在第二个示例中,当使用memcpy时,所有内容都将正确复制,因为(内部)它会尽力为性能执行对齐复制,并且在不可能的情况下,它会按字节顺序复制。在这里,所有结构和缓冲区都与我上面提到的限制一致。

这里的实际问题是什么?

如果你指定&buffer[1](给定,通常不对齐),你会冒险(明显在实践中)。访问cccc将从未对齐的地址加载一个字。在某些架构上,它会导致可怕的SIGBUS。 x86检测到未对齐的寻址并减慢一点(可能),但不会崩溃。