Sun Studio 12 Update 1:C++ 用户指南

第 4 章 语言扩展

本章介绍了与此编译器相关的语言扩展。在命令行上指定某些编译器选项之后,编译器才能识别本章中描述的某些功能。相关编译器选项在相应章节中列出。

使用 -features=extensions 选项可以编译其他 C++ 编译器通常接受的非标准代码。必须编译无效代码且不允许修改代码而使之有效时,您可以使用该选项。

本章介绍了使用 -features=extensions 选项时编译器支持的语言扩展。


注 –

可以很容易的将每个支持无效代码的实例转变为所有编译器接受的有效代码。如果允许使代码有效,那么您应该使代码有效而不是使用该选项。使用 -features=extensions 选项可以使某些编译器拒绝的无效代码永远存在。


4.1 链接程序作用域

可使用下列声明说明符来协助约束外部符号的声明和定义。文件链接到共享库或可执行文件之前,静态归档或目标文件指定的作用域限制不会生效。尽管如此,编译器仍然可以执行显示链接程序作用域说明符的某些优化。

通过使用这些说明符,您不必再使用链接程序作用域的 mapfile。也可以通过在命令行上指定-xldscope 来控制变量作用域的缺省设置。

有关更多信息,请参见A.2.136 -xldscope={v}

表 4–1 链接程序作用域声明说明符

值 

含义 

__global

符号定义具有全局链接程序作用域,是限制最小的链接程序作用域。对符号的所有引用都绑定到定义符号的第一个动态装入模块中的定义。该链接程序作用域是外部符号的当前链接程序作用域。

__symbolic

符号定义具有符号链接程序作用域,其限制程度高于全局链接程序作用域。将对链接的动态装入模块内符号的所有引用绑定到模块内定义的符号。在模块外部,符号也显示为全局符号。该链接程序作用域对应于链接程序选项 -Bsymbolic。尽管不能将 -Bsymbolic 与 C++ 库一起使用,但可以使用 __symbolic 说明符,而不会引起问题。有关链接程序的更多信息,请参见 ld(1)。

__hidden

符号定义具有隐藏的链接程序作用域。隐藏链接程序作用域具有比符号和全局链接程序作用域更高的限制。将动态装入模块内的所有引用绑定到该模块内的定义。符号在模块外部是不可视的。

符号定义可以用更多限制的说明符来重新声明,但是不可以用较少限制的说明符重新声明。符号定义后,不可以用不同的说明符声明符号。

__global 是限制最少的作用域,__symbolic 是限制较多的作用域,而 __hidden 是限制最多的作用域。

因为虚函数的声明影响虚拟表的结构和解释,所以所有虚函数对包括类定义的所有编译单元必须是可视的。

可以将链接程序作用域说明符应用于结构、类和联合声明和定义中,因为 C++ 类可能要求生成隐式信息,如虚拟表和运行时类型信息。在这种情况下,说明符后跟结构、类或联合关键字。这种应用程序为其所有隐式成员隐含了相同的链接程序作用域。

4.1.1 与 Microsoft Windows 兼容

为了在动态库方面与 Microsoft Visual C++ (MSVC++) 中的相似作用域功能兼容,也支持以下语法:

__declspec(dllexport) 等效于 __symbolic

__declspec(dllimport) 等效于 __global

在 Sun C++ 中使用此语法时,应将选项 -xldscope=hidden 添加到 CC 命令行。结果与使用 MSVC++ 得到的结果相当。在 MSVC++ 中,__declspec(dllimport) 应当仅用于外部符号的声明,而不用于定义。示例:


__declspec(dllimport) int foo(); // OK 
__declspec(dllimport) int bar() { ... } // not OK  

MSVC++ 中,对于将 dllimport 用于定义没有严格规定,而使用 Sun C++ 时结果则不同。尤其是,使用 Sun C++ 时将 dllimport 用于定义得到的是具有全局链接的符号而不是符号链接。Microsoft Windows 上的动态库不支持符号的全局链接。如果遇到此问题,可以更改源代码,对定义使用 dllexport 而不是 dllimport。这样,使用 MSVC++ 和使用 Sun C++ 得到的结果相同。

