不同对齐的指针 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。
据我了解,以下代码在 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。