据我所知,以下代码在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
的这种用法会导致未定义的行为吗?
答案 0 :(得分:5)
不,memcpy
不对对齐做出任何假设。它在功能上等同于逐字节复制。
顺便说一句,通过不是字符类型的不同类型的左值访问auto
对象会导致未定义的行为,无论是否对齐。这违反了有效类型规则,C11 6.5 p6和p7。
答案 1 :(得分:-1)
据我所知,两种情况都是UB(但不是因为调用了memcpy
),因为编译器没有正确地强制对齐变量的起始偏移。您可以确保使用特定于编译器的属性强制对齐,但这当然是特定于平台的解决方案。
假设开始偏移是对齐的(这是来自实践的假设),就像编译器通常这样做以获得性能:
在第一个示例中,您首先分配缓冲区索引0. buffer
通常是正确对齐的。 cccc
也会对齐,因为struct
没有打包。在这种情况下,它不应该导致问题。
在第二个示例中,当使用memcpy
时,所有内容都将正确复制,因为(内部)它会尽力为性能执行对齐复制,并且在不可能的情况下,它会按字节顺序复制。在这里,所有结构和缓冲区都与我上面提到的限制一致。
这里的实际问题是什么?
如果你指定&buffer[1]
(给定,通常不对齐),你会冒险(明显在实践中)。访问cccc
将从未对齐的地址加载一个字。在某些架构上,它会导致可怕的SIGBUS。 x86检测到未对齐的寻址并减慢一点(可能),但不会崩溃。