4.2 线程局部存储

通过声明线程局部变量,可以利用线程局部存储。线程局部变量声明普通变量声明与声明说明符 __thread 组成。有关更多信息,请参见A.2.182 -xthreadvar[= o]

必须将 __thread 说明符包括在第一个线程变量声明中。使用 __thread 说明符声明的变量的绑定方式与没有 __thread 说明符时相同。

只能使用 __thread 说明符声明静态持续时间的变量。具有静态持续时间的变量包括了文件全局、文件静态、函数局部静态和类静态成员。不能使用 __thread 说明符声明动态或自动持续时间的变量。线程变量可以具有静态初始化函数,但是不可以具有动态初始化函数或析构函数。例如,允许 __thread int x = 4;,但不允许 __thread int x = f();。线程变量不能包含具有重要构造函数和析构函数的类型。具体来说,就是线程变量的类型不能为 std::string

运行时对线程变量的地址运算符 (&) 求值并返回当前线程变量的地址。因此,线程变量的地址不是常量。

线程变量的地址在相应线程的生命周期中是稳定的。进程中任何线程都可以在线程变量的生命周期任意使用该变量的地址。不能在线程终止后使用线程变量的地址。线程变量的所有地址在线程终止后都是无效的。

4.3 用限制较少的虚函数覆盖

C++ 标准规定,覆盖虚拟函数在异常中允许的限制不得低于它覆盖的任何函数的限制。该虚函数可能与覆盖的任何函数具有相同或更多的限制。注意,不存在异常规范也允许任何异常。

例如,假定通过指向基类的指针调用函数。如果函数具有异常规范,则可以计算出没有其他正抛出的异常。如果覆盖函数具有限制较少的规范,则不可预料的异常可能会被抛出,这会导致奇怪的程序行为并且终止程序。这就是规则的原因。

使用 -features=extensions 时,编译器允许覆盖异常规范限制较小的函数。

4.4 对 enum 类型和变量进行前向声明

使用 -features=extensions 时,编译器允许对 enum 类型和变量进行前向声明。此外,编译器允许声明不完整 enum 类型的变量。编译器总是假定不完整 enum 类型的大小和范围与当前平台上的 int 类型相同。

以下是两行无效代码示例,如果使用 -features=extensions 选项,可对其进行编译。


enum E; // invalid: forward declaration of enum not allowed
E e;    // invalid: type E is incomplete

因为 enum 定义不能互相引用,并且 enum 定义不能交叉引用另一种类型,所以从来不必对枚举类型进行前向声明。要使代码有效,可以总是先提供 enum 的完整定义,然后再使用它。


注 –

在 64 位体系结构上,enum 要求的大小可能比 int 类型大。如果是这种情况,并且如果向前声明和定义在同一编译中是可视的,那么编译器将发出错误。如果实际大小不是假定的大小并且编译器没有发现这个差异,那么代码将编译并链接,但有可能不能正常运行。可能出现奇怪的程序行为,尤其是 8 字节值存储在 4 字节变量中时。


4.5 使用不完整 enum 类型

使用 -features=extensions 时,不完整的 enum 类型以前向声明处理。例如,以下是无效代码,如果使用 -features=extensions 选项,可对其进行编译。


typedef enum E F; // invalid, E is incomplete

如前所述,可以总是先包括 enum 类型的定义,然后再使用。

4.6 将 enum 名称作为作用域限定符

因为 enum 声明并不引入作用域,所以 enum 名称不能作为作用域限定符来使用。例如,以下代码是无效的。


enum E {e1, e2, e3};
int i = E::e1; // invalid: E is not a scope name

要编译该无效代码,请使用 -features=extensions 选项。-features=extensions 选项指示编译器在作用域限定符是 enum 类型的名称的情况下忽略该作用域限定符。

