Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/70.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C LLVM、联合、指针强制转换和未定义的行为_C_Clang_Llvm_Undefined Behavior - Fatal编程技术网

C LLVM、联合、指针强制转换和未定义的行为

C LLVM、联合、指针强制转换和未定义的行为,c,clang,llvm,undefined-behavior,C,Clang,Llvm,Undefined Behavior,Clang似乎将联合转换为最严格对齐的成员类型,然后免费使用指针强制转换,例如 union U { double x; int y; }; int f(union U *u) { return u->y; } 编译成 %union.U = type { double } ; Function Attrs: nounwind uwtable define i32 @f(%union.U* %u) #0 { %1 = alloca %union.U*, align 8 s

Clang似乎将联合转换为最严格对齐的成员类型,然后免费使用指针强制转换,例如

union U {
  double x;
  int y;
};

int f(union U *u) { return u->y; }
编译成

%union.U = type { double }

; Function Attrs: nounwind uwtable
define i32 @f(%union.U* %u) #0 {
  %1 = alloca %union.U*, align 8
  store %union.U* %u, %union.U** %1, align 8
  %2 = load %union.U*, %union.U** %1, align 8
  %3 = bitcast %union.U* %2 to i32*
  %4 = load i32, i32* %3, align 8
  ret i32 %4
}
我很惊讶,因为将指针投射到不同的类型,然后取消引用,通常是未定义的行为。当然,LLVM IR没有义务遵循与C相同的UB规则,但在大多数情况下,它是这样做的-这就是Clang遵循C UB规则的方式,它只是将代码相当直接地转录到IR中,并让后端处理它

那么,这究竟是如何/为什么是处理工会问题的有效方法呢

补充说明:上述IR与以下C生成的IR基本相同:

struct U {
  double x;
};

int f(struct U *u) { return *(int*)u; }

唯一的区别是最后的
align 8
变成
align 4
。我希望第二个C代码片段是UB,但第一个不是,因此第二个不能是UB。那么为什么第二个C代码片段不是UB呢?

第一个示例已经定义。如果读取了不是最后写入的成员,则该成员表示的字节将在新类型中重新解释。该类型可能是陷阱表示,在这种情况下,您将获得未定义的行为,但在现代机器上不太可能


第二个示例是由于别名规则而未定义的行为。联合由int类型访问,该类型与struct U或double类型不兼容


正确的代码是未定义行为的可能结果之一。

第二个示例是未定义行为。在一些实际体系结构中,
double
int
具有更严格的对齐要求。甚至可以想象一些深奥的体系结构,其中整数和浮点变量存储在内存的不同区域,以便在单独的ALU和FPU上更高效地运行。相反,当
int
double
不是同一个
union
的一部分时,将
int
的地址强制转换为
double*
并取消引用,例如,在32位Sparc Solaris上可能会出现
SIGBUS
错误,导致程序崩溃

即使在对齐不正确的指针上进行转换也是很困难的(因为仅将无效指针加载到寄存器中可能会使某些系统上的程序崩溃,例如旧式x86保护模式下的无效段选择器)。参见第J.2节和§6.3.2.3节。请注意,您注意到的一个更改,将对齐限制从8字节放宽到4字节,允许指针的低位为
100
,而不是
000
,并且将以
100
结尾的指针强制转换为必须以
000
结尾的指针类型已经是未定义的行为。(学究式的说法是,一个例外是,将空指针强制转换到任何其他指针类型总是安全的,并且会为您提供新类型的空指针。)

未定义的行为意味着编译器可以做任何事情,包括做你字面上说的和你想做的。由于您在第二个示例中显式地投射了指针,因此Clang可能会让您射中自己的脚

你的第一个例子是两个工会成员?您保证获得有效
int
对象的地址。根据(§6.2.5.28),“指向联合类型的所有指针应具有彼此相同的表示和对齐要求。指向其他类型的指针不必具有相同的表示或对齐要求。”第40页脚注41特别指出,“相同的表示和对齐要求意味着作为函数参数、函数返回值和联合成员的互换性。”在§6.7.2.1.16中,“指向联合对象的指针经过适当转换后,指向其每个成员[…],反之亦然。”

将适当的转换实现为标识函数当然是有效的!编译器可以在该体系结构上以任何有意义的方式表示指针,并且标准保证指针的表示对这两个对象都有效


也就是说,如果它读取联合的非活动成员,则该值是未指定的。如果在
int
宽度小于64位的目标上设置
u.y
并读取
u.x
,则
u.x
的对象表示的剩余位可能是任何内容,包括陷阱表示。或者,如果设置
u.x
和read
u.y
,值将取决于
int
double
如何表示的详细信息。

这低于语言级别,因此UB规则不适用。在机器代码级别,工会及其成员都共享同一地址。@BoPersson它低于C级别是的,但高于机器co级别de级别。与机器代码不同,LLVM IR应用了大多数关于未定义行为的C规则。如果你说有一些规则不适用,我准备相信你,但是有没有一个文档确切说明了异常是什么?据我所知,C11允许你访问联合体的任何成员,它会尝试将这些位解释为一个值您访问的类型。如果某些类型存在陷阱表示,这将是一个问题。可能LLVM不针对
int
将陷阱的系统。@BoPersson True,但生成的IR不了解联合。我添加了一些有希望的澄清-真正的问题是生成的IR与生成的f相同rom是一个可选的C代码片段,它对联合一无所知,看起来像UB,为什么不呢?Clang让你自食其果。未定义的行为并不意味着编译器一定会拒绝代码,甚至警告你!在Clang上,即使数据被删除,读取联合的非活动成员也不能保证工作ad是一个公共初始序列的一部分,该序列在活动用户之间共享