本章讨论特定于 C 编译器方面的内容。该信息已编入语言扩展和环境。
C 编译器与新的 ISO C 标准 ISO/IEC 9899-1999 中介绍的某些 C 语言功能兼容。如果您希望编译与以前的 C 标准 ISO/IEC 9889-1990 标准(及修正案 1)兼容的代码,请使用 -xc99=none,这样编译器将忽略 ISO/IEC 9899-1999 标准的增强功能。
十进制、八进制和十六进制的整型常量可加后缀以指示类型,如下表所示。
表 2–1 数据类型后缀
如果设置 -xc99=all,编译器将根据常量大小,使用以下列表中可以表示该值的第一项:
int
long int
long long int
如果值超过 long long int 可表示的最大值,编译器会发出警告。
如果设置 -xc99=none,则为无后缀常量指定类型时,编译器将根据常量大小,使用以下列表中可以表示该值的第一项:
一个多字符常量,它不是具有从每个字符的数值派生的值的转义序列。例如,常量 '123' 的值为:
0 |
'3' |
'2' |
'1' |
或 0x333231。
使用 -Xs 选项并且在 C 的其他非 ISO 版本中,该值为:
0 |
'1' |
'2' |
'3' |
使用以下声明说明符有助于隐藏外部符号的声明和定义。通过使用这些说明符,您不必再使用链接程序作用域的 mapfile。此外,还可以通过在命令行中指定 -xldscope 来控制变量作用域的缺省设置。有关更多信息,请参见B.2.102 -xldscope={v}。
表 2–2 声明说明符
对象或函数可以用限制较多的说明符重新声明,但不能用限制较少的说明符重新声明。符号定义后,不可以用不同的说明符声明符号。
__global 是限制最少的作用域,__symbolic 是限制较多的作用域,而 __hidden 是限制最多的作用域。
通过声明线程局部变量,可以利用线程局部存储。线程局部变量声明由一个标准变量声明外加变量说明符 __thread 组成。有关更多信息,请参见B.2.153 -xthreadvar[= o]。
您必须在所编译的源文件中线程变量的第一个声明中包含 __thread 说明符。
在具有静态存储持续时间的对象的声明中,只能使用 __thread 说明符。您可以如初始化任何其他静态存储持续时间的对象一样静态地初始化线程变量。
使用 __thread 说明符声明的变量与不使用 __thread 说明符声明的变量具有相同的链接程序绑定。这包括临时定义,如无初始化函数的声明。
线程变量的地址不是常量。因此,线程变量的地址操作符 (&) 在运行时求值,并返回当前线程的线程变量的地址。结果,静态存储持续时间的对象被动态地初始化为线程变量的地址。
线程变量的地址在相应线程的生命周期中是稳定的。进程中任何线程都可以在线程变量的生命周期任意使用该变量的地址。不能在线程终止后使用线程变量的地址。线程终止后,该线程的变量的所有地址都无效。
缺省情况下,IEEE 754 浮点运算“不停止”,并且下溢是“渐进的”。下面给出了概括性说明,有关详细信息,请参见《数值计算指南》。
不停止意味着遇到除数为零、浮点下溢或无效操作异常时执行不会停止。例如,考虑以下算式,其中 x 为零,y 为正数:
z = y / x;
缺省情况下,z 设置为值 +Inf,执行不会停止。但是,如果设置 -fnonstd 选项,此代码会导致退出,如核心转储。
下面是渐进下溢的工作方式。假设您有下列代码:
x = 10; for (i = 0; i < LARGE_NUMBER; i++) x = x / 10; |
第一次执行循环时,x 设置为 1;第二次执行循环时,设置为 0.1;第三次执行循环时,设置为 0.01,依此类推。最后,x 达到比较低的值,以致机器无法表示其值。下次循环运行时将出现什么情况?
假设可表示的最小数为 1.234567e-38
下次循环运行时,将通过去掉一位尾数并且指数减去 1 来修改该数,因此新值为 1.23456e-39,然后为 1.2345e-40,依此类推。这称为“渐进下溢”,它是缺省行为。在非标准模式下,不会发生这种“去位”情况;通常,x 被简单地设置为零。
C 编译器可识别称为计算转移 (computed goto) 语句的 C 扩展。使用计算转移 (computed goto) 语句能够在运行时确定分支目标。通过使用 '&&' 运算符可以获取标签的地址,并且可以将标签地址指定给 void * 类型的指针:
void *ptr; ... ptr = &&label1; |
后面的 goto 语句可以通过 ptr 转到 label1:
goto *ptr; |
由于 ptr 在运行时进行计算,因此 ptr 可以接受作用域内任何标签的地址,而 goto 语句可以转到该位置。
使用计算转移 (computed goto) 语句的一种方法是用于转移表的实现:
static void *ptrarray[] = { &&label1, &&label2, &&label3 }; |
现在可以通过索引来选择数组元素:
goto *ptrarray[i]; |
标签的地址只能通过当前函数作用域计算。尝试在当前函数外部获取标签的地址会产生不可预测的结果。
转移表和开关语句的作用相似(虽然存在某些主要差异),转移表使跟踪程序流更加困难。一个显著的差异是:开关语句转移目标全都从开关保留字开始正向转移;使用计算转移 (computed goto) 语句实现转移表可进行正向和反向分支。
#include <stdio.h> void foo() { void *ptr; ptr = &&label1; goto *ptr; printf("Failed!\n"); return; label1: printf("Passed!\n"); return; } int main(void) { void *ptr; ptr = &&label1; goto *ptr; printf("Failed!\n"); return 0; label1: foo(); return 0; } |
以下示例也使用转移表控制程序流:
#include <stdio.h> int main(void) { int i = 0; static void * ptr[3]={&&label1, &&label2, &&label3}; goto *ptr[i]; label1: printf("label1\n"); return 0; label2: printf("label2\n"); return 0; label3: printf("label3\n"); return 0; } %example: a.out %example: label1 |
计算转移 (computed goto) 语句的另一个应用是作为线程代码的解释程序。解释程序函数内部的标签地址可以存储在线程代码中以便快速分发。
使用 -xc99=none 进行编译时,Sun C 编译器包含数据类型 long long 和 unsigned long long,它们与数据类型 long 类似。long long 数据类型存储 64 位信息;在 SPARC V8 和 x86 上,long 存储 32 位信息。在 SPARC V9 上,long 数据类型存储 64 位信息。long long 数据类型在 -Xc 模式下不可用。
要输出或扫描 long long 数据类型,请在转换说明符前面加字母 ll。例如,要以带符号十进制格式输出 llvar(long long 数据类型的变量),请使用:
printf("%lld\n", llvar); |
某些二元运算符对其操作数的类型进行转换以便两个操作数具有相同的类型,该类型也是结果的类型。下面这些转换称为常见算术转换:
如果两个操作数中的任何一个的类型为 long double,则另一个操作数转换成类型 long double。
否则,如果两个操作数中的任何一个的类型为 double,则另一个操作数转换成类型 double。
否则,如果两个操作数中的任何一个的类型为 float,则另一个操作数转换成类型 float。
否则,对两个操作数执行整型提升。然后,应用以下规则:
如果两个操作数中的任何一个的类型为 unsigned long long int,则另一个操作数转换成类型 unsigned long long int。
如果两个操作数中的任何一个的类型为 long long int,则另一个操作数转换成类型 long long int。
如果两个操作数中的任何一个的类型为 unsigned long int,则另一个操作数转换成类型 unsigned long int。
否则,当仅在 SPARC V9 上进行编译并指定了 cc -xc99=none 时,如果一个操作数的类型为 long int 而另一个操作数的类型为 unsigned int,则两个操作数的类型均转换为 unsigned long int。
否则,如果两个操作数中的任何一个的类型为 long int,则另一个操作数转换为类型 long int。
否则,如果两个操作数中的任何一个的类型为 unsigned int,则另一个操作数转换为类型 unsigned int。
在标准 C 中,switch 语句中的 case 标签只能有一个关联值。Sun C 允许使用某些编译器中使用的扩展(称为 case 范围)。
case 范围指定要与单个 case 标签关联的值范围。case 范围语法为:
case low ... high :
case 范围的行为就好像为从 low 到 high(含)的给定范围内的每个值指定了 case 标签。(如果 low 和 high 相等,则 case 范围仅指定一个值。)较低值和较高值必须符合 C 标准的要求。也就是说,它们必须是有效的整数常量表达式(C 标准 6.8.4.2)。case 范围和 case 标签可以随意混合,一个 switch 语句中可以指定多个 case 范围。
编程示例:
enum kind { alpha, number, white, other }; enum kind char_class(char c); { enum kind result; switch(c) { case 'a' ... 'z': case 'A' ... 'Z': result = alpha; break; case '0' ... '9': result = number; break; case ' ': case '\n': case '\t': case '\r': case '\v': result = white; break; default: result = other; break; } return result; } |
除了 case 标签的现有要求以外的错误情形:
-如果 low 的值大于 high 的值,则编译器会拒绝代码并显示错误消息。(其他编译器的行为不一致,因此,只有通过错误消息才能确保程序在由其他编译器编译时不会表现出不同的行为。)
-如果 case 标签的值在已在 switch 语句中使用的 case 范围内,则编译器会拒绝代码并显示错误消息。
-如果 case 范围重叠,则编译器会拒绝代码并显示错误消息。
请注意,如果 case 范围的一个端点是数值,则在省略号 (...) 两侧留空格以避免其中一个点被视为小数点。
示例:
case 0...4; // error case 5 ... 9; // ok |
#assert predicate (token-sequence) |
将 token-sequence 和断言名称空间(与用于宏定义的空间不同)中的谓词相结合。谓词必须为标识符标记。
#assert predicate |
断言 predicate 存在,但是未与任何标记序列相结合。
缺省情况下,编译器提供以下预定义谓词(不在 -Xc 模式下):
#assert system (unix) #assert machine (sparc) #assert machine (i386)(x86) #assert cpu (sparc) #assert cpu (i386)(x86) |
缺省情况下,lint 提供以下预定义谓词(不在 -Xc 模式下):
#assert lint (on) |
任何断言均可使用 #unassert 进行删除,该命令的语法与 assert 的语法相同。使用不带参数的 #unassert 将删除关于谓词的所有断言;指定一个断言将只删除该断言。
可以使用以下语法在 #if 语句中测试断言:
#if #predicate(non-empty token-list) |
例如,可以使用以下行测试预定义谓词 system:
#if #system(unix) |
其结果为真。
#pragma pp-tokens |
指定实现定义的操作。
编译系统可识别以下 #pragma。编译器忽略未识别的 pragma。如果使用 -v 选项,将针对无法识别的 pragma 发出警告。
#pragma align integer (variable[, variable] )
align pragma 会使所有提及的变量内存与整数字节对齐,从而覆盖缺省值。请遵循以下限制:
integer 值必须是介于 1 和 128 之间的 2 的幂,有效值包括 1、2、4、8、16、32、64 和 128。
variable 是全局变量或静态变量,它不能为自动变量。
如果指定的对齐比缺省值小,就使用缺省值。
pragma 行必须在它提到的变量的声明前面出现;否则,它将被忽略。
提到但未在 pragma 行后面的文本中声明的任何变量将被忽略。例如:
#pragma align 64 (aninteger, astring, astruct) int aninteger; static char astring[256]; struct astruct{int a; char *b;}; |
#pragma c99(“implicit” | “ no%implicit”)
该 pragma 控制隐式函数声明的诊断。如果将 c99 pragma 值设置为 "implicit"(请注意使用了引号),则编译器在找到隐式函数声明时将生成一条警告。如果将 c99 pragma 值设置为 "no%implicit"(请注意使用了引号),则编译器将无提示地接受隐式函数声明,直到该 pragma 值重置。
-xc99 选项的值会影响该 pragma。如果 -xc99=all,则该 pragma 被设置为 #pragma c99("implicit");如果 -xc99=none,则该 pragma 被设置为 #pragma c99("no%implicit")。
缺省情况下,该 pragma 设置为 c99=("implicit")。
#pragma does_not_read_global_data ( funcname [, funcname])
该 pragma 断言指定列表中的例程不直接或间接读取全局数据。允许对调用这些例程的代码进行更好的优化。具体来讲,赋值语句或存储可以围绕这样的调用移动。
必须在该 pragma 之前使用原型或空参数列表声明指定的函数。如果全局访问的断言不为真,那么程序的行为就是未定义的。
#pragma does_not_return (funcname [, funcname])
此 pragma 向编译器断言,将不会返回对指定例程的调用。这样编译器可以执行与假定一致的优化。例如,寄存器生命周期将在调用点终止,进而允许更多的优化。
如果指定的函数不返回,程序的行为就是未定义的。只有在使用原型或空参数列表声明指定的函数之后才允许使用该 pragma,如以下示例所示:
extern void exit(int); #pragma does_not_return(exit) extern void __assert(int); #pragma does_not_return(__assert) |
#pragma does_not_write_global_data (funcname [, funcname ])
该 pragma 断言指定列表的例程不直接或间接写全局数据。允许对调用这些例程的代码进行更好的优化。具体来讲,赋值语句或存储可以围绕这样的调用移动。
必须在该 pragma 之前使用原型或空参数列表声明指定的函数。如果全局访问的断言不为真,那么程序的行为就是未定义的。
#pragma error_messages (on|off| default, tag… tag)
错误消息 pragma 提供源程序内部对 C 编译器和 lint 发出的消息的控制。对于 C 编译器,pragma 只对警告消息有效。C 编译器的 -w 选项通过禁止显示所有警告消息来覆盖该 pragma。
#pragma error_messages (on, tag… tag)
on 选项结束前面的任何 #pragma error_messages 选项(如 off 选项)的作用域,并覆盖 -erroff 选项的效果。
#pragma error_messages (off, tag… tag)
off 选项阻止 C 编译器或 lint 程序发出以 pragma 中指定的标记开头的指定消息。pragma 对任何指定的错误消息的作用域仍然有效,直到被另一个 #pragma error_messages 覆盖或编译结束。
#pragma error_messages (default, tag… tag)
default 选项结束前面的任何 #pragma error_messages 指令对指定标记的作用域。
#pragma fini (f1[, f2…,fn]
使实现在调用 main() 例程之后调用函数 f1 至 fn(完成函数)。此类函数的类型应为 void,并且不接受任何参数,当程序正常终止或所含共享对象从内存中删除时会调用这些函数。和“初始化函数”一样,完成函数按链接编辑器的处理顺序执行。
如果完成函数影响全局程序的状态,则应当小心操作。例如,除非接口明确声明您使用系统库完成函数时会发生什么情况,否则您应捕获和恢复所有全局状态信息,如系统库完成函数可能更改的 errno 的值。
此类函数每出现在 #pragma fini 指令中一次,就会被调用一次。
#pragma hdrstop
hdrstop pragma 必须放在最后一个头文件之后,以标识要共享相同预编译头文件的每个源文件中活前缀的结束。例如,考虑以下文件:
example% cat a.c #include "a.h" #include "b.h" #include "c.h" #include <stdio.h> #include "d.h" . . . example% cat b.h #include "a.h" #include "b.h" #include "c.h" |
活动源代码前缀在 c.h 结束,因此您需要在每个文件中在 c.h 后面插入 #pragma hdrstop。
#pragma hdrstop 必须只在使用 cc 命令指定的源文件的活前缀的末尾出现。不要在任何 include 文件中指定 #pragma hdrstop。
将 string 放在可执行文件的 .comment 部分。
#pragma init (f1[, f2…,fn] )
使实现在调用 main() 之前调用函数 f1 至 fn(初始化函数)。此类函数的类型应为 void,并且不接受任何参数,在开始执行时构造程序的内存映像时会调用这些函数。如果初始化函数在共享对象中,则在执行将共享对象放入内存的操作(无论是程序启动,还是某些动态装入操作,如 dlopen())时执行它们。调用初始化函数的唯一顺序是链接编辑器处理它们的顺序,静态和动态均可。
如果初始化函数影响全局程序的状态,则应当小心操作。例如,除非接口明确声明您使用系统库初始化函数时会发生什么情况,否则您应捕获和恢复所有全局状态信息,如系统库初始化函数可能更改的 errno 的值。
此类函数每出现在 #pragma init 指令中一次,就会被调用一次。
#pragma [no_]inline (funcname[, funcname])
该 pragma 控制 pragma 的参数中列出的例程名称的内联。该 pragma 的作用域针对整个文件。该 pragma 只允许全局内联控制,不允许特定于调用点的控制。
如果您使用 #pragma inline,它会提示编译器内联当前文件中与 pragma 中列出的例程列表匹配的调用。在某些情况下,此建议可能被忽略。例如,当函数的主体在另一个模块并且未使用交叉文件选项时,忽略该建议。
如果您使用 #pragma no_inline,它会提示编译器不要内联当前文件中与 pragma 中列出的例程列表匹配的调用。
只有在使用原型或空参数列表声明函数之后,才允许使用 #pragma inline 和 #pragma no_inline,如下例所示:
static void foo(int); static int bar(int, char *); #pragma inline(foo, bar) |
另请参见 -xldscope、-xinline、-xO 和 -xcrossfile。
#pragma int_to_unsigned (funcname )
对于返回类型 unsigned 的函数,在 -Xt 或 -Xs 模式下,将函数返回值的类型更改为 int。
(SPARC) #pragma MP serial_loop
Sun 特定的 MP pragma 已过时,并且不再受支持。但是,编译器改为支持 OpenMP 3.0 规范指定的 API。有关标准的指令的迁移信息,请参见《OpenMP API 用户指南》。
有关详细信息,请参阅3.8.3.1 串行 Pragma。
(SPARC) #pragma MP serial_loop_nested
Sun 特定的 MP pragma 已过时,并且不再受支持。但是,编译器改为支持 OpenMP 3.0 规范指定的 API。有关标准的指令的迁移信息,请参见《Sun Studio 12 Update 1:OpenMP API 用户指南》。
有关详细信息,请参阅3.8.3.1 串行 Pragma。
(SPARC) #pragma MP taskloop
Sun 特定的 MP pragma 已过时,并且不再受支持。但是,编译器改为支持 OpenMP 3.0 规范指定的 API。有关标准的指令的迁移信息,请参见《OpenMP API 用户指南》。
有关详细信息,请参阅3.8.3.2 并行 Pragma。
(SPARC) #pragma nomemorydepend
该 pragma 指定,对于某个循环的任何迭代,不存在内存依赖性。也就是说,在某个循环的任何迭代内部,不存在对相同内存的引用。该 pragma 将允许编译器(流水线化程序)在某个循环的单次迭代内更有效地调度指令。如果某个循环的任何迭代内部存在任何内存依赖性,则程序的执行结果未定义。编译器在优化级别 3 或更高级别上使用此信息。
此 pragma 的作用域从它自身开始,在以下任何一种情况最先出现时结束: 下一块的开始部分、当前块内部的下一个 for 循环、当前块的结尾。该 pragma 应用于 pragma 作用域结束前的下一个 for 循环。
(SPARC) #pragma no_side_effect(funcname[, funcname…])
funcname 指定当前转换单元内部某个函数的名称。必须在该 pragma 之前使用原型或空参数列表声明的函数。必须在函数的定义之前指定 pragma。对于命名的函数 funcname,该 pragma 声明函数无任何副作用。这意味着,funcname 返回一个只依赖传递参数的结果值。另外,funcname 和任何已调用的子函数具有以下特性:
不读取或写入调用点的调用者中可视的程序状态的任何部分。
不执行 I/O。
不更改调用点不可视程序状态的任何部分。
使用函数进行优化时,编译器使用此信息。如果函数具有副作用,执行调用该函数的程序的结果是未定义的。编译器在优化级别 3 或更高级别上使用此信息。
#pragma opt level (funcname[, funcname])
funcname 指定当前转换单元内定义的函数的名称。level 值指定用于所指定函数的优化级别。可以指定优化级别 0、1、2、3、4、5。可以通过将 level 设置为 0 来关闭优化。必须在该 pragma 之前使用原型或空参数列表声明函数。pragma 则必须对要优化的函数进行定义。
pragma 中所列任何函数的优化级别都降为 -xmaxopt 值。-xmaxopt=off 时,忽略 pragma。
#pragma pack(n)
使用 #pragma pack(n) 将影响结构或联合的成员封装。缺省情况下,结构或联合的成员按其自然边界对齐;一个字符型 (char) 数据占一个字节,一个短整型 (short) 数据占两个字节,一个整型 (integer) 数据占四个字节,等等。如果存在 n,它必须为 2 的幂,并且为任何结构或联合成员指定最严格的自然对齐。不接受零。
您可以使用 #pragma pack(n) 为结构或联合成员指定对齐边界。例如,#pragma pack(2) 会使 int、long、long long、float、double、long double 和指针与双字节边界对齐,而不是与其自然边界对齐。
如果 n 等于或大于您使用的平台上最严格的对齐(在 x86 上为 4,在 SPARC v8 上为 8,在 SPARC v9 上为 16),则该指令具有自然对齐的效果。同样,如果省略 n,成员对齐将恢复为自然对齐边界。
#pragma pack(n) 指令应用于它后面的所有结构或联合定义,直到出现下一个 pack 指令。如果在具有不同包装的不同转换单元中定义了相同的结构或联合,那么程序会因某种原因而失败。特别是,不应在包含定义了预编译库的接口的头文件之前使用 #pragma pack(n)。#pragma pack(n) 的建议用法是,将它放在程序代码中紧挨在要封装的任何结构或联合的前面。在封装的结构后面紧跟 #pragma pack( )。
请注意,使用 #pragma pack 时,封装的结构或联合本身的对齐方式与其更严格对齐的成员相同。因此,该结构或联合的任何声明将使用压缩对齐。例如,只包含 char 数据的 struct 无对齐限制,而包含 double 数据的 struct 将按 8 字节边界对齐。
如果您使用 #pragma pack 将结构或联合成员与其自然边界以外的边界对齐,则访问这些字段通常会导致 SPARC 出现总线错误。为避免发生此类错误,请确保同时还指定了 -xmemalign 选项。有关编译此类程序的最佳方法,请参见B.2.117 -xmemalign=ab。
#pragma pipeloop(n)
对于参数 n,该 pragma 接受正整数常量值或 0。该 pragma 指定,循环可流水线化,并且循环携带的依赖性的最小依赖距离为 n。如果该距离为 0,则循环实际上是 Fortran 风格的 doall 循环,并且应在目标处理程序上流水线化。如果该距离大于 0,则编译器(流水线化程序)将只尝试流水线化 n 次连续迭代。编译器在优化级别 3 或更高级别上使用此信息。
此 pragma 的作用域从它自身开始,在以下任何一种情况最先出现时结束: 下一块的开始部分、当前块内部的下一个 for 循环、当前块的结尾。该 pragma 应用于 pragma 作用域结束前的下一个 for 循环。
#pragma rarely_called(funcname[, funcname] )
该 pragma 提示编译器,指定的函数很少被调用。这样,编译器可以在此类例程的调用点执行配置文件反馈样式的优化,而没有配置文件收集阶段的开销。因为该 pragma 只是建议,所以编译器不执行基于该 pragma 的任何优化。
必须在该 pragma 之前使用原型或空参数列表声明指定的函数。以下是 #pragma rarely_called 的示例:
extern void error (char *message); #pragma rarely_called(error) |
#pragma redefine_extname old_extname new_extname
该 pragma 导致目标代码中名称 old_extname 的各个外部定义具体值被 new_extname 替换。结果,链接程序在链接时只看到名称 new_extname。如果在第一次使用 old_extname 作为函数定义、初始化函数或表达式之后遇到 #pragma redefine_extname,则该作用未定义。(在 – Xs 模式下不支持该 pragma。)
如果 #pragma redefine_extname 可用,编译器会提供预定义宏 __PRAGMA_REDEFINE_EXTNAME 的定义,这样您可以编写在有无 #pragma redefine_extname 的条件下均可运行的可移植代码。
#pragma redefine_extname 的目的是,提供一种在函数名称无法更改时重新定义函数接口的有效方法。例如,当某个库中必须保留初始函数定义时,为了与现有程序兼容,创建同一函数的新定义以供新程序使用。这可以通过用新名称将新函数定义增加到库中来完成。因此,声明函数的头文件使用 #pragma redefine_extname,以便对函数的所有使用均与该函数的新定义链接。
#if defined(__STDC__) #ifdef __PRAGMA_REDEFINE_EXTNAME extern int myroutine(const long *, int *); #pragma redefine_extname myroutine __fixed_myroutine #else /* __PRAGMA_REDEFINE_EXTNAME */ static int myroutine(const long * arg1, int * arg2) { extern int __myroutine(const long *, int*); return (__myroutine(arg1, arg2)); } #endif /* __PRAGMA_REDEFINE_EXTNAME */ #else /* __STDC__ */ #ifdef __PRAGMA_REDEFINE_EXTNAME extern int myroutine(); #pragma redefine_extname myroutine __fixed_myroutine #else /* __PRAGMA_REDEFINE_EXTNAME */ static int myroutine(arg1, arg2) long *arg1; int *arg2; { extern int __fixed_myroutine(); return (__fixed_myroutine(arg1, arg2)); } #endif /* __PRAGMA_REDEFINE_EXTNAME */ #endif /* __STDC__ */ |
#pragma returns_new_memory (funcname[, funcname])
该 pragma 断言指定函数的返回值在调用点上不使用任何内存作为别名。实际上,该调用返回一个新存储单元。该信息使优化器更好地跟踪指针值并澄清存储单元。这将导致循环的调度、流水线化和并行化的改进。然而,如果断言为假,则程序的行为未定义。
只有在使用原型或空参数列表声明指定的函数之后才允许使用该 pragma,如以下示例所示:
void *malloc(unsigned); #pragma returns_new_memory(malloc) |
#pragma unknown_control_flow (funcname[, funcname])
为了描述改变调用程序的流程图的过程, C 编译器提供了 #pragma unknown_control_flow 指令。通常,该指令带有 setjmp() 等函数的声明。在 Sun 系统上,include 文件 <setjmp.h> 包含以下内容:
extern int setjmp(); #pragma unknown_control_flow(setjmp) |
同样,必须声明具有 setjmp() 类似属性的其他函数。
原则上,识别该属性的优化器可在控制流程图中插入相应的界限,从而在调用 setjmp() 的函数中安全地处理函数调用,同时保持优化流程图的未受影响部分的代码的能力。
必须在该 pragma 之前使用原型或空参数列表声明指定的函数。
#pragma unroll (unroll_factor)
对于参数 unroll_factor,该 pragma 接受正整数常量值。当解开因子不为 1 时,该指令作为对编译器的建议,指出指定的循环应由给定的因子解开。如果可能,编译器将使用该解开因子。如果解开因子值为 1,该指令作为一个命令,向编译器指出不解开该循环。编译器在优化级别 3 或更高级别上使用此信息。
此 pragma 的作用域从它自身开始,在以下任何一种情况最先出现时结束: 下一块的开始部分、当前块内部的下一个 for 循环、当前块的结尾。该 pragma 应用于 pragma 作用域结束前的下一个 for 循环。
#pragma [no_]warn_missing_parameter_info
如果指定 #pragma warn_missing_parameter_info,编译器将针对函数声明不包含任何参数类型信息的函数调用发出一条警告。请看以下示例:
exmaple% cat -n t.c 1 #pragma warn_missing_parameter_info 2 3 int foo(); 4 5 int bar () { 6 7 int i; 8 9 i = foo(i); 10 11 return i; 12 } % cc t.c -c -errtags "t.c", line 9: warning: function foo has no prototype (E_NO_MISSED_PARAMS_ALLOWED) example% |
#pragma no_warn_missing_parameter_info 将使任何以前的 #pragma warn_missing_parameter_info 无效。
缺省情况下,#pragma no_warn_missing_parameter_info 是有效的。
#pragma weak symbol1 [= symbol2]
定义弱全局符号。该 pragma 主要在源文件中用于生成库。如果链接程序无法解析弱符号,它并不生成错误消息。
#pragma weak symbol |
将 symbol 定义为弱符号。如果链接程序找不到 symbol 的定义,它不会生成错误消息。
#pragma weak symbol1 = symbol2 |
将 symbol1 定义为弱符号,它是符号 symbol2 的别名。只能在已定义 symbol2(可以在源文件中定义,也可以在其中一个包含的头文件中定义)的同一转换单元中使用这种形式的 pragma。否则,将导致编译错误。
如果程序调用但未定义 symbol1,并且 symbol1 在所链接的库中是弱符号,则链接程序使用该库中的定义。但是,如果程序定义自己的 symbol1 版本,则使用该程序的定义,而不使用库中 symbol1 的弱全局定义。如果程序直接调用 symbol2,则使用库中的定义;symbol2 的重复定义会导致 错误。
标识符 |
说明 |
__STDC__ |
__STDC__ 1 -Xc __STDC__ 0 -Xa、-Xt 未定义 -Xs |
如果 __STDC__ 未定义 (#undef __STDC__),编译器将发出警告。__STDC__ 未在 -Xs 模式下定义。
cc(1) 手册页中给出了最新的预定义列表。
如果指定 -fast,则不应依赖 errno 的值,因为该值会因代码优化而改变。要避免此问题,最简单的方法是不指定 -fast。
但是,如果指定 -fast,并且依赖 errno 的值,请执行以下操作:
通过指定 -lmopt,不与数学优化库链接。
指定 -xbuiltin=none、-U__MATHERR_ERRNO_DONTCARE、-xnolibmopt 和 -xnolibmil。
C 编译器针对 C 语言实现了许多扩展。
C 编译器支持 _Restrict 关键字,该关键字与 C99 标准中的 restrict 关键字等效。_Restrict 关键字可与 -xc99=none 和 -xc99=all 一起使用,而 restrict 关键字只能与 -xc99=all 一起使用。
有关支持的 C99 特性的更多信息,请参见表 C–6。
__asm 关键字(注意开头的两个下划线)是 asm 关键字的同义字。如果您使用 asm 而不是 __asm,并且在 -Xc 模式下编译,则编译器会发出警告。如果您在 – Xc 模式下使用 _ _asm,则编译器不会发出警告。_ _asm 语句采用以下形式:
__asm("string"); |
其中 string 是有效的汇编语言语句。
该语句将给定的汇编程序文本直接发送到汇编文件。在文件作用域(而不是函数作用域)声明的基本 asm 语句称为全局 asm 语句。其他编译器将其称为顶级 asm 语句。
全局 asm 语句是按指定的顺序发出的。也就是说,它们保留相对于彼此的顺序,并保持相对于前后函数的位置。
在较高优化级别中,编译器可能会删除认为不被引用的函数。由于编译器不知道从全局 asm 中引用了哪些函数,因此可能会无意中删除这些函数。
请注意,那些提供模板及操作数规范的扩展 asm 语句不允许作为全局语句。__asm 和 __asm__ 是 asm 关键字的同义字,可以互换使用。
__inline 和 __inline__ 是 inline 关键字的同义字(C 标准,第 6.4.1 节)
__builtin_constant_p 是编译器内置函数。它接受一个数值参数,如果已知参数是一个编译时常量,则返回 1。返回值 0 意味着编译器无法确定参数是否是编译时常量。此内置函数的典型用法是在宏中用于手动编译时优化。
__FUNCTION__ 和 __PRETTY_FUNCTION__ 是预定义标识符,这些标识符包含词法上封闭的函数的名称。它们在功能上等效于 c99 预定义标识符 __func__。在 Solaris 平台上,__FUNCTION__ 和 __PRETTY_FUNCTION__ 在 -Xs 和 -Xc 模式下不可用。
本节列出用于控制编译和运行环境的环境变量。
指定可供程序进行多处理器执行的处理器数。如果目标机器具有多个处理器,线程可以映射到独立的处理器。运行该程序将导致创建执行程序的并行化部分的两个线程。
控制 -xprofile=collect 命令在其中存储执行频率数据的文件的名称。
控制 -xprofile=collect 命令在其中放置执行频率数据文件的目录。
控制每个辅助线程的任务结束状态,可设置为 spin ns 或 sleep n ms。缺省值为 sleep。有关详细信息,请参见《OpenMP API 用户指南》。
cc 通常在目录 /tmp 中创建临时文件。可以通过将环境变量 TMPDIR 设置为您选定的目录,来指定其他目录。但是,如果 TMPDIR 不是有效目录,cc 将使用 /tmp。-xtemp 选项优先于 TMPDIR 环境变量。
如果您使用 Bourne shell,请键入:
$ TMPDIR=dir; export TMPDIR |
如果您使用 C shell,请键入:
% setenv TMPDIR dir |
#include <stdio.h> |
尖括号 (<>) 导致预处理程序在系统上头文件的标准位置搜索头文件,此位置通常是 /usr/include 目录。
对于您已存储在您自己的目录中的头文件,格式不同:
#include "header.h" |
对于 #include "foo.h" 形式的语句(其中使用了引号),编译器按以下顺序搜索 include 文件:
当前目录(即放置“包含”文件的目录)
以 -I 选项命名的目录(如果有)
/usr/include 目录
如果头文件所在的目录与包含该头文件的源文件所在的目录不同,请指定使用 cc 及 -I 选项存储头文件时所用目录的路径。例如,假设在源文件 mycode.c 中已包含 stdio.h 和 header.h:
#include <stdio.h> #include "header.h" |
进一步假设 header.h 存储在目录 ../defs 中。命令:
% cc– I../defs mycode.c |
指示预处理程序首先在包含 mycode.c 的目录中搜索 header.h,然后在目录 ../defs 中搜索,最后在标准位置搜索。它还指示预处理程序首先在 ../defs 中搜索 stdio.h,然后在标准位置搜索。不同之处在于:仅对于其名称用引号括起的头文件,才查找当前目录。
您可以在 cc 命令行上多次指定 -I 选项。预处理程序按指定目录出现的顺序查找它们。您可以在同一命令行上对 cc 指定多个选项:
% cc– o prog– I../defs mycode.c |
新的 -I- 选项提供对缺省搜索规则的更多控制。只有命令行上的第一个 -I- 选项的作用如本节所述。当命令行上出现 -I- 时:
对于 #include "foo.h" 形式的 include 文件,按以下顺序搜索目录:
1. 使用 -I 选项指定的目录(在 -I- 前后)。
2. 编译器提供的 C++ 头文件、ANSI C 头文件和专用文件的目录。
3. /usr/include 目录。
对于 #include <foo.h> 形式的 include 文件,按以下顺序搜索目录:
1. 使用 -I 选项指定的目录(在 -I- 后面)。
2. 编译器提供的 C++ 头文件、ANSI C 头文件和专用文件的目录。
3. /usr/include 目录。
下例显示在编译 prog.c 时使用 -I- 的结果。
prog.c #include "a.h" #include <b.h> #include "c.h" c.h #ifndef _C_H_1 #define _C_H_1 int c1; #endif int/a.h #ifndef _A_H #define _A_H #include "c.h" int a; #endif int/b.h #ifndef _B_H #define _B_H #include <c.h> int b; #endif int/c.h #ifndef _C_H_2 #define _C_H_2 int c2; #endif |
以下命令显示了在当前目录(包含文件的目录)中搜索 #include "foo.h" 形式的包含语句的缺省行为。当处理 inc/a.h 中的 #include "c.h" 语句时,预处理程序包含 inc 子目录中的 c.h 头文件。当处理 prog.c 中的 #include "c.h" 语句时,预处理程序包含具有 prog.c 的目录中的 c.h 文件。请注意,-H 选项指示编译器输出所包含文件的路径。
example% cc -c -Iinc -H prog.c inc/a.h inc/c.h inc/b.h inc/c.h c.h |
以下命令显示了 -I- 选项的效果。当预处理程序处理 #include "foo.h" 形式的语句时,它并不首先在包含目录中查找,而是按照通过 -I 选项指定的目录在命令行上的显示顺序搜索这些目录。处理 inc/a.h 中的 #include "c.h" 语句时,预处理程序包含 ./c.h 头文件,而不是 inc/c.h 头文件。
example% cc -c -I. -I- -Iinc -H prog.c inc/a.h ./c.h inc/b.h inc/c.h ./c.h |
任何时候都不要将编译器安装区域 /usr/include、/lib 或 /usr/lib 指定为搜索目录。
有关更多信息,请参见B.2.37 -I[-| dir]。