(WWDC) 理解 Undefined Behavior
内容概览
- 什么是 undefined behavior?
- 编译器 和 undefined behavior
- 安全性问题
- 使用工具进行问题检测
- Swift 更安全
什么是 undefined behavior?
本国际标准中未作任何规定的行为 —— ISO C++14 Standard
当你的代码语法正确,但是执行的行为不在语言准许的范围内,这种行为就是Undefined behavior。
比如:除数为0、数组越界、数值类型溢出、更改字符串字面量
编译器如何处理Undefined Behavior?
- 诊断并给出 warning 或 error
- 按照文档中的规则执行
- 产生不可预料的结果
为什么会有Undefined Behavior?
这其实是一个权衡的结果,C、C++、Objective-C这些语言更看重性能而不是安全。
也正因如此,我们的操作系统才能这么高效地运行。
不过,开发者需要为此付出代价。
所以,开发者需要知道这些行为并且知道如何解决他们。
Undefined Behavior 示例
使用未初始化的变量
int uninitialized_variable(int arg) {
int value;
if (arg <= 0)
value = 42;
return arg + value;
}
如果为该函数传入正数,就会导致 Undefined Behavior,因为value
未初始化就被使用。
这时候编译器会捕获问题并发出warning。静态分析器(static analyzer) 可以针对更复杂的情况给出详细的信息。
未对齐的指针
char *serialize_misaligned(char *buffer, int a, int b) {
*(int *)buffer = a;
buffer += sizeof(a);
*(int *)buffer = b;
buffer += sizeof(b);
return buffer;
}
该函数传入的是char类型的指针,但是使用时却变成了int类型。
问题在于,不是所有的char类型的指针都可以变成int类型的指针。
int类型需要对齐,也就是,在内存中的地址偏移量是4。
int类型需要对齐
在不同的硬件架构下迁移代码时,这个问题会更容易发生。因为不同的硬件有不同的对齐约束。
不过,从Xcode 9开始,你可以使用 Undefined Behavior Sanitizer
来捕获这些问题。
编译器 和 Undefined Behavior
Undefined Behavior 可以提供信息
在第一行中,因为Undefined Behavior指出Signed int不能溢出,所以编译器就可以假设 x
一定小于 x + 1
。
所以编译器可以直接按照整数偏移量去内存寻址,这可以使代码运行更高效。
在最后一行中,因为Undefined Behavior指出空指针不能取值,所以编译器断定被取值的指针一定不为NULL,然后就可以对代码进行优化。
编译器如何优化代码
编译器读入你的源代码,然后生成中间代码并进行分析和优化,最后输出二进制。
优化无用代码
无用代码:不执行或者不对执行结果造成影响的代码。
移除无用代码可以缩减应用的体积。
示例代码:
int foo(int *P) {
int var = *P;
return 42;
}
消除无用代码后:
int foo(int *P) {
return 42;
}
所以,即使对该函数传入NULL,也不会发生错误。
因为该段代码无用,编译器已经将其消除。
优化顺序
void contains_null_check(int *P) {
int unused = *P;
...
if (P == NULL)
return;
*P = 4;
}
对上面的代码进行优化时,可能有不同的优化方案。
1.消除多余的NULL检查:
void contains_null_check(int *P) {
int unused = *P;
...
*P = 4;
}
2.消除无用代码:
void contains_null_check(int *P) {
...
*P = 4;
}
1.消除无用代码:
void contains_null_check(int *P) {
...
if (P == NULL)
return;
*P = 4;
}
2.消除多余的NULL检查:
void contains_null_check(int *P) {
...
if (P == NULL)
return;
*P = 4;
}
可以看出,不同的优化方案可以有差别极大的优化结果!
不同的编译器版本,可能有不同的优化方案。
在这些情况下,优化方案会发生变化:
- 切换debug和release模式
-
切换优化选项
- 切换不同的编译器
-
切换模拟器和真机
- 切换Xcode的主要版本
安全性问题
- 缓冲区溢出
- 使用未初始化的变量
- 使用释放后的变量
- 重复释放
- 多线程数据竞争
使用工具进行问题检测
Static Analyzer:
使用建议:
- 在每次build的时候运行
- 在CI平台中运行
Runtime Sanitizers:
- Address Sanitizer (缓冲区溢出、使用释放后的变量、重复释放)
- Thread Sanitizer (数据竞争)
- Undefined Behavior Sanitizer (未对齐的指针、对NULL指针取值、整数溢出、类型不匹配等)
在Scheme - diagnostics中开启:
Swift 更安全
使用Swift也会遭遇Undefined Behavior:
- 需要和C语言家族的API交互
- 使用UnsafePointer<T>, UnsafeMutableRawBufferPointer类型
参考内容:
Understanding Undefined Behavior
转载请注明出处,谢谢~