Sun Studio 12:C 用户指南

附录 D 支持的 C99 功能

此附录列出了 C 编程语言标准 ISO/IEC 9899:1999 支持的功能。

-xc99 标志可控制编译器对实现功能的识别。有关 -xc99 语法的更多信息,请参见B.2.73 -xc99[= o]


注 –

虽然编译器在缺省情况下支持下面列出的 C99 功能,但是 /usr/include 中由 Solaris 软件提供的标准头文件仍不符合 1999 ISO/IEC C 标准。如果遇到错误消息,请尝试使用 -xc99=none 获取这些头文件的 1990 ISO/IEC C 标准行为。


D.1 讨论和示例

本附录提供了有关下列某些支持的功能的讨论和示例。

D.1.1 浮点计算器的精度

5.2.4.2.2 浮点类型 <float.h> 的特性

使用浮点操作数进行运算的值以及同时符合常见算术转换和浮点常量的值经过计算所得格式的范围和精度可能大于该类型所要求的范围和精度。计算格式的使用由 FLT_EVAL_METHOD 的特定值确定:

表 D–1 FLT_EVAL_METHOD

值 

含义 

-1 

不能确定的。 

编译器仅根据该类型的范围和精度计算所有运算和常量的结果。 

编译器根据双精度的范围和精度计算浮点和双精度类型的运算和常量的结果。根据长双精度的范围和精度计算长双精度运算和常量的结果。 

编译器仅根据长双精度的范围和精度计算所有运算和常量的结果。 

当您将 float.h 包括在 SPARC 体系结构中时,缺省情况下 FLT_EVAL_METHOD 将扩展到 0,并根据其类型计算所有浮点表达式的结果。

当您将 float.h 包括在 x86 体系结构中时,缺省情况下 FLT_EVAL_METHOD 将扩展到 -1(当 -xarch=sse2-xarch=amd64 时除外),并根据其类型计算所有浮点常量表达式的结果,同时将所有其他浮点表达式按长双精度类型进行计算。

当您指定 -flteval=2 并包括 float.h 时,FLT_EVAL_METHOD 将扩展到 2,并将所有浮点表达式按长双精度类型进行计算。有关更多信息,请参见B.2.20 -flteval[={ any|2}]

当您在 x86 中指定 -xarch=sse2-xarch=amd64 并包括 float.h 时,FLT_EVAL_METHOD 将扩展到 0,并根据其类型计算所有浮点表达式的结果。

即使浮点表达式按双精度类型来计算结果,-Xt 选项也不影响 FLT_EVAL_METHOD 的扩展。有关更多信息,请参见B.2.63 -X[c|a| t|s]

使用 -fsingle 选项将导致浮点表达式以单精度来计算结果。有关更多信息,请参见B.2.27 -fsingle

当您在 x86 体系结构中指定 -fprecision 以及 -xarch=sse2-xarch=amd64 并包括 float.h 时,FLT_EVAL_METHOD 将扩展到 -1

D.1.2 C99 关键字

6.4.1 关键字

C99 标准引入了以下新的关键字。在 -xc99=none 的情况下编译时,如果使用这些关键字作为标识符,编译器将会发出警告。在不设置 -xc99=none 的情况下,编译器会根据上下文针对使用这些关键字作为标识符发出警告或错误消息。

D.1.2.1 使用 restrict 关键字

通过 restrict 限定指针访问的对象要求对该对象的所有访问都直接或间接使用特定 restrict 限定指针的值。通过任何其他方式访问该对象可能导致不确定的行为。restrict 限定符的既定用途是允许编译器做出提升优化的假定。

有关如何有效使用 restrict 限定符的示例和说明,请参见3.8.2 限定指针

D.1.3 __func__ 支持

6.4.2.2 预定义的标识符。

编译器支持预定义标识符 __func____func__ 定义为字符数组,它包含 __func__ 所在的当前函数的名称。

D.1.4 通用字符名 (UCN)

6.4.3 通用字符名

UCN 允许在 C 源码中使用任何字符,而不仅仅是英文字符。UCN 的格式是:

UCN 不能指定小于 00A0 的值(0024 ($)、0040 (@) 或 0060 (?) 除外),也不能指定 D800 到 DFFF 范围内(含 D800 和 DFFF)的值。

UCN 可用于标识符、字符常量和字符串文字中,以指定 C 基本字符集中不存在的字符。

UCN \unnnnnnnn 指定了其八位短标识符(根据 ISO/IEC 10646 的规定)为 nnnnnnnn 的字符。同样,通用字符名 ¯nnnn 指定了其四位短标识符为 nnnn(其八位短标识符为 0000nnnn)的字符。

D.1.5 使用 // 注释代码

6.4.9 注释

字符 // 引入包含直到(但不包括)新换行符的所有多字节字符的注释,除非 // 字符出现在字符常量、字符串文字或注释中。

D.1.6 禁止隐式 int 和隐式函数声明

6.5.2.2 函数调用

与 1990 C 标准不同,1999 C 标准不再允许隐式声明。C 编译器的以前版本仅在设置了 -v(详细)的情况下发出有关隐式定义的警告消息。只要标识符隐式定义为 int 或函数,系统便会对隐式定义发出这些消息及其他新警告。

该编译器的几乎所有用户均可能注意到这种变化,原因是它会导致大量警告消息。常见原因包括未能包含用于声明所使用函数的相应系统头文件,如需要包含 <stdio.h>printf。可以使用 -xc99=none 恢复无提示地接受隐式声明的 1990 C 标准行为。

C 编译器对隐性函数声明生成警告:


example% cat test.c
void main()
{
  printf("Hello, world!\n");
}
example% cc test.c
"test.c", line 3: warning: implicit function declaration: printf
example%

D.1.7 使用隐式 int 的声明

6.7.2 类型说明符:

每个声明中的声明说明符中应至少指定一个类型说明符。另请参见D.1.6 禁止隐式 int 和隐式函数声明

现在,C 编译器会对任何隐式 int 声明都发出警告,如以下示例所示:


example% more test.c
volatile i;
const foo()
{
  return i;
}
example% cc test.c
  "test.c", line 1: warning: no explicit type given
  "test.c", line 3: warning: no explicit type given
example%

D.1.8 灵活的数组成员

6.7.2.1 结构和联合说明符

也被称为 "struct hack"。允许结构的最后一个成员是长度为零的数组,如 int foo[];。这种结构一般用作访问 malloc 内存的头文件。

例如,在结构 struct s { int n; double d[]; } S; 中,数组 d 是不完整数组类型。对于 S 的该成员,C 编译器不对任何内存偏移进行计数。换句话说,sizeof(struct s)S.n 的偏移相同。

可以像使用任何普通数组成员一样使用 dS.d[10] = 0;

如果没有 C 编译器对不完整数组类型的支持,您将按以下称为 DynamicDouble 的示例所示定义和声明结构:


typedef struct { int n; double d[1]; ) DynamicDouble;

请注意,数组 d 不是不完整数组类型,并且已使用一个成员进行声明。

下一步,声明指针 dd 并分配内存,如下所示:


DynamicDouble *dd = malloc(sizeof(DynamicDouble)+(actual_size-1)*sizeof(double));

然后,将偏移的大小存储在 S.n 中,如下所示:


dd->n = actual_size;

由于编译器支持不完全数组类型,因此您无需使用一个成员声明数组即可达到同样的效果。


typedef struct { int n; double d[]; } DynamicDouble;

如以前一样声明指针 dd 并分配内存,只是不必再从 actual_size 中减去 1:


DynamicDouble *dd = malloc (sizeof(DynamicDouble) + (actual_size)*sizeof(double));

如以前一样将偏移存储在 S.n 中,如下所示:


dd->n = actual_size;

D.1.9 幂等限定符

6.7.3 类型限定符:

如果同一限定符在同一说明符限定符列表中出现多次(无论直接出现还是通过一个或多个 typedef),行为与该类型限定符仅出现一次时相同。

在 C90 中,以下代码会导致错误:


%example cat test.c

const const int a;