要使代码有效,请删除无效的限定符 E::


注 –

使用该选项提高了排字错误的可能性,产生了编译没有错误消息的错误程序。


4.7 使用匿名 struct 声明

匿名结构声明是既不声明结构标记也不声明对象或 typedef 名称的声明。C++ 中不允许匿名结构。

-features=extensions 选项允许使用匿名 struct 声明,但仅作为联合的成员。

以下代码是无效匿名 struct 声明示例,如果使用 -features=extensions 选项,可对其进行编译。


union U {
  struct {
    int a;
    double b;
  };  // invalid: anonymous struct
  struct {
    char* c;
    unsigned d;
  };  // invalid: anonymous struct
};

struct 成员的名称是可视的,没有 struct 成员名称的限定。如果该代码示例中提供了 U 的定义,则可以编写:


U u;
u.a = 1;

匿名结构与匿名联合服从相同的限制。

请注意,可以通过为每个 struct 提供一个名称以使代码有效,如:


union U {
  struct {
    int a;
    double b;
  } A;
  struct {
    char* c;
    unsigned d;
  } B;
};
U u;
U.A.a = 1;

4.8 传递匿名类实例的地址

不允许获取临时变量的地址。例如,因为以下代码获取了构造函数调用创建的变量地址,所以这些代码是无效的。但是,如果使用 -features=extensions 选项,编译器将接受该无效代码。


class C {
  public:
    C(int);
    ...
};
void f1(C*);
int main()
{
  f1(&C(2)); // invalid
}

注意,可以通过使用显式变量来使该代码有效。


C c(2);
f1(&c);

函数返回时,临时对象被销毁。程序员应确保临时变量的地址没有留下。此外,销毁临时变量(例如 f1)时,临时变量中存储的数据会丢失。

4.9 将静态名称空间作用域函数声明为类友元

下面的代码是无效的:


class A {
  friend static void foo(<args>);
  ...
};

因为类名具有外部链接并且所有定义必须是相等的,所以友元函数也必须具有外部链接。但是,如果使用 -features=extensions 选项,编译器将接受该代码。

程序员处理该无效代码的方法大概是在类 A 的实现文件中提供非成员 "helper" 函数。可以通过使 foo 成为静态成员函数得到相同效果。如果不要客户端调用函数,则可以使该函数私有化。


注 –

如果使用该扩展,则任何客户端都可以“劫取”您的类。任何客户端都可以包括类的头文件,然后定义其自身的静态函数 foo,该函数将自动成为类的友元。结果就好像是您使类的所有成员成为了公共的。


4.10 将预定义 __func__ 符号用于函数名

使用 -features=extensions 时,编译器将每个函数中的标识符 __func__ 隐式声明为静态 const char 数组。如果程序使用标识符,编译器还会提供以下定义,其中,function-name 是函数原始名称。类成员关系、名称空间和重载不反映在名称中。


static const char __func__[] = "function-name";

例如,请考虑以下代码段。


#include <stdio.h>
void myfunc(void)
{
  printf("%s\n", __func__);
}

每次调用函数时,函数将把以下内容打印到标准输出流。


myfunc

4.11 __packed__ 属性

此属性附加于 structunion 类型定义中,它指定结构或联合的每个成员(除了零宽度位字段)的放置,以最大限度地减小所需内存。附加于 enum 定义时,此属性指示应使用最小的整数类型。

structunion 类型指定此属性等效于对每个结构或联合成员指定 packed 属性。

在以下示例中,struct my_packed_struct 的成员紧紧打包在一起,但其成员的内部布局并不打包。为此,还需要打包 struct my_unpacked_struct


struct my_unpacked_struct
{
   char c;
   int i;
;
              
struct __attribute__ ((__packed__)) my_packed_struct
{
   char c;
   int  i;
   struct my_unpacked_struct s;
};

只能对 enumstructunion 的定义指定此属性,不能对未定义枚举类型、结构或联合的 typedef 指定此属性。