Oracle Solaris Studio 12.2:C 用户指南

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

本章讨论特定于 C 编译器方面的内容。该信息已编入语言扩展和环境。

C 编译器与新的 ISO C 标准 ISO/IEC 9899-1999 中介绍的某些 C 语言功能兼容。如果您希望编译与以前的 C 标准 ISO/IEC 9889-1990 标准(及修正案 1)兼容的代码,请使用 -xc99=none,这样编译器将忽略 ISO/IEC 9899-1999 标准的增强功能。

2.1 常量

本节包含与特定于 Solaris Studio C 编译器的常量有关的信息。

2.1.1 整型常量

十进制、八进制和十六进制的整型常量可加后缀以指示类型,如下表所示。

表 2–1 数据类型后缀

后缀  

类型  

uU

unsigned

lL

long

llLL

long long(在 -xc99=none-Xc 模式下不可用)

luLULulUuluLUlUL

unsigned long

lluLLULLullUullULLuLLUll

unsigned long long(在 -xc99=none-Xc 模式下不可用)

如果设置 -xc99=all,编译器将根据常量大小,使用以下列表中可以表示该值的第一项:

如果值超过 long long int 可表示的最大值,编译器会发出警告。

如果设置 -xc99=none,则为无后缀常量指定类型时,编译器将根据常量大小,使用以下列表中可以表示该值的第一项:

2.1.2 字符常量

一个多字符常量,它不是具有从每个字符的数值派生的值的转义序列。例如,常量 '123' 的值为:

0

'3'

'2'

'1'

0x333231

使用 -Xs 选项并且在 C 的其他非 ISO 版本中,该值为:

0

'1'

'2'

'3'

或者 0x313233

2.2 链接程序作用域说明符

使用以下声明说明符有助于隐藏外部符号的声明和定义。通过使用这些说明符,您不必再使用链接程序作用域的 mapfile。此外,还可以通过在命令行中指定 -xldscope 来控制变量作用域的缺省设置。有关更多信息,请参见B.2.101 -xldscope={v}

表 2–2 声明说明符

值  

含义  

__global

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

__symbolic

该符号具有符号链接程序作用域,该作用域的限制比全局链接程序作用域的限制更多。从所链接的动态模块内部对符号的所有引用都绑定到在模块内部定义的符号上。在模块外部,符号也显示为全局符号。该链接程序作用域对应于链接程序选项 -Bsymbolic。有关链接程序的更多信息,请参见 ld(1)。

__hidden

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

对象或函数可以用限制较多的说明符重新声明,但不能用限制较少的说明符重新声明。符号定义后,不可以用不同的说明符声明符号。

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

2.3 线程局部存储说明符

通过声明线程局部变量,可以利用线程局部存储。线程局部变量声明由一个标准变量声明外加变量说明符 __thread 组成。有关更多信息,请参见B.2.151 -xthreadvar[= o]

您必须在所编译的源文件中线程变量的第一个声明中包含 __thread 说明符。

在具有静态存储持续时间的对象的声明中,只能使用 __thread 说明符。您可以如初始化任何其他静态存储持续时间的对象一样静态地初始化线程变量。

使用 __thread 说明符声明的变量与不使用 __thread 说明符声明的变量具有相同的链接程序绑定。这包括临时定义,如无初始化函数的声明。

线程变量的地址不是常量。因此,线程变量的地址操作符 (&) 在运行时求值,并返回当前线程的线程变量的地址。结果,静态存储持续时间的对象被动态地初始化为线程变量的地址。

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

2.4 浮点,非标准模式

缺省情况下,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 被简单地设置为零。

2.5 作为值的标签

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) 语句的另一个应用是作为线程代码的解释程序。解释程序函数内部的标签地址可以存储在线程代码中以便快速分发。

2.6 long long 数据类型

