iOS技术

(WWDC) 理解 Undefined Behavior

2019-05-22  本文已影响0人  FicowShen

内容概览


什么是 undefined behavior?

本国际标准中未作任何规定的行为 —— ISO C++14 Standard

当你的代码语法正确,但是执行的行为不在语言准许的范围内,这种行为就是Undefined behavior。
比如:除数为0、数组越界、数值类型溢出、更改字符串字面量



编译器如何处理Undefined Behavior?



为什么会有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) 可以针对更复杂的情况给出详细的信息。

编译器会捕获问题并发出warning



未对齐的指针

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 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;
}

可以看出,不同的优化方案可以有差别极大的优化结果!




不同的编译器版本,可能有不同的优化方案。

在这些情况下,优化方案会发生变化:


安全性问题


使用工具进行问题检测



Static Analyzer:

使用建议:



Runtime Sanitizers:



在Scheme - diagnostics中开启:


Swift 更安全

使用Swift也会遭遇Undefined Behavior:




参考内容:
Understanding Undefined Behavior




转载请注明出处,谢谢~

上一篇下一篇

猜你喜欢

热点阅读