此附录讨论了 ISO/IEC 9899:1999 C 编程语言标准的一些功能。
-std=c99 标志控制编译器识别 9899:1999 ISO/C 标准。有关 -std 标志语法的更多信息,请参见–std=value。
本附录提供了有关下列某些支持的功能的讨论和示例。
子条款 5.2.4.2.2 浮点类型 <float.h> 的特性
子条款 6.2.5 _Bool
子条款 6.2.5 _Complex 类型
子条款 6.3.2.1 不限制仅在左值进行数组到指针的转化
子条款 6.4.1 关键字
子条款 6.4.2.2 预定义标识符
6.4.3 通用字符名
子条款 6.4.4.2 十六进制浮点文字
子条款 6.4.9 注释
子条款 6.5.2.2 函数调用
子条款 6.5.2.5 复合文字
子条款 6.7.2 类型说明符
子条款 6.7.2.1 结构和联合说明符
子条款 6.7.3 类型限定符
子条款 6.7.4 函数说明符
子条款 6.7.5.2 数组声明符
子条款 6.7.8 初始化
子条款 6.8.2 复合语句
子条款 6.8.5 迭代语句
子条款 6.10.3 宏替换
子条款 6.10.6 STDC pragma
子条款 6.10.8 __STDC_IEC_559 和 __STDC_IEC_559_COMPLEX 宏
子条款 6.10.9 Pragma 运算符
功能:5.2.4.2.2 浮点类型 <float.h> 的特性
使用浮点操作数进行运算的值以及同时符合常见算术转换和浮点常量的值,经过计算所得格式的范围和精度可能大于该类型所要求的范围和精度。计算格式的使用以实现定义的 FLT_EVAL_METHOD 值为特征,如下表中所示。
|
当在 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 类型进行计算。有关更多信息,请参见-flteval[={any|2}]。
当您在 x86 上指定 -xarch=sse2(或 SSE2 处理器系列的任何更高版本,例如 sse3、ssse3、sse4_1、sse4_2 等)或 -m64,并包括 float.h 时,FLT_EVAL_METHOD 将扩展到 0。将根据其类型计算所有浮点表达式的结果。
即使浮点表达式按 double 类型来计算结果,-Xt 选项也不影响 FLT_EVAL_METHOD 的扩展。有关更多信息,请参见-X[c|a|t|s]。
使用 -fsingle 选项将导致浮点表达式以单精度来计算结果。有关更多信息,请参见-fsingle。
当您在 x86 体系结构上指定 -fprecision 以及 -xarch=sse2(或 SSE2 处理器系列的任何更高版本,例如 sse3、ssse3、sse4_1、sse4_2 等)或 -m64,并包括 float.h 时,FLT_EVAL_METHOD 将扩展到 -1。
C99 标准引入了以下新的关键字。如果您在使用 -std=c89 进行编译时使用这些关键字作为标识符,则编译器会发出警告。在使用 -std=c99 或 -std=c11 的情况下,编译器会根据上下文针对使用这些关键字作为标识符发出警告或错误消息。
inline
_Imaginary
_Complex
_Bool
restrict
通过 restrict 限定指针访问的对象要求对该对象的所有访问都直接或间接使用该特定 restrict 限定指针的值。通过任何其他方式访问该对象可能导致不确定的行为。restrict 限定符的既定用途是使编译器可以做出提升优化的假定。
有关如何有效使用 受限指针 限定符的示例和说明,请参见Restricted Pointers。
编译器支持预定义标识符 __func__,该标识符定义为 chars 数组,它包含 __func__ 所在的当前函数的名称。
功能:6.4.3 通用字符名
UCN 允许在 C 源码中使用任何字符,而不仅仅是英文字符。UCN 具有以下格式:
\u4_hex_digits_value
\U8_hex_digits_value
UCN 不能指定 0024 ($)、0040 (@) 或 0060 (?) 以外小于 00A0 的值,也不能指定 D800 到 DFFF(含 D800 和 DFFF)范围内的值。
UCN 可用于标识符、字符常量和文本字符串中,以指定 C 基本字符集中不存在的字符。
UCN \Unnnnnnnn 指定了其八位短标识符(根据 ISO/IEC 10646 的规定)为 nnnnnnnn 的字符。同样,通用字符名 \unnnn 指定了其四位短标识符为 nnnn(其八位短标识符为 0000nnnn)的字符。
字符 // 引入包含直到(但不包括)新换行符的所有多字节字符的注释,除非 // 字符出现在字符常量、文本字符串或注释中。
功能:6.5.2.2 函数调用
与 1990 C 标准不同,1999 C 标准不再允许隐式声明。C 编译器的以前版本仅在设置了 -v(详细)的情况下发出有关隐式定义的警告消息。只要标识符隐式定义为 int 或函数,系统便会对隐式定义发出这些消息及其他新警告。
该编译器的几乎所有用户均可能注意到这种变化,原因是它会导致大量警告消息。常见原因包括未能包含用于声明所使用函数的相应系统头文件,如需要包含 <stdio.h> 的 printf。无提示接受隐含声明的 1990 C 标准行为可使用 -std=c89 恢复。
现在,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%
每个声明中的声明说明符中应至少指定一个类型说明符。有关更多信息,请参见禁止隐式 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%
此功能也称为 struct hack。允许 struct 的最后一个成员为零长度数组,例如 int foo[];这种类型的 struct 通常用作头文件来访问 malloc()'d 内存。
例如,在结构 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;
如果同一限定符在同一说明符限定符列表中出现多次(无论直接出现还是通过一个或多个 typedefs),行为与该类型限定符仅出现一次时相同。
在 C90 中,以下代码会导致错误:
%example cat test.c const const int a; int main(void) { return(0); } %example cc -std=c89 test.c "test.c", line 1: invalid type combination
但是,对于 C99,C 编译器接受多个限定符。
%example cc -std=c99 test.c %example
完全支持 1999 C ISO 标准定义的内联函数。
请注意,根据 C 标准,内联仅仅是对 C 编译器的建议。C 编译器可以选择不内联任何内容,而编译对实际函数的调用。
仅当在 -xO3 或更高优化级别进行编译并且优化器的启发式算法确定这样有利时,Oracle Solaris Studio C 编译器才会内联 C 函数调用。C 编译器不提供强制内联函数的方法。
静态内联函数很简单。在引用处内联使用内联函数说明符定义的函数,或者对实际函数进行调用。编译器可以选择在每个引用处执行的操作。编译器确定在 -xO3 及更高级别进行内联是否有益。如果内联无益(或在低于 -xO3 的优化级别进行内联),将编译对实际函数的引用,并将函数定义编译为对象代码。请注意,如果程序使用函数的地址,将在对象代码中编译实际的函数,而不进行内联。
外部内联函数较为复杂。有两种类型的 extern 内联函数:内联定义和外部内联函数。
内联定义是使用关键字 inline 定义的函数,不包含关键字 static 或 extern,并且显示在源文件(或包含的文件)中的所有原型也包含关键字 inline,而不包含关键字 static 或 extern。对于内联定义,编译器不得创建函数的全局定义。这意味着对非内联的内联定义的任何引用都将是对在其他位置定义的全局函数的引用。换句话说,编译此翻译单元(源文件)所产生的对象文件将不会包含内联定义的全局符号。对非内联的函数的任何引用都将是对由其他某个对象文件或库在链接时提供的外部(全局)符号的引用。
外部内联函数由具有 extern 存储类说明符(即函数定义或原型)的文件作用域声明来声明。对于 extern 内联函数,编译器将在生成的对象文件中提供此函数的全局定义。编译器可以选择内联对该函数的任何引用(该函数在提供其定义的翻译单元(源文件)中可见),也可以选择调用全局函数。
未定义依赖于函数调用实际是否内联的任何程序的行为。
另外需要注意的是,具有外部链接的内联函数不得声明或引用翻译单元任意位置处的静态变量。
要从 Oracle Solaris Studio C 编译器中获取与大多数程序中 extern 内联函数的 GNU C 编译器实现兼容的行为,请使用 -features=no%extinl 标志。指定了此标志时,Oracle Solaris Studio C 编译器会将此函数视为已声明为静态内联函数。
当获取此函数的地址时,会发生此行为不兼容的情况。对于 gcc,这将是全局函数的地址,而对于Oracle Solaris Studio 的 C编译器,将使用本地静态定义地址。
现在,关键字 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,并且使用它可访问至少包含十个元素的整数数组。
在堆栈中分配 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
功能:6.7.8 初始化
指定的初始化函数为初始化稀疏数组提供了一种机制,这在数字编程的实践中很常见。
指定的初始化函数可以对稀疏结构进行初始化,这在系统编程中很常见,并且可以通过任何成员对联合进行初始化,而不管其是否为第一个成员。
请看以下这些示例。此处的第一个示例显示了如何使用指定的初始化函数来对数组进行初始化:
enum { first, second, third }; const char *nm[] = { [third] = "third member", [first] = "first member", [second] = "second member", };
下面的示例证明了如何使用指定的初始化程序来对 struct 对象的字段进行初始化:
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 };
C 编译器接受关于可执行代码的混合类型声明,如下例所示:
#include <stdio.h> int main(void){ int num1 = 3; printf("%d\n", num1); int num2 = 10; printf("%d\n", num2); return(0); }
C 编译器接受作为 for 循环语句中第一个表达式的类型声明:
for (int i=0; i<10; i++){ //loop body };
for 循环的初始化语句中声明的任何变量的作用域是整个循环(包括控制和迭代表达式)。
功能: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));
功能:6.10.9 Pragma 运算符
_Pragma ( string-literal ) 形式的一元运算符表达式处理如下:
如果文本字符串具有 L 前缀,则删除该前缀。
删除前导和结尾双引号。
用双引号替换每个换码序列。
用单个反斜杠替换每个换码序列 \\。
预处理标记的结果序列作为 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 -xO3 example% cat test.i
下面显示了预处理程序完成后的源代码。
void main() { omp_set_dynamic(0); omp_set_num_threads(2); # pragma omp parallel { printf("Hello!\n"); } } example% cc test.c -xopenmp example% ./a.out Hello! Hello! example%