使用 -xc99=none 进行编译时,Solaris Studio C 编译器包含数据类型 long longunsigned long long,它们与数据类型 long 类似。long long 数据类型存储 64 位信息;long 使用 -m32 编译时存储 32 信息。long 数据类型使用 -m64 编译时存储 64 位信息。long long-Xc 模式下不可用(发出警告)。

2.6.1 输出 long long 数据类型

输出或扫描 long long 数据类型,请在转换说明符前面加字母 ll。例如,要以带符号十进制格式输出 llvarlong long 数据类型的变量),请使用:


printf("%lld\n", llvar);

2.6.2 常见算术转换

某些二元运算符对其操作数的类型进行转换以便两个操作数具有相同的类型,该类型也是结果的类型。下面这些转换称为常见算术转换:

2.7 switch 语句中的 case 范围

在标准 C 中,switch 语句中的 case 标签只能有一个关联值。Solaris Studio C 允许使用某些编译器中使用的扩展(称为 case 范围)。

case 范围指定要与单个 case 标签关联的值范围。case 范围语法为:

case low ... high :

case 范围的行为就好像为从 lowhigh(含)的给定范围内的每个值指定了 case 标签。(如果 lowhigh 相等,则 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 标签的现有要求以外的错误情形:

请注意,如果 case 范围的一个端点是数值,则在省略号 (...) 两侧留空格以避免其中一个点被视为小数点。

示例:


 case 0...4;   // error
 case 5 ... 9; // ok

2.8 断言

以下形式的一行内容:


#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)

其结果为真。

2.9 支持的属性

为便于兼容性,编译器实现以下属性 (__attribute__ ((keyword ))):

2.10 警告和错误

#error#warning 处理器指令可用于生成编译时诊断。

#error token-string

发送错误诊断 token-string 并终止编译操作

#warning token-string

发送警告诊断 token-string 并继续执行编译操作。

2.11 Pragma

以下形式的预处理行:


#pragma pp-tokens

指定实现定义的操作。

编译系统可识别以下 #pragma。编译器忽略未识别的 pragma。如果使用 -v 选项,将针对无法识别的 pragma 发出警告。

2.11.1 align

#pragma align integer (variable[, variable] )

align pragma 会使所有提及的变量内存与整数字节对齐,从而覆盖缺省值。请遵循以下限制:


#pragma align 64 (aninteger, astring, astruct)
int aninteger;
static char astring[256];
struct astruct{int a; char *b;};

2.11.2 c99

#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")

2.11.3 does_not_read_global_data

#pragma does_not_read_global_data ( funcname [, funcname])

该 pragma 断言指定列表中的例程不直接或间接读取全局数据。允许对调用这些例程的代码进行更好的优化。具体来讲,赋值语句或存储可以围绕这样的调用移动。

必须在该 pragma 之前使用原型或空参数列表声明指定的函数。如果全局访问的断言不为真,那么程序的行为就是未定义的。

2.11.4 does_not_return

#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)

2.11.5 does_not_write_global_data

#pragma does_not_write_global_data (funcname [, funcname ])

该 pragma 断言指定列表的例程不直接或间接写全局数据。允许对调用这些例程的代码进行更好的优化。具体来讲,赋值语句或存储可以围绕这样的调用移动。

必须在该 pragma 之前使用原型或空参数列表声明指定的函数。如果全局访问的断言不为真,那么程序的行为就是未定义的。

2.11.6 error_messages

#pragma error_messages (on|off| default, tag… tag)

错误消息 pragma 提供源程序内部对 C 编译器和 lint 发出的消息的控制。对于 C 编译器,pragma 只对警告消息有效。C 编译器的 -w 选项通过禁止显示所有警告消息来覆盖该 pragma。

2.11.7 fini

#pragma fini (f1[, f2…,fn]

使实现在调用 main() 例程之后调用函数 f1fn(完成函数)。此类函数的类型应为 void,并且不接受任何参数,当程序正常终止或所含共享对象从内存中删除时会调用这些函数。和“初始化函数”一样,完成函数按链接编辑器的处理顺序执行。

如果完成函数影响全局程序的状态,则应当小心操作。例如,除非接口明确声明您使用系统库完成函数时会发生什么情况,否则您应捕获和恢复所有全局状态信息,如系统库完成函数可能更改的 errno 的值。

此类函数每出现在 #pragma fini 指令中一次,就会被调用一次。

2.11.8 hdrstop

#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。

2.11.9 ident

#pragma ident string

string 放在可执行文件的 .comment 部分。

2.11.10 init

#pragma init (f1[, f2…,fn] )

使实现在调用 main() 之前调用函数 f1fn(初始化函数)。此类函数的类型应为 void,并且不接受任何参数,在开始执行时构造程序的内存映像时会调用这些函数。如果初始化函数在共享对象中,则在执行将共享对象放入内存的操作(无论是程序启动,还是某些动态装入操作,如 dlopen())时执行它们。调用初始化函数的唯一顺序是链接编辑器处理它们的顺序,静态和动态均可。

如果初始化函数影响全局程序的状态,则应当小心操作。例如,除非接口明确声明您使用系统库初始化函数时会发生什么情况,否则您应捕获和恢复所有全局状态信息,如系统库初始化函数可能更改的 errno 的值。

此类函数每出现在 #pragma init 指令中一次,就会被调用一次。

2.11.11 inline

#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

2.11.12 int_to_unsigned

#pragma int_to_unsigned (funcname )

对于返回类型 unsigned 的函数,在 -Xt-Xs 模式下,将函数返回值的类型更改为 int

2.11.13 MP serial_loop

(SPARC) #pragma MP serial_loop


注 –

Sun 特定的旧 MP pragma 已过时,并且不再受支持。但是,编译器改为支持 OpenMP 3.0 规范指定的 API。有关标准的指令的迁移信息,请参见《OpenMP API 用户指南》。


有关详细信息,请参阅3.8.3.1 串行 Pragma

2.11.14 MP serial_loop_nested

(SPARC) #pragma MP serial_loop_nested


注 –

Sun 特定的旧 MP pragma 已过时,并且不再受支持。但是,编译器改为支持 OpenMP 3.0 规范指定的 API。有关标准的指令的迁移信息,请参见《Solaris Studio OpenMP API 用户指南》。


有关详细信息,请参阅3.8.3.1 串行 Pragma

2.11.15 MP taskloop

(SPARC) #pragma MP taskloop


注 –

Sun 特定的旧 MP pragma 已过时,并且不再受支持。但是,编译器改为支持 OpenMP 3.0 规范指定的 API。有关标准的指令的迁移信息,请参见《OpenMP API 用户指南》。


有关详细信息,请参阅3.8.3.2 并行 Pragma

2.11.16 nomemorydepend

(SPARC) #pragma nomemorydepend

该 pragma 指定,对于某个循环的任何迭代,不存在内存依赖性。也就是说,在某个循环的任何迭代内部,不存在对相同内存的引用。该 pragma 将允许编译器(流水线化程序)在某个循环的单次迭代内更有效地调度指令。如果某个循环的任何迭代内部存在任何内存依赖性,则程序的执行结果未定义。编译器在优化级别 3 或更高级别上使用此信息。

此 pragma 的作用域从它自身开始,在以下任何一种情况最先出现时结束: 下一块的开始部分、当前块内部的下一个 for 循环、当前块的结尾。该 pragma 应用于 pragma 作用域结束前的下一个 for 循环。

2.11.17 no_side_effect

#pragma no_side_effect(funcname[, funcname…] )

funcname 指定当前转换单元内部某个函数的名称。必须在该 pragma 之前使用原型或空参数列表声明的函数。必须在函数的定义之前指定 pragma。对于命名的函数 funcname,该 pragma 声明函数无任何副作用。这意味着,funcname 返回一个只依赖传递参数的结果值。另外,funcname 和任何已调用的子函数具有以下特性:

使用函数进行优化时,编译器使用此信息。如果函数具有副作用,执行调用该函数的程序的结果是未定义的。编译器在优化级别 3 或更高级别上使用此信息。

2.11.18 opt

#pragma opt level (funcname[, funcname])

funcname 指定当前转换单元内部定义的某个函数的名称。level 值指定用于所指定函数的优化级别。可以指定优化级别 0、1、2、3、4、5。可以通过将 level 设置为 0 来关闭优化。必须在该 pragma 之前使用原型或空参数列表声明函数。pragma 则必须对要优化的函数进行定义。

pragma 中所列任何函数的优化级别都降为 -xmaxopt 值。-xmaxopt=off 时,忽略 pragma。

2.11.19 pack

#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 上使用 -m32 时为 4,在 SPARC 上使用 -m32 时为 8,但使用 -m64 时为 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.116 -xmemalign=ab


2.11.20 pipeloop

#pragma pipeloop(n)

对于参数 n,该 pragma 接受正整数常量值或 0。该 pragma 指定,循环可流水线化,并且循环携带的依赖性的最小依赖距离为 n。如果该距离为 0,则循环实际上是 Fortran 风格的 doall 循环,并且应在目标处理程序上流水线化。如果该距离大于 0,则编译器(流水线化程序)将只尝试流水线化 n 次连续迭代。编译器在优化级别 3 或更高级别上使用此信息。

此 pragma 的作用域从它自身开始,在以下任何一种情况最先出现时结束: 下一块的开始部分、当前块内部的下一个 for 循环、当前块的结尾。该 pragma 应用于 pragma 作用域结束前的下一个 for 循环。

2.11.21 rarely_called

#pragma rarely_called(funcname[, funcname] )

该 pragma 提示编译器,指定的函数很少被调用。这样,编译器可以在此类例程的调用点执行配置文件反馈样式的优化,而没有配置文件收集阶段的开销。因为该 pragma 只是建议,所以编译器不执行基于该 pragma 的任何优化。

必须在该 pragma 之前使用原型或空参数列表声明指定的函数。以下是 #pragma rarely_called 的示例:


extern void error (char *message);
#pragma rarely_called(error)

2.11.22 redefine_extname

#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__ */

2.11.23 returns_new_memory

#pragma returns_new_memory (funcname[, funcname])

该 pragma 断言指定函数的返回值在调用点上不使用任何内存作为别名。实际上,该调用返回一个新存储单元。该信息使优化器更好地跟踪指针值并澄清存储单元。这将导致循环的调度、流水线化和并行化的改进。然而,如果断言为假,则程序的行为未定义。

只有在使用原型或空参数列表声明指定的函数之后才允许使用该 pragma,如以下示例所示:


void *malloc(unsigned);
#pragma returns_new_memory(malloc)

2.11.24 unknown_control_flow

#pragma unknown_control_flow (funcname[, funcname])

为了描述改变函数调用方流程图的过程,C 编译器提供了 #pragma unknown_control_flow 指令。通常,该指令带有 setjmp() 等函数的声明。在 Solaris 系统上,include 文件 <setjmp.h> 包含:


extern int setjmp();
#pragma unknown_control_flow(setjmp)

同样,必须声明具有 setjmp() 类似属性的其他函数。

原则上,识别该属性的优化器可在控制流程图中插入相应的界限,从而在调用 setjmp() 的函数中安全地处理函数调用,同时保持优化流程图的未受影响部分的代码的能力。

必须在该 pragma 之前使用原型或空参数列表声明指定的函数。

2.11.25 unroll

#pragma unroll (unroll_factor)

对于参数 unroll_factor,该 pragma 接受正整数常量值。当解开因子不为 1 时,该指令作为对编译器的建议,指出指定的循环应由给定的因子解开。如果可能,编译器将使用该解开因子。如果解开因子值为 1,该指令作为一个命令,向编译器指出不解开该循环。编译器在优化级别 3 或更高级别上使用此信息。

此 pragma 的作用域从它自身开始,在以下任何一种情况最先出现时结束: 下一块的开始部分、当前块内部的下一个 for 循环、当前块的结尾。该 pragma 应用于 pragma 作用域结束前的下一个 for 循环。

2.11.26 warn_missing_parameter_info

#pragma [no_]warn_missing_parameter_info

如果指定 #pragma warn_missing_parameter_info,编译器将针对函数声明不包含任何参数类型信息的函数调用发出一条警告。请看以下示例:


example% 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 是有效的。

2.11.27 weak

#pragma weak symbol1 [= symbol2]

定义弱全局符号。该 pragma 主要在源文件中用于生成库。如果链接程序无法解析弱符号,它并不生成错误消息。


#pragma weak symbol

symbol 定义为弱符号。如果链接程序找不到 symbol 的定义,它不会生成错误消息。


#pragma weak symbol1 = symbol2

symbol1 定义为弱符号,它是符号 symbol2 的别名。只能在已定义 symbol2(可以在源文件中定义,也可以在其中一个包含的头文件中定义)的同一转换单元中使用这种形式的 pragma。否则,将导致编译错误。

如果程序调用但未定义 symbol1,并且 symbol1 在所链接的库中是弱符号,则链接程序使用该库中的定义。但是,如果程序定义自己的 symbol1 版本,则使用该程序的定义,而不使用库中 symbol1 的弱全局定义。如果程序直接调用 symbol2,则使用库中的定义;symbol2 的重复定义会导致出现错误。

2.12 预定义的名称

cc(1) 手册页中给出了最新的预定义列表。

下面的标识符被预定义为类似于对象的宏:

表 2–3 预定义标识符 __STDC__

扩展到:  

编译时使用:  

1

-Xc

0

-Xa,-Xt

未定义

-Xs

如果 __STDC__ 未定义 (#undef __STDC__),编译器将发出警告。__STDC__ 未在 -Xs 模式下定义。

2.13 保留 errno 的值

使用 -fast,编译器可以用不设置 errno 变量的等效优化代码自由替换对浮点函数的调用。而且,-fast 还会定义宏 __MATHERR_ERRNO_DONTCARE,使编译器无需确保 errno 的有效性。因此,在浮点函数调用后依赖于 errno 值的用户代码可能生成不一致的结果。

解决此问题的一种方法是避免用 -fast 编译此类代码。但是,如果需要 -fast 优化并且代码依赖于在浮点库调用后正确设置的 errno 值,应使用以下选项进行编译

-xbuiltin=none -U__MATHERR_ERRNO_DONTCARE -xnolibmopt -xnolibmil

然后,在命令行上使用 -fast,以禁止编译器优化此类库调用,并确保正确处理 errno

2.14 扩展

C 编译器针对 C 语言实现了许多扩展。

2.14.1 _Restrict 关键字

C 编译器支持 _Restrict 关键字,该关键字与 C99 标准中的 restrict 关键字等效。_Restrict 关键字可与 -xc99=none-xc99=all 一起使用,而 restrict 关键字只能与 -xc99=all 一起使用。

有关支持的 C99 特性的更多信息,请参见表 C–6

2.14.2 _ _asm 关键字

__asm 关键字(注意开头的两个下划线)asm 关键字的同义字。如果您使用 asm 而不是 __asm,并且在 -Xc 模式下编译,则编译器会发出警告。如果您在 – Xc 模式下使用 _ _asm,则编译器不会发出警告。_ _asm 语句采用以下形式:


__asm("string");

其中 string 是有效的汇编语言语句。

该语句将给定的汇编程序文本直接发送到汇编文件。在文件作用域(而不是函数作用域)声明的基本 asm 语句称为全局 asm 语句。其他编译器将其称为顶级 asm 语句。

全局 asm 语句是按指定的顺序发出的。也就是说,它们保留相对于彼此的顺序,并保持相对于前后函数的位置。

在较高优化级别中,编译器可能会删除认为不被引用的函数。由于编译器不知道从全局 asm 中引用了哪些函数,因此可能会无意中删除这些函数。

请注意,那些提供模板及操作数规范的扩展 asm 语句不允许作为全局语句。__asm__asm__asm 关键字的同义字,可以互换使用。

2.14.3 __inline__inline__

__inline__inline__inline 关键字的同义字(C 标准,第 6.4.1 节)

2.14.4 __builtin_constant_p()

__builtin_constant_p 是编译器内置函数。它接受一个数值参数,如果已知参数是一个编译时常量,则返回 1。返回值 0 意味着编译器无法确定参数是否是编译时常量。此内置函数的典型用法是在宏中用于手动编译时优化。

2.14.5 __FUNCTION____PRETTY_FUNCTION__

__FUNCTION____PRETTY_FUNCTION__ 是预定义标识符,这些标识符包含词法上封闭的函数的名称。它们在功能上等效于 c99 预定义标识符 __func__。在 Solaris 平台上,__FUNCTION____PRETTY_FUNCTION__-Xs-Xc 模式下不可用。

2.15 环境变量

本节列出用于控制编译和运行环境的环境变量。

2.15.1 OMP_DYNAMIC

启用或禁用线程数的动态调整。

2.15.2 OMP_NESTED

启用或禁用嵌套并行操作。

2.15.3 OMP_NUM_THREADS

设置执行过程中要使用的线程数。

2.15.4 OMP_SCHEDULE

设置运行时调度类型和块大小。

2.15.5 PARALLEL

指定可供程序进行多处理器执行的处理器数。如果目标机器具有多个处理器,线程可以映射到独立的处理器。运行该程序将导致创建执行程序的并行化部分的两个线程。

2.15.6 SUN_PROFDATA

控制 -xprofile=collect 命令在其中存储执行频率数据的文件的名称。

2.15.7 SUN_PROFDATA_DIR

控制 -xprofile=collect 命令在其中放置执行频率数据文件的目录。

2.15.8 SUNW_MP_THR_IDLE

控制每个辅助线程的任务结束状态,可设置为 spin nssleep n ms。缺省值为 sleep。有关详细信息,请参见《OpenMP API 用户指南》。

2.15.9 TMPDIR

cc 通常在目录 /tmp 中创建临时文件。可以通过将环境变量 TMPDIR 设置为您选定的目录,来指定其他目录。但是,如果 TMPDIR 不是有效目录,cc 将使用 /tmp-xtemp 选项优先于 TMPDIR 环境变量。

如果您使用 Bourne shell,请键入:


$ TMPDIR=dir; export TMPDIR

如果您使用 C shell,请键入:


% setenv TMPDIR dir

2.16 如何指定 include 文件

包含 C 编译系统提供的任何标准头文件,请使用以下格式:


#include <stdio.h>

尖括号 (<>) 导致预处理程序在系统上头文件的标准位置搜索头文件,此位置通常是 /usr/include 目录。

对于您已存储在您自己的目录中的头文件,格式不同:


#include "header.h"

对于 #include "foo.h" 形式的语句(其中使用了引号),编译器按以下顺序搜索 include 文件:

  1. 当前目录(即放置“包含”文件的目录)

  2. -I 选项命名的目录(如果有)

  3. /usr/include 目录

如果头文件所在的目录与包含该头文件的源文件所在的目录不同,请指定使用 cc-I 选项存储头文件时所用目录的路径。例如,假设在源文件 mycode.c 中已包含 stdio.hheader.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

2.16.1 使用 -I- 选项更改搜索算法

新的 -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

2.16.1.1 警告

任何时候都不要将编译器安装区域 /usr/include/lib/usr/lib 指定为搜索目录。

有关更多信息,请参见B.2.37 -I[-| dir]

2.17 在独立式环境中编译

Solaris Studio C 支持编译能与标准 C 库链接并在包含标准 C 库和其他运行时支持库的运行时环境中执行的程序。C 标准中为此类环境指明了一个托管环境。而为未提供标准库函数的环境指明了一个独立式环境。

针对于独立式环境,一般情况下 C 编译器不支持编译,因为某些可能从编译代码中调用的运行时支持函数一般仅在标准 C 库中可用。问题是:编译器转换的源代码可能将调用引入至不包含函数调用的源代码结构中的运行时支持函数中,并且这些函数一般在独立式的环境中不可用。请看以下示例:


% cat -n lldiv.c
	 1	void
	 2	lldiv(
	 3	    long long *x,
	 4	    long long *y,
	 5	    long long *z)
	 6	{
	 7	    *z = *x / *y ;
	 8	}
% cc -c -m32 lldiv.c
% nm lldiv.o | grep " U "
 0x00000000 U __div64
% cc -c -m64 lldiv.c
% nm lldiv.o | grep " U "

在该示例中,使用 -m32 选项编译源文件 lldiv.c 使其在 32 位平台上运行时,对第 7 行中声明的转换将导致外部引用名为 __div64 的运行时支持函数,并且此函数是 32 位版本的标准 C 库中唯一可使用的函数。

使用 -m64 选项编译同一源文件使其在 64 位平台上运行,编译器将使用目标计算机中的 64 位算法指令集,但其中不包括 64 位版本的标准 C 库中运行时支持函数所需要的指令。

尽管在一般情况下不支持使用 C 编译器将独立式环境视为目标,但在遵从注意事项的情况下可使用此编译器在特定的独立式环境(即 Solaris 内核和设备驱动程序)中编译代码。

必须写入在 Solaris 内核中运行的代码(包括设备驱动程序),这样外部函数调用才能引用那些只在内核中可用的函数。要使以上情况成为可能,建议遵从以下准则:

  1. 对于只在用户模式下运行的库,不要包含头文件。

  2. 除非确定内核中存在与标准 C 库或其他用户模式库中相同的函数,否则不要调用这些函数。

  3. 不要使用浮点类型或 C99 复合类型。

  4. 不要使用与运行时支持库相关联的编译选项,例如 -xprofile-xopenmp

    cc(1) 手册页的“文件”一节中介绍了与特定编译器选项关联的可重定位对象文件。相关选项的说明下面介绍了与 C 编译器选项关联的运行时支持库。

如前所述,在源代码转换后,编译器可能会生成对运行时支持函数的调用。对于 Solaris 内核,在特定情况下可能被调用的运行时支持函数集要小于一般情况下调用的相应函数集,因为内核不使用浮点/复合类型、数学库函数或与运行时支持库相关联的编译器选项。

下表列出了 C 编译器转换源代码后,可能被调入代码中的运行时支持函数,这些代码是为了能够在 Solaris 内核中运行而编译的。该表列出了其上的源代码转换生成了调用的平台、被调用函数的名称,以及导致生成函数调用的原结构或编译器功能。仅列出了 64 位平台,因为支持 C 编译器的所有版本的 Solaris 均在 64 位内核中运行。

针对于 32 位指令集进行编译时,可能会因指令集的特定限制而调用特定于其他计算机的支持函数。

功能 

64 位平台 

引用自 

__align_cpy_n

SPARC 

返回大结构;n 取值为1、2、4、8 或 16

_memcpy

x86 

返回大结构 

_memcpy

x86 和 SPARC 

矢量化 

_memmove

x86 和 SPARC 

矢量化 

_memset

x86 和 SPARC 

矢量化 

注意:一些内核的版本不提供 _memmove()_memcpy()_memset(),但提供与用户模式例程相似的内核模式例程,例如 memmove()、memcpy()memset()

可在《编写设备驱动程序》指南和《SPARC 遵从性定义》(版本 2.4)中找到其他信息。