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

Is a memcpy between differently aligned pointers UB?

据我了解,以下代码在 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条,

If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

附有脚注,阐明

Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime.

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 的这种用法会导致未定义的行为吗?

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

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

在您的第一个示例中,您在第一个缓冲区索引 0 处分配。buffer 通常对齐正确。 cccc 也将对齐,因为 struct 未打包。在这种情况下应该不会造成问题。

在第二个示例中,当使用 memcpy 时,所有内容都将正确复制,因为(在内部)它会尽最大努力进行对齐复制以提高性能,并且当不可能时,它会按字节进行复制。同样,所有结构和缓冲区都符合我上面提到的限制。

这里的实际问题是什么?

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

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

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