JavaScript is required to for searching.
跳过导航链接
退出打印视图
Oracle Solaris Studio 12.3:C 用户指南     Oracle Solaris Studio 12.3 Information Library (简体中文)
search filter icon
search icon

文档信息

前言

1.  C 编译器介绍

2.  特定于 C 编译器实现的信息

3.  并行化 C 代码

4.  lint 源代码检验器

5.  基于类型的别名分析

6.  转换为 ISO C

7.  转换应用程序以适用于 64 位环境

8.  cscope:交互检查 C 程序

A.  按功能分组的编译器选项

B.  C 编译器选项参考

C.  实现定义的 ISO/IEC C99 行为

D.  C99 的功能

D.1 讨论和示例

D.1.1 浮点计算器的精度

D.1.2 C99 关键字

D.1.2.1 使用 restrict 关键字

D.1.3 __func__ 支持

D.1.4 通用字符名 (UCN)

D.1.5 使用 // 注释代码

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

D.1.7 使用隐式 int 的声明

D.1.8 灵活的数组成员

D.1.9 幂等限定符

D.1.10 inline 函数

D.1.10.1 Oracle Solaris Studio C 编译器针对内联函数的 gcc 兼容性

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

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

D.1.13 指定的初始化函数

D.1.14 混合声明和代码

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

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

D.1.17 _Pragma

E.  实现定义的 ISO/IEC C90 行为

F.  ISO C 数据表示法

G.  性能调节

H.  Oracle Solaris Studio C:K&R C 与 ISO C 之间的差异

索引

D.1 讨论和示例

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

D.1.1 浮点计算器的精度

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

使用浮点操作数进行运算的值以及同时符合常见算术转换和浮点常量的值,经过计算所得格式的范围和精度可能大于该类型所要求的范围和精度。计算格式的使用以实现定义的 FLT_EVAL_METHOD 值为特征,如下表中所示。

表 D-1 FLT_EVAL_METHOD

含义
-1
不能确定的。
0
编译器仅根据该类型的范围和精度计算所有运算和常量的结果。
1
编译器根据 double 的范围和精度计算 floatdouble 类型的运算和常量的结果。根据 long double 的范围和精度计算 long double 运算和常量的结果。
2
编译器根据 long double 的范围和精度计算所有运算和常量的结果。

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

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

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

当您在 x86 上指定 -xarch=sse2(或 SSE2 处理器系列的任何更高版本,例如 sse3ssse3sse4_1sse4_2 等)或 -m64,并包括 float.h 时,FLT_EVAL_METHOD 将扩展到 0。将根据其类型计算所有浮点表达式的结果。

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

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

当您在 x86 体系结构上指定 -fprecision 以及 -xarch=sse2(或 SSE2 处理器系列的任何更高版本,例如 sse3ssse3sse4_1sse4_2 等)或 -m64,并包括 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.7.2 受限指针

D.1.3 __func__ 支持

功能:6.4.2.2 预定义的标识符

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

D.1.4 通用字符名 (UCN)

功能:6.4.3 通用字符名

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

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

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

UCN \Unnnnnnnn 指定了其八位短标识符(根据 ISO/IEC 10646 的规定)为 nnnnnnnn 的字符。同样,通用字符名 \unnnn 指定了其四位短标识符为 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 的偏移相同。

可以像使用任何普通数组成员一样使用 d,例如 S.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 函数说明符

完全支持 1999 C ISO 标准定义的内联函数。

请注意,根据 C 标准,内联仅仅是对 C 编译器的建议。C 编译器可以选择不内联任何内容,而编译对实际函数的调用。

仅当在 -xO3 或更高优化级别进行编译并且优化器的启发式算法确定这样有利时,Oracle Solaris Studio C 编译器才会内联 C 函数调用。C 编译器不提供强制内联函数的方法。

静态内联函数很简单。在引用处内联使用内联函数说明符定义的函数,或者对实际函数进行调用。编译器可以选择在每个引用处执行的操作。编译器确定在 -xO3 及更高级别进行内联是否有益。如果内联无益(或在低于 -xO3 的优化级别进行内联),将编译对实际函数的引用,并将函数定义编译为目标代码。请注意,如果程序使用函数的地址,将在目标代码中编译实际的函数,而不进行内联。

外部内联函数较为复杂。有两种类型的外部内联函数:内联定义外部内联函数

内联定义是使用关键字 inline 定义的函数,不包含关键字 staticextern,并且显示在源文件(或包含的文件)中的所有原型也包含关键字 inline,而不包含关键字 staticextern。对于内联定义,编译器不得创建函数的全局定义。这意味着对非内联的内联定义的任何引用都将是对在其他位置定义的全局函数的引用。换句话说,编译此翻译单元(源文件)所产生的目标文件将不会包含内联定义的全局符号。对非内联的函数的任何引用都将是对由其他某个目标文件或库在链接时提供的外部(全局)符号的引用。

外部内联函数由具有 extern 存储类说明符(即函数定义或原型)的文件作用域声明来声明。对于外部内联函数,编译器将在生成的目标文件中提供此函数的全局定义。编译器可以选择内联对该函数的任何引用(该函数在提供其定义的翻译单元(源文件)中可见),也可以选择调用全局函数。

未定义依赖于函数调用实际是否内联的任何程序的行为。

另外需要注意的是,具有外部链接的内联函数不得声明或引用翻译单元任意位置处的静态变量。

D.1.10.1 Oracle Solaris Studio C 编译器针对内联函数的 gcc 兼容性

要从 Oracle Solaris Studio C 编译器中获取与大多数程序中外部内联函数的 GNU C 编译器实现兼容的行为,请使用 -features=no%extinl 标志。指定了此标志时,Oracle Solaris Studio C 编译器会将此函数视为已声明为静态内联函数。

当获取此函数的地址时,会发生此行为不兼容的情况。对于 gcc,这将是全局函数的地址;对于 Sun 编译器,将使用局部静态定义地址。

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 string 行为完全相同。考虑以下示例。首先列出示例的源代码,然后在预处理程序使其通过预处理之后,再列出示例的源代码。

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 -xO3example% 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%