IBM XL C/C++ 编译器中添加的 ISO C11 支持

第 1 阶段中引入的新功能

新的 ISO C 编程语言标准提供了多项功能来帮助提高编程效率、调试和提高性能。IBM XL 编译器正在分阶段支持新的 C 标准,以便您可以利用有用的功能,比如支持复数类型对象初始化的功能、静态断言,以及针对没有返回值的函数的函数特性。这些现在是 XL 编译器中的一部分,以简化调试和改善性能。

Rajan Bhakta, 技术架构师,z/OS XL C/C++ ISO C 标准加拿大代表, IBM

Rajan Bhakta 的照片Rajan Bhakta 在 IBM 工作。他拥有 5 年的 IBM XL C 开发经验,目前是 ISO C 标准的加拿大代表和 INCITS 中代表 IBM 的 C 代表,还是 z/OS XL C/C++ 的技术架构师。



2012 年 10 月 10 日

免费下载:IBM® XL® C/C++ for AIX 编译器  |  IBM® XL® C/C++ for Linux 编译器
下载更多的 IBM 软件试用版,并加入 IBM 软件下载与技术交流群组,参与在线交流。

一些改进的概述

IBM® XL 编译器拥有严格遵守标准的传承。延续 IBM 对编程语言标准的不变承诺,IBM® AIX® XL C/C++ Version 12.1、Linux XL C/C++ Version 12.1 和 BlueGene XL C/C++ Version 12.1 编译器从新的 ISO C 标准(ISO/IEC 9899:2011,代号为 C11)引入了新功能。这有助于使 XL 系列编译器可移植、实用,使 C 和 C++ 编程更加轻松和高效,继续增强了 XL 编译器家族赖以成名的性能。

随着编程语言不断演化,添加了更多的功能并采用了新的发展道路,XL 编译器也在不断更新,以提供最佳的性能和适用性。在此历程中,只要 C11 功能适用,人们就会将它们添加到 C 和 C++ 编译器中,以获得可在语言之间移植的代码,并为两个群体的编程人员带来收益。

因为引入了新的编程人员生产力功能(比如复值初始化),所以使用无穷大和 NaN 初始化特殊的复数变得更加简单。此外,还添加了静态断言来帮助调试,这使创建更高质量的程序比以往更加简单。

为了帮助提高性能,添加了 _Noreturn 函数特性关键字。利用此特性,可以让 XL 编译器利用函数从不返回的信息,这提供了优化机会并且可以实现更快的程序。

所有 C11 功能都已在 EXTC1X 语言级别下启用,以便早期采用者可以抢先获得 C11 所提供的性能和编程效率。


复值初始化

以前的 C99 标准引入了复数浮点类型来表示复数。这提供了一种创建程序的方法,这些程序可以操作和计算复数,根据需要使用、输入和输出复值。XL 编译器向标准添加了扩展,使操作复数的组成部分变得更加轻松(例如 __real____imag__ 运算符)。

即使有了 C99 提供的所有支持和 IBM 扩展,仍然有一些事情无法轻松完成,或者无法使用复数来解决。缺少的一个关键的功能部分是,无法使用无穷大或 NaN 作为虚数部分来初始化复数。IBM 提供的 __real__ 和 __imag__ 运算符有助于在非静态和非外部上下文中解决了这一问题。但是,文件范围或其他静态初始化仍然无法轻松地为某些实现创建这些值。具体而言,当复数支持没有纯粹的虚数支持(这在 C 标准中是可选的)时,就无法创建这些值。

如果使用较老的 C99 标准复数支持的强制性部分,您就会得到复数 5.5 + Infinityi,代码类似于清单 1。

清单 1. 设置复值的预期方法
double _Complex value = 5.5 + INFINITY * __I;

但是,上面的代码生成的实际值为 NaN + Infinityi。原因在于,__I 本身不是虚数部分(纯虚数),而是一个具有虚数部分的值的复数类型。这一细微区别使得使用无穷大和 NaN 初始化复值变得非常困难。如果 __I 是纯虚数,表达式的 INFINITY * __I 部分可能会执行普通的标量乘法。但考虑到 __I 实际上是一个复数类型对象,所以实际发生的是在所有其他项(包括 INFINITY)上执行了类型提升(从 doubledouble _Complex),执行了复数乘法:

