美文网首页iOS技术
(WWDC) 理解 Undefined Behavior

(WWDC) 理解 Undefined Behavior

作者: FicowShen | 来源:发表于2019-05-22 13:54 被阅读0次

    内容概览

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

    编译器会捕获问题并发出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;
    }
    

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




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

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

    • 切换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




    转载请注明出处,谢谢~

    相关文章

      网友评论

        本文标题:(WWDC) 理解 Undefined Behavior

        本文链接:https://www.haomeiwen.com/subject/preyzqtx.html