签到

06月20日
尚未签到

共有回帖数 0

    回望空城

    等级:
    原文:blog.llvm.org/2011/05/what-every-c-programmer-should-know.html

      人们偶尔会问为什么LLVM的汇编代码有时会在优化器打开时产生SIGTRAP信号。经过深入研究,他们发现Clang生成了“ud2”指令(假设x86代码)——和__builtin_trap()生成的一样。这里关系到若干问题,都围绕C代码的未定义行为和LLVM如何处理它展开。
      本博文(作为包括三篇的一个系列的第一篇)试图解释其中的一些问题,以便使读者可以更好地了解为此作出的权衡和复杂性,也许还有C的一些阴暗面。这证实C不像许多有经验的C程序员(特别是注重底层的)认为的,是一种“高级汇编”,并且C++和Objective-C已经直接继承它的很多问题。


    未定义行为

    日语翻译:blog-ja.intransient.info/2011/05/c-13.html

      LLVM IR和C语言都有“未定义行为”的概念。未定义行为是一个广泛的话题,包含很多细节。最好的介绍,我发现的最好的介绍是John Regehr的博文(blog.regehr.org/archives/213 )。这篇优秀的文章的大意是许多看似合理的东西在C中实际上存在未定义行为,这是一种常见的程序的错误来源。此外,任何未定义行为准许实现(编译器和运行时)产生这样的代码:格式化您的硬盘驱动器、做完全意想不到的事情,或者更糟(www.catb.org/jargon/html/N/nasal-demons.html )。我再次强烈建议阅读John的文章。
      未定义行为在基于C的语言中存在,因为C的设计者希望它成为一个高效的低层次的编程语言。相比之下,像Java(和许多其他的“安全”的语言)已回避未定义行为,因为他们想要在实现之间的行为安全、可重现,并愿意为此牺牲性能。无论哪一个都不是“(应该)致力于的正确的目标”,如果你是一个C程序员,你真的应该明白未定义行为是什么。
      在详细讨论之前,这里有必要简要提及,编译器需要做什么以使广泛的C应用程序取得良好的性能,因为没有灵丹妙药。在一个很高的层次上,编译器通过如下手段产生一个高性能的应用程序:a)良好的必需算法的实现,像寄存器分配,调度算法,等等。b)了解很多“招数”(如窥孔优化、循环变换等),并在有利时运用它们。c)善于消除不必要的抽象(如C中的宏导致的冗余、内联函数、消除C++临时对象等。以及d)不把任何东西搞砸。下面提到的优化可能听起来微不足道的,但实际上在一个关键循环仅节省一个周期就可以使一些编解码器的运行速率提高10%或节约10%的电源。

    C中未定义行为的优势与例子

      在进入未定义行为和作为一个C编译器使用时的LLVM的策略和行为的阴暗面之前,我想考虑一些未定义行为的具体用例,并讨论它们各自如何使性能比像Java那样的安全语言更好,是有帮助的。读者可以把它看作通过未定义行为“启用优化”或导致需要使每个用例被定义的“避免开销”。虽然在某些时候编译器优化器可以消除其中的某些开销,一般地(在所有情况下)这样做将需要解决停机问题和许多其它的“有趣的挑战”。
      值得指出的还有,Clang和GCC都确定了C标准保留未定义的一些行为。我将描述的东西是按照标准和被这两个编译器的默认模式下都作为未定义的行为。
      使用未初始化的变量:这被公认为在C程序中产生问题的一个源头,有很多工具来捕获这些:从编译器的警告到静态和动态分析。不要求所有的变量在进入作用域时被零初始化(像Java那样)可以提高性能。对于多数标量变量,零初始化会引起很小的开销,但对栈数组和malloc得到的内存会引发对存储的memset操作,这可能相当昂贵,特别是这些存储通常被完全覆写。
      有符号整数溢出:若(例如)“int”类型的算术操作溢出,结果是未定义的。一个例子是“INT_MAX+1”不保证是INT_MIN。这种行为可以对某些代码启用特定类别的重要优化。例如,了解INT_MAX+1未定义允许优化“X+1X”为“true”。允许乘法“不能”溢出(因为这样做将是未定义的)允许优化“X*2/2”为“X”。这些看起来像是微不足道的,因为这类操作通常被内联和宏展开。这允许的一个更重要的优化是对于“=”循环,如下:

    for (i = 0; i = N; ++i) { ... }
    在这个循环中,编译器可以假定该循环将迭代N+1次,如果“i”的溢出是未定义的,从而允许介入范围广泛的循环优化。另一方面,如果该变量被定义为溢出时回绕,那么编译器必须假设这个循环可能是无限的(当N是INT_MAX发生)——然后禁用这些重要的循环优化。这尤其影响64位平台,因为如此多的代码使用“int”作为循环变量。
      值得注意的是,无符号溢出是保证定义为2的补(回绕),所以可以总是使用它们。使有符号整数溢出有定义的成本会使这些优化根本丢失(例如,一种常见症状是在64位的目标上的一堆按符号扩展)Clang和GCC都接受“-fwrapv”标志,这迫使编译器对有符号整数溢出视为有定义的(而不是INT_MIN除以-1)。

    过大的移位量:
    对一个uint32_t移32位或更多是未定义的。我的猜测是它起源于底层移位操作在不同的CPU上的不同:例如,X86截断32位移位量至5位(于是移32位相当于移0位),但PowerPC截断32位移位量至6位(于是移32位产生0)。由于这些硬件上的差异,其行为在C中是完全未定义的(因此,移32位在PowerPC上可以格式化你的硬盘驱动器,它*不*保证生产零)。消除这种未定义行为的成本是使编译器对变量移位产生一个额外的操作(类似一个“与”运算)为变量的变化,这将在通常的CPU上使代价翻倍。

    解引用野指针和数组越界访问:
    随机指针解引用(像NULL,指向已被free的内存,等等)和数组访问越界的特殊情况,是在C应用程序的常见错误(希望不用解释)。为了消除这种未定义行为的来源,每个数组访问需要检查范围,并且ABI(译注:应用程序二进制接口)将不得不改变,以确保任何指针算术操作伴随范围信息。这对许多数值和其它应用程序成本极高,同时破坏了与每一个现有的C库的二进制兼容性。

    解引用一个NULL指针:
    和流行的看法相反,解引用一个空指针在C中是未定义的。它并没有被定义为引起陷入,并且如果你mmap一个0页面,访问这个页面是未定义的。这在禁止解引用野指针的规则之外,使NULL指针作为哨位不可行。NULL解引用成为未定义使广泛的优化成为可能:作为对比,Java使编译器在任何对象指针解引用之间移动具有副作用的操作无效,因为它无法被优化器证明是非空的。这显著地不利于调度和其它优化。在基于C的语言中,NULL成为未定义的使大量简单标量优化能被暴露为宏展开和内联。

    如果你使用的是基于LLVM的编译器,你可以解引用“volatile”空指针来获得崩溃,如果这就是你需要的,因为优化器一般不关心volatile的存取。目前没有标识使随机NULL指针读取能被作为有效的访问或使随机读取知道它们的指针是“允许为空”的。

    违反类型规则:转换int*为float*并解引用(像存在一个“float”一样访问一个“int”)是未定义的。C要求这些种类的类型转换通过memcpy发生:使用指针转换不正确并导致结果未定义。这个规则是相当微妙的,我在这里不想详细讨论细节(对于char*有一个例外,向量有特殊的属性,联合造成改变,等等)。这种行为使称为“基于类型的别名分析”(TBAA)成为可能,被广泛用于编译器中的内存访问优化分析,并能显着提高生成的代码的性能。例如,这个规则允许clang优化此函数:
    float *P;
    void zero_array() {
    int i;
    for (i = 0; i  10000; ++i)
    P = 0.0f;
    }
    为“memset(P, 0, 40000)”。这种优化也允许许多读取被提取出循环,消除公共子表达式等。这一类未定义的行为,可以通过传递-fno-strict-aliasing标志禁用,同时禁止此分析。传递此标志时,Clang被要求编译这个循环为10000个4字节存储(慢了若干倍),因为它得假定对任何改变P的值的存储都是有可能的,像如下的代码:

    int main() {
    P = (float*)&P; // 转换导致TBAA在zero_array中违例。
    zero_array();
    }

    此类类型滥用是相当少见的,这就是为什么标准委员会决定,为了显著的性能提升,“合理”的类型转换可以导致预期外的结果。值得指出的是Java获取基于类型的优化同时没有这些缺陷,因为它在语言中根本没有不安全的指针转换。

    无论如何,我希望这使你了解在C中未定义行为能使某些类别的优化成为可能。当然有许多其它种类,类似序列点违例如“foo(i, ++i)”,多线程程序的竞争条件,“restrict”违例,除以零,等等。

    在我们的下一篇文章里,我们将讨论为什么C的未定义行为是一件非常可怕的事,如果性能不是你的唯一目标。在系列的最后一篇文章中,我们将谈论LLVM和Clang如何处理它。





    楼主 2016-06-23 12:18 回复

共有回帖数 0
  • 回 帖
  • 表情 图片 视频
  • 发表

登录直线网账号

Copyright © 2010~2015 直线网 版权所有,All Rights Reserved.沪ICP备10039589号 意见反馈 | 关于直线 | 版权声明 | 会员须知