int main(void) {
  return(0);
}

%example cc -xc99=none test.c
"test.c", line 1: invalid type combination

但是,对于 C99,C 编译器接受多个限定符。


%example cc -xc99 test.c
%example

D.1.10 inline 函数

6.7.4 函数说明符

添加了 C99 函数说明符 inlineinline 对于具有内部和外部链接的函数可发挥全部功能。现在即可按照 1999 C ISO 标准的规定来使用内联函数定义和外部内联函数。

内联函数定义是使用关键字 inline 定义的函数,不包含关键字 static 或 extern,并且显示在源文件(或包含文件)中的所有原型也包含关键字 inline,而不包含关键字 static 或 extern。

内联函数定义没有为该函数提供外部定义。在包含内联函数的源文件中显示的任何函数调用将符合以下条件:在调用点对函数定义进行内联或引用外部定义的函数。

只有在进行优化时,并且只有在编译器优化器认识这样做有益时,编译器才会对内联定义的调用进行内联。否则将调用外部函数。因此,包含内联定义的任何程序应与包含 extern 函数定义的目标文件链接在一起。

同时将关键字 extern 及内联与函数定义一起使用(或在继续函数定义的文件中的任何原型上使用)将导致在该目标文件中定义外部函数。要兼容与包含多个 extern 内联函数定义的对象相链接的 C++,链接程序只能选择其中一种函数来满足任何外部引用。

要获得符合标准的行为,必须使用当前编译器对旧代码重新进行编译。但是,如果您在旧的 C 和 C++ 二进制文件(C/C++ 5.6 之前)中使用了 extern 内联函数定义的实例,并且希望在不更改旧二进制文件行为的情况下将这些二进制文件与新的 C 和 C++ 二进制文件相链接,请指定 -features=no%extinl

D.1.11 Static 及数组声明符中允许的其他类型限定符

6.7.5.2 数组声明符:

现在,关键字 static 可以出现在函数声明符中参数的数组声明符中,表示编译器至少可以假定许多元素将传递到所声明的函数中。使优化器能够作出以其他方式无法确定的假定。

C 编译器将数组参数调整为指针,因此 void foo(int a[])void foo(int *a) 相同。

如果您指定 void foo(int * restrict a); 等类型限定符,则 C 编译器使用实质上与声明限定指针相同的数组语法 void foo(int a[restrict]); 表示它。

C 编译器还使用 static 限定符保留关于数组大小的信息。例如,如果您指定 void foo(int a[10]),则编译器仍将它表达为 void foo(int *a)。按以下所示使用 static 限定符:void foo(int a[static 10]),让编译器知道指针 a 不是 NULL,并且使用它可访问至少包含十个元素的整数数组。

D.1.12 可变长度数组 (VLA):

6.7.5.2 数组声明符

在栈中分配 VLA 时,仿佛调用了 alloca 函数。无论其作用域如何,其生存期与通过调用 alloca 在栈中分配数据时相同;直到函数返回时为止。如果在其中分配 VLA 的函数返回时释放栈,则释放分配的空间。

尚未对可变长度数组强制施加所有约束。约束违规导致不确定的结果。


#include <stdio.h>
void foo(int);

int main(void) {
  foo(4);
  return(0);
}

void foo (int n) {
  int i;
  int a[n];
  for (i = 0; i < n; i++)
    a[i] = n-i;
  for (i = n-1; i >= 0; i--)
    printf("a[%d] = %d\n", i, a[i]);
}

example% cc test.c
example% a.out
a[3] = 1
a[2] = 2
a[1] = 3
a[0] = 4

D.1.13 指定的初始化函数

6.7.8 初始化

指定的初始化函数为初始化稀疏数组提供了一种机制,这在数字编程的实践中很常见。

指定的初始化函数可以对稀疏结构进行初始化,这在系统编程中很常见,并且可以通过任何成员对联合进行初始化,而不管其是否为第一个成员。

请看以下这些示例。此处的第一个示例显示了如何使用指定的初始化函数来对数组进行初始化:


 enum { first, second, third };
          const char *nm[] = {
          [third] = "third member",
          [first] = "first member",
          [second] = "second member",
          };