5.5 + INFINITY * __I
= (5.5 + 0i) + (INFINITY + 0i) * (0 + 1i) // Type promotion
= (5.5 + 0i) + (INFINITY * 0) + (0i * 0) + (INFINITY * 1i) + (0i * 1i) // FOIL
= (5.5 + 0i) + (NaN) + (0) + (INFINITYi) + (0) // Do the multiplications
= (5.5 + NaN + 0 + 0) + (0i+ INFINITYi) // Group like terms
= NaN + INFINITYi

因此,您最终得到了一个与预期结果有很大差别的结果。

所以您获得了您凭直觉预测的值,C11 添加了对 3 个新的类似复数初始化函数的宏的支持:

  • CMPLX
  • CMPLXF
  • CMPLXL

这些宏接受两个参数,一个用于实数部分,另一个用于虚数部分。如果参数本身适合静态初始化,这些宏可用于静态初始化。例如,使用 C11 标准,通过使用清单 2 中的 CMPLX 宏,可在任何范围中使用 5.0 + NaNi 初始化一个复数类型对象。

清单 2. 设置复值的 C11 方法
double _Complex value = CMPLX(5.5, 0.0/0.0);

这些宏相当于使用了以下函数:

double _Complex CMPLX( double x, double y ); 
float _Complex CMPLXF( float x, float y ); 
long double _Complex CMPLXL( long double x, long double y );

一般而言这非常有用,支持处理特殊情形的复数计算,比如前面介绍的使用无穷大或 NaN 作为虚数部分的初始值的情形。

例如,下面的函数返回一个复数列表中的最小值:

清单 3. getMinComplex.c
#include <complex.h> 
static const double _Complex maxPossibleValue = 
    CMPLX(1.0/0.0, 1.0/0.0); // Inf + Inf * i 

double _Complex getMinimum(double _Complex values[], 
        int size) { 
    double _Complex currentMinimum = maxPossibleValue; 
    for (int i = 0; i < size; i ++) { 
        if (__real__(values[i]) < __real__(currentMinimum)) { 
            currentMinimum = values[i];
        } else if (__real__(values[i]) ==
                __real__(currentMinimum)) { 
            if (__imag__(values[i]) < 
                    __imag__(currentMinimum)) { 
                currentMinimum = values[i];
            } 
        } 
    } 
    
    return currentMinimum; 
}

如果使用一个复值抽样列表调用此程序,则会提供该列表中以实数为主的最小值。

要使用此新功能编译程序,编译命令将是在 C11 语言级别对编译器的一次简单调用,如清单 4 所示。

清单 4. 在 C11 语言级别调用编译器的命令
> xlc –qlanglvl=extc1x –c getMinComplex.c

这里给出的函数,是使用复数虚拟化宏使以前非常困难、耗时或低效的任务变得更轻松的一个简单示例。


静态(编译时)断言

调试和验证代码始终是一项艰难的任务。许多语言中都引入了帮助编程人员完成此任务的各种方法。一些帮助包括用谓词验证某些前提条件。显然,可以在运行时使用这种方法确保只要前提条件违规就会发出一条错误消息,但在某些情形下,最好尽可能在编译时找到这些违规。

C 和 C++ 语言始终使用断言来帮助调试和验证代码。以前,这仅限于通过 assert() 宏进行运行时检查。但是在 C11 中,该语言支持通过 _Static_assert 声明执行编译时检查。此声明允许编译器在给定的常量表达式为 false(它将得到 0 值)时发出一条消息。

这可以帮助编程人员确保他们编写的代码会按预期使用,或者在以不合适的方式使用(可能导致难以找到运行时错误)时停止编译。例如,在共享模块时,这些模块常常必须处于相同的地址模式下才能工作。在这种情况下,编程人员可添加一个编译时断言,以避免在尝试将模块与不同地址模式链接时得到链接时误匹配。

清单 5. bufferScaffold.c
#include <stdlib.h> 
#include <assert.h> 

void fillBuffer(int* buffer);

/* assert.h defines _Static_assert as static_assert
    for a more user friendly name. */
static_assert(sizeof(int*) == 8, "Not in 64-bit mode!");

int main(void) { 
    int* buffer = (int*)malloc(65000); 
    
    fillBuffer(buffer); 
    
    // ... Do something with the buffer 
    
    return *buffer; 
}

像编程人员所期望的那样,在 64 位模式下编译模块会得到干净的编译,如清单 6 所示。

清单 6. 在 64 位模式下编译模块
> xlc –qlanglvl=extc1x –q64 –c bufferScaffold.c

但是,如果在 32 位模式下编译模块,编译时断言会触发并发出一条消息,让构建者知道生成过程存在问题(清单 7 中的示例来自 AIX XL C 编译器)。

清单 7. 来自 AIX XL C 编译器的示例
> xlc –qlanglvl=extc1x –q32 –c bufferScaffold.c 
> "bufferScaffold.c", line 7.1: 1506-865 (S) Not in 64-bit mode!

如上所示,编译时断言的添加可使编程更加安全且错误更少,进而从长远上节省编程人员时间。如果在编译时执行此检查,则没有运行时成本,所以程序的运行速度应像不存在编译时断言一样快。


不返回值的函数

XL 编译器处于优化的前沿,它们将编程人员提供的 C/C++ 代码尽可能转换为最快的机器代码。但是,即使是最优秀的编译器有时也需要帮助。这样的一种情况是,编译器总是无法对从不返回值的函数进行优化。在许多情形下,编译器可以确定是否需要帮助,但并不总是能够确定。但是,如果编程人员提供了相关保证,比如保证一个函数不会返回值,那么编译器可以做出需要保存、还原、可改写哪些值等假设。这可实现可加速程序的运行时性能的更积极的优化。

为了处理此情形,C11 标准引入了 _Noreturn 函数区分符,这使编程人员能够表明指定的函数不会返回。就像内联函数区分符一样,它为编译器提供了以可移植方式更好地优化程序的空间。例如,编译器可能不会保存将由函数处理的寄存器。

一种有用的情形适用于应用程序中的错误处理程序函数。这是一个常见的任务,其中会发出错误,清除程序,可在终止之前设置程序的返回代码。在非常简单的例子中(清单 8 中的程序中),errorHandler 函数从不返回,所以可以使用 _Noreturn 函数区分符来标记它,以便为编译器提供给函数以及围绕函数调用的代码生成更好的代码的机会。

清单 8. errorHandler.c
#include <stdlib.h> 
#include <stdnoreturn.h> 

/* stdnoreturn.h defines _Noreturn as noreturn 
        for a more user friendly name. */ 
void noreturn errorHandler(char* value) { 
    if (value[0] == 'S') { 
        exit(16); // Severe error 
    } else if (value[0] == 'E') { 
        exit(8); // Error 
    } else if (value[0] == 'W') { 
        exit(4); // Warning 
    } 
    
    // Unknown error 
    exit(0); 
} 

int main(int argc, char* argv[]) { 
    for (int i = 1; i < argc; i++) { 
        if (argv[i][0] == 'I') { 
            continue; // Informational 
        } else { 
            errorHandler(argv[i]); 
        } 
        return 66; 
    }
    return 55; 
}

在 C11 语言级别上编译此程序会让编译器知道 errorHandler 函数不会返回,并且在更高的优化级别上,它可以生成更快的程序。

清单 9. 编译命令示例
> xlc –qlanglvl=extc1x –O3 errorHandler.c –o errHan

使用类似清单 10 中提供的输入来运行该程序,将生成返回代码 8。

清单 10. 示例运行
> errHan Info Info Error Severe Warning Info 
> echo $? 
8

在这个简单示例中,如果对比生成的代码与没有 _Noreturn 函数区分符的程序版本就会发现,编译器生成了更好的代码来加速程序。


结束语

IBM 在所有 XL C/C++ 编译器家族中对标准的不变承诺使得编程变得更轻松、更高效且更可移植。它还提供了一种方式来显著增强 XL 系列编译器赖以成名的性能。

C 语言仍在不断演化,而语言的标准化为编程人员带来了更好的体验。XL C/C++ 编译器引入的支持新语言标准的新功能旨在帮助创建企业级应用程序,以实现快速程序创建和更经济的调试,并提供了性能增强,使程序既干净又井然有序。

通过在 XL C++ 编译器中也引入 C11 功能,跨语言的编程变得更加轻松和更可移植。XL 编译器始终允许混合使用 C 和 C++,常见的功能集允许编程人员选择使用最习惯的语言,并为两种语言提供一种通用、简单的语法。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Rational
ArticleID=843908
ArticleTitle=IBM XL C/C++ 编译器中添加的 ISO C11 支持
publish-date=10102012