下面的示例证明了如何使用指定的初始化函数来对结构对象的字段进行初始化:


division_t result = { .quot = 2, .rem = -1 };

下面的示例显示了如何使用指定的初始化函数对复杂的结构进行初始化(否则这些结构可能会被误解):


 struct { int z[3], count; } w[] = { [0].z = {1}, [1].z[0] = 2 };

通过使用单个指示符可以从两端创建数组:


int z[MAX] = {1, 3, 5, 7, 9, [MAX-5] = 8, 6, 4, 2, 0};

如果 MAX 大于 10,则数组将在中间位置包含取值为零的元素;如果 MAX 小于 10,则前五个初始化函数提供的某些值将被后五个初始化函数的值覆盖。

联合的任何成员均可进行初始化:


union { int i; float f;} data = { .f = 3.2 };

D.1.14 混合声明和代码

6.8.2 复合语句

现在,C 编译器接受关于可执行代码的混合类型声明,如以下示例所示:


#include <stdio.h>

int main(void){
  int num1 = 3;
  printf("%d\n", num1);

  int num2 = 10;
  printf("%d\n", num2);
  return(0);
}

D.1.15 for 循环语句中的声明

6.8.5 迭代语句

C 编译器接受作为 for 循环语句中第一个表达式的类型声明:


for (int i=0; i<10; i++){ //loop body };

for 循环的初始化语句中声明的任何变量的作用域是整个循环(包括控制和迭代表达式)。

D.1.16 具有可变数目的参数的宏

6.10.3 宏替换

C 编译器接受以下形式的 #define 预处理程序指令:


#define identifier (...) replacement_list
#define identifier (identifier_list, ...) replacement_list

如果宏定义中的 identifier_list 以省略号结尾,则意味着调用中的参数比宏定义中的参数(不包括省略号)多。否则,宏定义中参数的数目(包括由预处理标记组成的参数)与调用中参数的数目匹配。对于在其参数中使用省略号表示法的 #define 预处理指令,在其替换列表中使用标识符 __VA_ARGS__。以下示例说明可变参数列表宏工具。


#define debug(...) fprintf(stderr, __VA_ARGS__)
#define showlist(...) puts(#__VA_ARGS__)
#define report(test, ...) ((test)?puts(#test):\
                        printf(__VA_ARGS__))
debug(“Flag”);
debug(“X = %d\n”,x);
showlist(The first, second, and third items.);
report(x>y, “x is %d but y is %d”, x, y);

其结果如下:


fprintf(stderr, “Flag”);
fprintf(stderr, “X = %d\n”, x);
puts(“The first, second, and third items.”);
((x>y)?puts(“x>y”):printf(“x is %d but y is %d”, x, y));

D.1.17 _Pragma

6.10.9 Pragma 操作符

_Pragma ( string-literal ) 形式的一元操作符表达式处理如下:

预处理标记的结果序列作为 pragma 指令中的预处理程序标记进行处理。

删除一元操作符表达式中的最初四个预处理标记。

#pragma 比较,_Pragma 的优势在于:_Pragma 可以用于宏定义。

_Pragma("string") 与 #pragma 字符串行为完全相同。考虑以下示例。首先列出示例的源代码,然后在预处理程序使其通过预处理之后,再列出示例的源代码。


example% cat test.c

#include <omp.h>
#include <stdio.h>

#define Pragma(x) _Pragma(#x)
#define OMP(directive) Pragma(omp directive)

void main()
{
  omp_set_dynamic(0);
  omp_set_num_threads(2);
  OMP(parallel)
  {
  printf("Hello!\n");
  }
}

example% cc test.c -P -xopenmp -x03
example% cat test.i

下面是预处理程序完成后的源代码。


void main()
{
  omp_set_dynamic(0);
  omp_set_num_threads(2);
  # pragma omp parallel
  {
    printf("Hellow!\n");
  }
}

example% cc test.c -xopenmp -->
example% ./a.out
Hello!
Hello!
example%