2004 年 5 月 27 日更新 |
Sun[tm] Studio 9:运行时库自述文件C++ 常见问题 |
目录
A. 此版本的新问题
- 如何可靠地确定每个版本中的 C++ 编译器?
- 我最近对我的 Solaris[tm] 操作系统安装了补丁程序,现在我的代码不能编译。出了什么问题?
- 为什么当我没在我的程序中编译或包含 foo.cc 时却会收到有关 foo.cc 的错误消息和警告消息?
- 为什么当我编译从 -P 预处理选项生成的 foo.i 文件时会收到“duplicate definition”的错误消息?
- 为何在编译时我看不到改善,即便我已开始使用 C++ 编译器中的预编译头工具也是如此?
- 为什么编译大文件所花时间会比小文件多很多?
- 为什么我将 SPARC V9 存档库链接到动态库时出现错误消息?它在 Sun Studio 8 中工作。
- 出现以下消息的原因是:"SunWS_cache:Error:Lock attempt failed for SunWS_cache"?
- 比 gcc 或 KAI 流慢的标准库流。这对性能产生了影响。有没有解决办法?
- 为什么我从链接程序那里收到下列警告消息:"ld:warning:symbol 'clog' has differing types"?
- 为什么当我用 -xarch=v8plus 或 -xarch=v8plusa 编译我的多线程程序(使用 STLport)崩溃?
- 为什么编译器现在认为对 abs() 的调用具有二义性?
- 为什么临时对象会被破坏?
- 如何可靠地确定每个版本中的 C++ 编译器?
每个编译器都对用以标识自己的宏进行预定义。编译器的供应商倾向于保持这些预定义宏在各版本之间的稳定性,我们特别将它们记录为稳定的公用接口。
查找您有哪些编译器的一种好办法是编写一个小程序,用以测试预定义宏并输出适合您使用目的的字符串。也可以编写一个伪程序并用 -E(或其他编译器的等同命令)来编译它。
请参见《C++ 用户指南》索引中的“宏”,获取预定义 C++ 编译器宏的列表。特别是,__SUNPRO_CC 的值会是三位数十六进制数字。第一位是主要版本。第二位是小版本。第三位是微版本。例如,C++ 5.6 是 0x560。
这里有一些有趣的预定义宏:
#ifdef __sun Sun compiler #endif #ifdef Sun C compiler /* __SUNPRO_C value is the version number */ #endif #ifdef __SUNPRO_CC Sun C++ compiler /* __SUNPRO_CC value is the version number */ #endif #ifdef __sparc generate code for SPARC architecture #endif #ifdef __sparcv9 generate code for SPARC architecture #endif #ifdef generate code for Intel architecture #endif- 我最近对我的 Solaris 操作系统安装了补丁程序,现在我的代码不能编译。出了什么问题?
在新近的 Solaris 版本中可用的新的数学函数可能会导致以前的有效代码变得无效。
<math.h> 中的函数以前只有用于 double 类型的版本。新的 Solaris 头和库还有用于 float 和 long double 类型的超载。为了避免二义性调用,当用整数参数调用这些函数时,可以添加显式计算。例如:
#include <math.h> extern int x; double z1 = sin(x); // now ambiguous double z2 = sin( (double)x ); // OK float z3 = sin( (float)x ); // OK long double z4 = sin( (long double)x ); // OK下面所列得到 Solaris 补丁程序提供完整的 ANSI C++ <cmath> 和 <math.h> 库支持,如 Solaris 8 和 9 的 libm 补丁程序中实现的那样。
- Solaris 9 sparc (PatchId 111722-04)
- Solaris 9 i386 (PatchId 111728-03)
- Solaris 8 sparc (PatchId 111721-04)
- Solaris 8 i386 (PatchId 112757-01)
- 为什么当我没在我的程序中编译或包含 foo.cc 时却会收到有关 foo.cc 的错误消息和警告消息?
如果头文件 foo.h 中有模板声明,缺省情况下编译器就会搜索具有 C++ 文件扩展名(foo.c、foo.cc、foo.C、foo.cpp、foo.c++)的文件 foo ,并在找到后自动包括它。有关详细信息,请参阅《C++ 用户指南》中题为 “模板定义搜索”的节。
如果您有一个不想用这种方法处理的文件 foo.cc,您有两个选择:
- 更改 .h 或.cc 文件的名称来消除名称匹配。
通过指定 -template=no%extdef 选项禁用模板定义文件的自动搜索。但是,该选项将禁用独立模板定义的所有搜索。C++ 标准库实施依赖于查找独立定义的编译器。必须在您的代码中明确包含所有模板定义,这样您就不能使用独立定义模型了。
模板定义模型的进一步讨论,请参见《C++ 用户指南》5.2.1 和 5.2.2 节,关于定义独立和定义包含模型的说明指针,请参见《C++ 用户指南》。
- 为什么当我编译从 -P 预处理选项生成的 foo.i 文件时会收到“duplicate definition”的错误消息?
缺省情况下,编译器允许模板定义被其他声明分享。模板定义模型的进一步讨论,请参见《C++ 用户指南》5.2.1 和 5.2.2 节,关于定义独立和定义包含模型的说明指针,请参见《C++ 用户指南》。
当编译器在 .h 或 .i 文件中看到没有所需模板定义的声明时,它会寻找模板定义文件。与 .h 或 .i 文件同名但扩展名为为 c、cc、C、c++ 或 cpp 的文件会被认为有模板定义。如果存在这样的文件,它会自动被包括,如 C++ 手册中所描述。
假定文件 foo.cc用 -P 进行过编译,从而生成 foo.i,并有所需模板的声明,但没有定义。编译器会查找定义文件,最后找到 foo.cc。foo.cc 文件会自动被包括,这可能导致出现重复的定义。
可以通过指定 -template=no%extdef 选项来关闭模板定义的编译器搜索功能。但是,该选项将禁用独立模板定义的所有搜索。C++ 标准库实施依赖于查找独立定义的编译器。
在这种情况下,如果您要编译 .i 文件,只需重命名该文件,给它一个唯一的名称即可。然后,您无须禁用独立模板编译。例如:
CC -P foo.cc mv foo.i foo_prep.i CC -c foo_prep.i注意,如果您使用 -E 选项生成预处理文件,它将无法工作。 The -E 选项记录所包括文件的文件名,所以 .cc 文件仍可以被自动发现。
- 为何在编译时我看不到改善,即便我已开始使用 C++ 编译器中的预编译头工具也是如此?
使用预编译头并不能保证快速编译速度。预编译头会增加一些开销,当您直接编译文件时这种开销是不存在的。要得到优良的性能,预编译头必须有一些可以消除预编译的冗余度。
例如,很可能从预编译中得到益处的程序是包括许多系统头、iostream、STL 头和项目头的程序。这些文件中包含按条件编译的代码。有些头会被多次包括,并且编译器只有在确定与冗余包括无关时才必须扫描整个文件。系统头通常有数百个要扩展的宏。
使用预编译头意味着打开一个文件而不是许多文件。无关的多重包括会被消除,比如内容和附加空白空间。头中的宏是预扩展的。通常,这种节省对于编译运行时间的缩短有重要意义。
- 为什么编译大文件所花时间会比小文件多很多?
文件的大小可能不是问题,下面是三个可能造成延迟的原因。
文件中函数的大小和优化的级别
优化程度高的大文件要花长时间处理,可能需要大量内存。如果代码以扩展方式使用大的宏,则看起来很小的函数在宏扩展以后可能会变得很大。
不带任何优化的编译(无 -xO 或 -O 选项)。如果编译很快完成,问题可能就是文件中有一个或几个很大的函数,需要时间和内存去优化它。
另外,确保用于编译的计算机有大量物理内存来满足编译运行。如果您没有足够的内存,优化状态会不稳定。
内联函数
内联函数(C 和 C++)在编译期间充当宏。当函数调用扩展为内联时,它可能会转为大量代码。然后,编译器处理一个大的函数,而不是两个或更多小函数。
禁用函数内联时,编译的处理速度会变快。当然,生成的代码也许会运行的更慢。
有关详细信息,请参见《C++ 用户指南》中的 -xinline 说明和“使用内联函数”。
C++ 类模板
C++ 模版导致编译器以所调用的模板为基础生成代码。一行源代码可能会需要编译器生成一个或多个模板函数。 不是模板本身极大地降低了编译的速度,而是编译器有太多的代码要处理,这要比从原始源代码那里看到的要多。
例如,如果它不是针对已有了函数的标准库,那么下面的代码行
cout << "value = " << x << endl;将导致编译器生成 241 个函数。
- 为什么我将 SPARC V9 存档库链接到动态库时出现错误消息?它在 Sun Studio 8 中工作。
V9 新的缺省编译器地址代码模型是 -xcode=abs44,它可在以前的 -xcode=abs64 基础局上提高性能。但是,新的代码模型在动态库中不可用。这个问题有两个解决方案。
用 -xcode=pic13 或 -xcode=32 重新编译对象文件。此方法比较可取,而且几乎始终是正确的。
用 -xcode=abs64 重新编译对象文件。此方法会导致动态库不可共享。每个进程都必须在复制到内存中的独立区域时对库进行重写。此方法用于长期在有严格性能限制和系统共享程度低的情况下运行的应用程序。
- 出现以下消息的原因是:"SunWS_cache:Error:Lock attempt failed for SunWS_cache"?
关于模板缓存的“lock attempt failed”错误消息有两个主要导致原因:
有时编译会以某种方式中断或终止,以致于它不释放在缓存中保留的锁定。这种情况可能会在原有的编译器版本中发生。新的版本以及原有编译器的当前补丁程序可确保锁定无论编译器以何种方式退出都能得以释放。可以删除锁定文件,但缓存可能会破坏,这样会导致其他问题。最安全的修复方法是删除整个模板缓存。
模板文件必须可由编译器进程写入。有关详细信息,请参见 umask(1) 手册页。特别是,您必须确保在它的里面创建缓存或文件的进程的 umask 允许其他需要访问同一缓存的进程进行写入。如果目录未安装在 NFS 文件系统上,系统就必须针对可读写功能进行安装。
- 比 gcc 或 KAI 流慢的标准库流。这对性能产生了影响。有没有解决办法?
可以在链接时指定新的 C++ 编译选项 -sync_stdio=no 来解决这个问题,或者在 sync_with_stdio(false) 函数中添加调用并重新编译。
stdlib 2.1.1 的主要性能问题是它在缺省情况下将 C stdio 与 C++ 流同步。每个到达 cout 的输出都会立即刷新。如果您的程序对 cout 进行了大量输出,而对 stdout 没有,则过度的缓冲刷新可能会极大地影响程序的运行时性能。C++ 标准要求这种行为,但不是所有实施都满足这个标准。下面的程序说明了此同步问题:它必须在新行的前面打印“Hello beautiful world”:
#include <iostream> #include <stdio.h> int main() { std::cout << "Hello "; printf("beautiful "); std::cout << "world"; printf("\n"); }如果 cout 和 stdout 独立进行缓冲,输出就可能会混乱。如果不能重新编译可执行文件,请在链接时指定新的 C++ 编译选项 -sync_stdio=no。该选项会导致 sync_with_stdio( ) 在程序初始化时被调用(在任何其他程序输出发生之前)。
如果重新编译,就要在任何程序输出之前,通过指定输出不需要同步,添加对 sync_with_stdio(false) 函数的调用。下面是一个示例调用:
#include <iostream> int main(int argc, char** argv) { std::ios::sync_with_stdio(false); }对sync_with_stdio 的调用应该是您程序中的第一个调用。
有关 -sync_stdio 的详细信息,请参见《C++ 用户指南》或 C++ 手册页 CC(1)。
- 为什么我从链接程序那里收到下列警告消息:"ld:warning:symbol 'clog' has differing types"?
当在同一程序中链接 libm.so.2 和经典的 iostream 库时,关于一对虚弱符号的链接程序警告,表明有不同种类。可以忽略此警告消息。
在 Solaris 10 中的缺省数学库是 libm.so.2,并且它包括 C99 标准所需要的全局名称空间中的复杂日志函数“clog”。如果通过指定 -compat=4 或 -library=iostream 来使用 C++ 经典的 iostreams,将在全局名称空间中得到缓冲标准错误流“clog”。(标准 iostreams 没有此冲突符号。)
我们需要调节头和库以便以静默方式为每个“clog”符号重命名,这样就可以在同一个程序中使用两个。然而,我们必须在每个库中将原始符号拼写保留为微弱符号,所以查找原始数据的二进制文件可以继续链接。
要确保得到 iostream 和数学声明,包括适当的系统头而不是您自己声明这些实体。
- 为什么当我用 -xarch=v8plus 或 -xarch=v8plusa 编译我的多线程程序(使用 STLport)崩溃?
当使用 -xarch=v8plus 或 -xarch=v8plusa 编译时,STLport 最初会阻止正确的多线程操作。这个故障已被修复,但修复包括对 STLport 头中的更改,以及对一些 STLport 对象的更改。使用 STLport 头的早期版本进行编译的代码,在链接到新程序前,需要用 C++ 5.6 或早期版本的补丁程序重新编译。
- 为什么编译器现在认为对 abs() 的调用具有二义性?
26.5 节中的 C++ 标准需要下列 abs 函数超载:
In <stdlib.h> and <cstdlib>
int abs(int); long abs(long);<math.h> and <cmath>
float abs(float); double abs(double); long double abs(long double);直到最近,abs 在 Solaris 上可适用的唯一版本一直都是传统的 int 版本。如果您调用 abs(具有任何数字类型),值将隐含地转换为类型 int,并且 abs 的 int 版本被调用(假定包括 <stdlib.h> 或 <cstdlib>)。
Solaris 头和库的最新更新现在符合有关数学函数的 C++ 标准。
例如,如果您包括 <math.h> 但不包括 <stdlib.h>, 并且调用带整数参数的 abs, 编译器就会选择函数的三个浮点版本。一个整数值可以转换为任一浮点类型,并且两种转换的优先级是一样的。参考:C++ 标准节 13.3.3。函数调用因此不明确。使用符合 C++ 标准的编译器会生成一个含糊的错误消息。
如果调用带有整数参数的 abs 函数,就应包括标准头 <stdlib.h> 或 <cstdlib> 以确保得到有关它的正确声明。如果使用浮点值调用 abs,就还应包括 <math.h> 或 <cmath>。
下面是一个简单的推荐编程练习:如果包括 <math.h> 或 <cmath>,就还要包括 <stdlib.h> 或 <cstdlib>。
类似的考虑适用于其他数学函数,如 cos 或 sqrt。Solaris 头和库遵循 C++ 标准,提供函数的 float、double 和 long double 超载版本。例如,如果调用带有整数值的 sqrt,因为只有一个 sqrt 版本可用,所以是原先编译的代码。如果有三个浮点版本可用,就必须将整数值分配给所需的浮点类型。
double root_2 = sqrt(2); // error double root_2 = sqrt(2.0); // OK double x = sqrt(int_value); // error double x = sqrt(double(int_value)); // OK- 为什么临时对象会被破坏?
编译器有时为了方便会生成临时对象,有时则是因为语言规则需要这样。例如,函数返回的值是一个临时对象,类型转换的结果是一个临时对象。
原始 C++ 规则是临时对象 ("temp"),随时都可以销毁(直到创建它的块结束时)。Sun C++ 编译器在块的结尾(关闭右括号)处销毁临时文件。
在争论几年后,C++ 委员会决定在一个固定的地方销毁临时文件:即创建临时文件的完整表达式的结尾。通常,是指表达式所在语句的结尾。这是 C++ 标准中的规则。
我们发现,许多使用依赖于 Sun C++ 的(也许非故意的)临时文件的程序会存留到块结束时。相应地,我们让编译器保留缺省行为:缺省情况下的临时对象在创建它们的块的结尾被销毁。
如果需要符合标准的行为,即让临时文件在创建它的完全表达式的结尾销毁,请使用编译时选项 -features=tmplife。
您无须在整个程序中一直使用该选项。在模块中创建的临时对象会根据在编译时生效的选项,在该模块中的表达式结尾或块结尾销毁。
B. 版本、补丁程序和支持
- 标准 I/O 流和传统 I/O 流有何区别?能就此问题推荐一些教材吗?
- 如何知道哪些 C++ 编译器版本是兼容的?
- 哪些 RogueWave 库已被“批准”用于 Sun Studio 9?
- 我怎么知道有哪些补丁程序,以及当前的补丁程序解决了哪些问题?
- 我是否需要有 libC.so.5 和 libCrun.so.1 的补丁程序?
- 标准 I/O 流和传统 I/O 流有何区别?能就此问题推荐一些教材吗?
这两个库的设计和实现是完全不同的。简单 I/O 的编程界面是很相似的。对于更复杂的操作,例如编写您自己的流类或操纵器,就很不一样了。
本版本中的传统 iostream 库和 C++ 3.x 以及 4.x 中的版本是兼容的。除了本发行版本中所提供的文档以外,还有一本参考书:
- Steve Teale
C++ IOStreams Handbook
Addison-Wesley 1993标准 iostream 库是由 C++ 标准来描述的。除了本版本中所提供的文档以外,还有两本很好的参考书:
- Nicolai Josuttis
The C++ Standard Library
Addison-Wesley 1999
(关于全部 C++ 标准库的教材)
- Angelika Langer 和 Klaus Kreft
Standard C++ IOStreams and Locales
Addison-Wesley 1999
(只是 iostream 和语言环境的教程。)两个版本的 iostream 实现简单 I/O 的源代码看起来是一样的。为了使转换更容易,我们对标准 iostream 提供了非标准的头文件 <iostream.h>、<fstream.h> 和 <strstream.h>。它们提供了一套和传统 iostream 类似的全局名称空间声明。如果使用这些头,在缺省情况下就会得到标准的 iostream。要获得经典的 iostream,就应该用 -library=iostream 进行编译和链接。
例如,虽然不是所有的编译器都能做到,但使用我们的编译器可以让以 下代码既能用于传统 iostream 也能用于标准 iostream:
#include <iostream.h> class myclass { public: myclass(int i) :k(i) { } friend ostream& operator<<(ostream&, const myclass&); private: int k; }; // user-written output operator ostream& operator<<(ostream& os, const myclass& m) { os << m.k; return os; } int main() { // simple I/O using cout, cin cout << "Enter a number:" << endl; int val; if( !(cin >> val) ) { cout << "Invalid entry, using zero" << endl; val = 0; } // using the user-written output operator myclass m(val); cout << "Value is " << m << endl; }该代码以下列方法中的任何一种,使用 Sun 编译器进行编译和运行:
example% CC -library=iostream example.cc # standard mode with classic iostreams example% CC -library=iostream example.cc # standard mode with classic iostreams example% CC -compat=4 example.cc # C++ 4.2 compatbility mode- 我怎么知道哪些 C++ 编译器版本是兼容的?
首先,我们来定义一下:“向上兼容”是指用较老编译器所编译的对象代码可以用较新编译器的代码来连接,只要最终连接时所用的编译器是整个过程中最新的编译器。
C++ 4.0、4.1 和 4.2 编译器是向上兼容的。(C++ 4.2 手册中有一些编译器版本之间的“名字粉碎”问题。)
兼容模式 (-compat) 下的 C++ 5.0、5.6、5.2、5.3、5.4 和 5.5 编译器与 4.2 编译器是向上兼容的。C++ 4.2 和 5.0 到 5.6 版的实际对象代码是完全兼容的,但较新编译器所发送的调试信息 (stabs) 与较早的调试器是不兼容的。
缺省的标准模式下的 C++ 5.0 到 5.6 编译器是向上兼容的。实际对象代码是完全兼容的,但较新编译器所发送的调试信息 (stabs) 与较早的调试器是不兼容的。
- 哪些 RogueWave 库已被“批准”用于 Sun Studio 9?
我们无法可靠地跟踪哪些供应商批准了其产品的哪些版本用于我们的编译器的一些版本。而且确保本常见问题总是最新问题会更加困难。您必须和供应商联系,看他们是否用 C++ 编译器的任何特定版本测试了其产品。
不过,某些 RogueWave 库是随我们的编译器发送的,这就是说我们一 起发送的版本是能正确配合的。
- 我怎么知道有哪些补丁程序,以及当前的补丁程序解决了哪些问题?
关于产品补丁程序的最新信息,请访问开发人员入口,网址为
http://developers.sun.com/prodtech/cc。产品补丁程序可以从 http://sunsolve.sun.com 下载。
- 我是否需要有 libC.so.5 和 libCrun.so.1 的补丁程序?
一般来说,Solaris[tm] 操作系统与这些库的最新版本一起发送。但是,由于修改错误和一些性能提升,这些库经常会有补丁程序。这些补丁程序总是累积性的并且向后兼容,所以最好是获得最新的补丁程序,网址是 http://sunsolve.sun.com。下表给出了 2004 年 7 月最新的补丁程序 ID 情况。
请检查数据库中最新的软件包。该软件包名为 SUNWlibC(32 位)和 SUNWlibCx(64 位)。请搜索 libCrun 并将搜索限制为“Patch Descriptions”而不是“All Sun Internal Collections”,后者是搜索列表框的缺省设置。
表 1:libC 和 libCrun 补丁程序 补丁程序 ID Solaris
操作系统架构 108434-17
8
SPARC/v8
108435-17
8
SPARC/v9
108436-15
8
x86
111711-11
9
SPARC/v8
111712-11
9
SPARC/v9
111713-08
9
x86
C. 编译器兼容性
- 我能混合使用兼容模式 (-compat) 的代码与标准模式的代码吗?
我们建议不要在同一个程序中混合代码,而且也不支持这样做,即使是 通过“插件程序”或动态装入库。原因如下:
类对象的布局不同。
函数的调用顺序不同。
“名字粉碎”不同。
处理异常的方法有冲突。
两套 iostream 对象连接到同一个文件描述符会引起问题。
即使程序的两部分(兼容模式和标准模式)不进行通信,如果代码中任何地方抛出了异常,程序也可能会立即崩溃。
在某些情况下您可以把兼容模式和标准模式的目标文件链接到一起。这个问题在编译器随附的《C++ 迁移指南》中有详细说明。请看第一章中的“混合旧二进制文件和新二进制文件”。该指南可从 http://docs.sun.com 获得。
- 我怎么才能将 C++ 或 C 的程序与 F77、F90 或 F95 的程序混合?
从 Workshop 6 update 1(编译器版本 5.2)开始,您可以使用 -xlang={f90|f95|f77} 选项。这个选项让驱动程序找出链接行究竟需要哪些库以及这些库需要以何种顺序出现。
-xlang 选项在 C 编译器中不可用。要混合 C 和 Fortran 例程,您必须用 cc 来编译并用 Fortran 链接器将其链接。
D. 编码和诊断
- 为什么编译器会对标准异常类报告含义不清?
- 为什么 C++ 5.3 会对我的派生虚拟函数的抛出指定发送错误?
- 为什么在我链接程序时会丢失模板实例?实例似乎在模板缓存中。
- 为什么在我使用 +w2 时会出现函数未扩展的警告,而使用 +w2 +d 就不会?
- 我能使用 -ptr 选项来获得多个模板系统信息库,或在不同项目中共享系统信息库吗?如果不能,我该怎么做?
- 为什么 fprintf("%s",NULL) 会导致段故障?
- 根据我调用 sqrt() 的不同方式,得到的复数平方根中虚部的符号也不同。这是什么原因?
- 在类模板中的一个友元函数未能实例化,而且出现链接时间错误。这在 C++ 5.0 中是可以的,为什么现在不行了?
- 为什么编译器说一个封闭类中的一个成员无法从嵌套的类中访问?
- 运行时出现“纯虚拟函数调用”消息是什么原因?
- 为什么编译器提示派生类虚拟函数用不同的签名隐藏了基类虚拟函数?我的其他编译器没有对此代码报错。
- 为什么编译器会对标准异常类报告含义不清?
在 Solaris 中,标准头文件 <math.h> 对结构“异常”有一个声明,这是标准 Unix 所要求的。如果您通过使用声明或使用指令把 C++ 的标准异常类带到全局范围中,会造成冲突。
// 例 1 #include <math.h> #include <exception> using namespace std; // using-declaration exception E; // error, exception is ambiguous // 例 2 #include <math.h> #include <exception> using std::exception; // using-directive exception E; // error, multiple declaration for exception使用声明的名称转换和使用指令稍有不同,所以错误消息并不完全一样。
解决办法:
- 用 <cmath> 代替 <math.h>。在 Solaris 中,<cmath> 只包含 C 和 C++ 标准所指定的声明。如果您需要 <math.h> 的 Unix 特性,那您就不能使用这个解决办法。
- 不写入 using std::exception;(如果还使用 <math.h> 的话)。显式写出 std::exception 或使用 typedef 来访问标准异常类,如下例:
#include <math.h> #include <exception> std::exception E; // OK typedef std::exception stdException; // OK stdException F; // OK- 不要写 using namespace std;。
C++ 名称空间标准包含的名称太多,您在实际代码中使用这条指令的时候很可能会与应用程序代码或第三方的库冲突。(C++ 编程的书籍和文章有时候用这个使用指令来缩减小示例的大小。使用单独的使用声明或显式限定名称。
- 为什么 C++ 5.3 会对我的派生虚拟函数的抛出指定发送错误?
5.3 C++ 编译器中新加入了一条 C++ 规则,派生类中的虚拟函数只能允许其所覆盖的函数所允许的异常。覆盖函数限制性可以更高,但不能更低。考虑以下示例:
class Base { public: // might throw an int exception, but no others virtual void f() throw(int); }; class Der1 :public Base { public: virtual void f() throw(int); // ok, same specification }; class Der2 :public Base { public: virtual void f() throw(); // ok, more restrictive }; class Der3 :public Base { public: virtual void f() throw(int, long); // error, can't allow long }; class Der4 :public Base { public: virtual void f() throw(char*); // error, can't allow char* }; class Der5 :public Base { public: virtual void f(); // error, allows any exception };这段代码给出了这条 C++ 规则的原因:
#include "base.h" // declares class Base void foo(Base* bp) throw() { try { bp->f(); } catch(int) { } }由于 Base::f() 被声明为只引发整数异常,foo 函数可以捕获整数异常,并声明它不允许任何异常换码。假设有人后来声明了 Der5 类,其中的覆盖函数可以引发任何异常,并把一个 Der5 指针传递给 foo。即使编译 foo 函数时可见代码没有任何问题,foo 函数也将无效。
- 为什么在我链接程序时会丢失模板实例?实例似乎在模板缓存中。
模板缓存保持了编译器所产生的目标文件的依存性列表,以及缓存中的模板实例。 Note, however, that the compiler now only uses the template cache when you specify -instances=extern.如果您移动或重命名目标文件,或把目标文件合并到库中,就会断开与缓存的连接。有两个解决办法:
- 直接在最终目录中生成目标文件。模板缓存将在同一个目录下。
不要这样:
example% CC -c -instances=extern f1.cc example% mv f1.o /new/location/for/files而要这样:
example% CC -c -instances=extern f1.cc -o /new/location/for/files/f1.o您可以将这个过程封装在 makefile 宏中。
- 您可以用 CC -xar 来生成中间归档 (.a) 文件。每个归档就会包含归档中对象所使用的所有模板实例。然后再把那些归档链接到最终程序中。有些模板实例在不同的归档中重复了,但链接器会只为每个归档保留一个实例。
example% CC -c -instances=extern f1.cc f2.cc f3.cc example% CC -xar f1.o f2.o f3.o -o temp1.a example% CC -c -instances=extern f4.cc f5.cc f6.cc example% CC -xar f4.o f5.0 f6.0 -o temp2.a example% CC -c -instances=extern main.cc example% CC main.o temp1.a temp2.a -o main- 为什么在我使用 +w2 时会出现函数未扩展的警告,而使用 +w2 +d 就不会?
C++ 编译器有两种内联:由分析器完成的 C++ 内联函数内联,以及由代码发生器完成的优化内联。C 和 Fortran 编译器只有优化内联。(平台上的所有编译器都使用相同的代码发生器。)
C++ 编译器的分析器会尝试扩展内联任何隐含或显式声明为内联的函数。 如果函数太大,分析器只在您使用 +w2 选项时才会发送警告。+d 选项会阻止分析器内联任何函数。所以如果使用 +d,就不会出现警告。(-g 选项也会关闭 C++ 内联函数的内联。)-xO 选项不影响此类内联。
优化内联不依赖于编程语言。如果您选择了优化级别 (-xO4) 或更高级别,代码发生器就会检查所有的函数,无论它们在源代码中如何声明。而且只要代码发生器认为有利,它就会把函数调用替换成内联代码。不发送任何关于优化内联(或内联函数失败)的消息。+d 选项不影响优化内联。
- 我能使用 -ptr 选项来获得多个模板系统信息库,或在不同项目中共享系统信息库吗?如果不能,我该怎么做?
5.0 到 5.6 版本不支持 -ptr 选项。尽管 4.2 版本中有这个选项,但它的结果并不总是如用户所希望的那样,而且还导致了很多问题。
建议最好不要在不同的项目中共享系统信息库。共享系统信息库所导致的问题很可能比您试图解决的问题更严重。在任一个目录中只能编译一个项目。与不同项目相关的二进制文件应使用不同的目录。
从 5.0 版开始,编译器会在生成目标文件的目录中放一个模板系统信息库。如果您想对一个项目使用多个系统信息库,应在想放置相关系统信息库的目录中生成一个目标文件。在链接时,会在与目标文件相关的所有系统信息库中自动搜索模板实例。不需要任何编译器选项。
- 为什么 printf("%s",NULL) 会导致段故障?
有些应用程序错误地认为空字符指针应按指向空字符串的指针来处理。在这些应用程序中访问空字符指针时就会发生段违规。
有几个原因导致不使用 *printf() 函数系列来检查空指针。其中包括(但并不仅限于)以下原因:
- 这样做会造成安全的假像。它使程序员认为把空指针传递到 printf() 是没问题的。
- 这会鼓励程序员编写无法移植的代码。ANSI C、XPG3、XPG4、SVID2 和 SVID3 规定 printf("%s", pointer) 需要使指针指向空的终止字符数组。
- 这使得调试更加困难。如果程序员把空指针传递到 printf() 并且程序丢掉核心,很容易用调试工具找到是哪个 printf() 调用给出了错误指针。但是,如果 printf() 通过打印“(null pointer)”而隐藏了错误,则管线中的其他程序很可能会试着翻译“(null pointer)”,而实际上它们在等待真实数据。这时候可能就无法确定真实的问题到底隐藏在何处了。
如果您的某个应用程序把空指针传递给 *printf,您可以使用一个特别共享目标 /usr/lib/0@0.so.1,它提供了在位置 0 建立 0 值的机制。由于这个库将所有牵涉到任何类型的空指针非关联化的错误都屏蔽了,所以您只应把这个库作为改正代码之前的临时解决办法。
- 根据我调用 sqrt() 的不同方式,得到的复数平方根中虚部的符号也不同。这是什么原因?
这个函数的实现是按照 C99 csqrt 附件 G 规范进行的。例如,这是下列代码范例的输出:
complex sqrt (3.87267e-17, 0.632456)
float sqrt (3.87267e-17, -0.632456)
在兼容模式下使用 libcomplex 的示例:
#include <iostream.h> #include <math.h> #include <complex.h> int main() { complex ctemp(-0.4,0.0); complex c1(1.0,0.0); double dtemp(-0.4); cout<< "complex sqrt "<< sqrt(ctemp)<<endl; cout<< "float sqrt "<< sqrt(c1*dtemp)<<endl; }在标准模式下使用 libCstd 的示例:
#include <iostream> #include <math.h> #include <complex> using namespace std; int main() { complex<double> ctemp(-0.4,0.0); complex<double> c1(1.0,0.0); double dtemp(-0.4); cout<< "complex sqrt "<< sqrt(ctemp)<<endl; cout<< "float sqrt "<< sqrt(c1*dtemp)<<endl; }复数的 sqrt 函数是用 atan2 实现的。下面的示例阐明了使用 atan2 的问题。这个程序的输出为:
c=-0.000000 b=-0.400000 atan2(c, b)=-3.141593 a=0.000000 b=-0.400000 atan2(a, b)=3.141593一种情况下,atan2 的输出为负而另一种情况下输出为正。这取决于第一个传递的参数是 -0.0 还是 0.0。
#include <stdio.h> #include <math.h> int main() { double a = 0.0; double b = -0.4; double c = a*b; double d = atan2(c, b); double e = atan2(a, b); printf("c=%f b=%f atan2(c, b)=%f\n", c, b, d); printf("a=%f b=%f atan2(a, b)=%f\n", a, b, e); }- 在类模板中的一个友元函数未能实例化,而且出现链接时间错误。这在 C++ 5.0 中是可以的,为什么现在不行了?
下面的试验范例用 C++ 5.0 编译器编译和连接是没有错误的,但在较 新版本的编译器上却会导致连接时错误。
example% cat t.c #include <ostream> using std::ostream; template <class T> class TList { public: friend ostream& operator<< (ostream&, const TList&); }; template <class T> ostream& operator<< (ostream& os, const TList<T>& l) { return os; } class OrderedEntityList { public: TList<int> *Items; ostream& Print(ostream &) const; }; ostream& OrderedEntityList::Print(ostream& os) const { os << *Items; return os; } main() { } example% CC t.c Undefined first referenced symbol in file std::basic_ostream<char,std::char_traits<char> >&operator<<(std::basic_ostream<char,std::char_traits<char> >&,const TList<int>&) 4421826.o ld:fatal:Symbol referencing errors.No output written to a.out按照标准,这个试验范例是无效的。问题在于,下列声明不涉及任何模板实例:
friend ostream& operator<< (ostream&, const TList&);不涉及任何模板实例。
非限定的名称查找无法匹配模板声明,即使它在友元声明时是可见的。要使友元声明匹配模板,您需要将其声明为模板函数,或限定名称。
不管用哪种方法,在友元声明时模板的声明都必须是可见的。
总的来说,友元声明不涉及模板,但它声明了一个与函数调用最匹配的函数。(其他方面都相同的情况下非模板函数比模板函数更好。)
下面的代码是有效的:
template <class T> class TList; // so we can declare the operator<< template template <class T> ostream& operator<< (ostream& os, const TList<T>& l) { return os; } template <class T> class TList { public: // note the scope qualification on the function name friend ostream& ::operator<< (ostream&, const TList&); };- 为什么编译器提示无法从嵌套的类中访问封闭类中的成员?
class Outer { typedef int my_int; static int k; class Inner { my_int j; // error, my_int not accessible int foo() { return k; // error, k not accessible } }; };按照 ARM 和 C++ 标准,嵌套类对封装类中的成员没有特别的访问权。由于 my_int 和 k 在 Outer 中是私有的,所以只有 Outer 的友元才能访问它们。要使嵌套类成为友元,您必须前向声明该类,然后使其成为友元,如下例所示:
class Outer { typedef int my_int; static int k; // add these two lines ahead of the class definition class Inner; friend class Inner; class Inner { my_int j; // OK int foo() { return k; // OK } }; };- 运行时出现“纯虚拟函数调用”消息是什么原因?
出现“纯虚拟函数调用”消息肯定是因为程序中有错误。错误以下面两种方式中的一种发生:
- 从抽象类的构造函数或析构函数把“this”参数传递到外部函数就会导致这种错误。在构造和析构时,“this”的类型是构造函数或析构函数本身类的类型,而不是最终构成类的类型。然后可能会试图调用纯虚拟函数。考虑以下示例:
class Abstract; void f(Abstract*); class Abstract { public: virtual void m() = 0; // pure virtual function Abstract() { f(this); } // constructor passes "this" }; void f(Abstract* p) { p->m(); }抽象构造函数调用 f 时,“this”的类型为“Abstract*”,而且函数 f 试图调用纯虚拟函数 m。
- 另外,尝试调用未用显式限定来定义的纯虚拟函数也会导致这种错误。可以给纯虚拟函数提供主体,但它只能通过在调用时限定名称的方式来调用,绕过虚拟调用机制。
class Abstract { public: virtual void m() = 0; // body provided later void g(); }; void Abstract::m() { ...} // m 的定义 void Abstract::g() { m(); // error, tries to call pure virtual m Abstract::m(); // OK, call is fully qualified }- 为什么编译器提示派生类虚拟函数用不同的签名隐藏了基类虚拟函数?我的其他编译器没有对此代码报错。
C++ 的规则是重载只在一个范围内发生,绝不会跨范围发生。基类被认为是在围绕派生类的范围内。所以在派生类中所声明的任何名称都会隐藏而不能重载基类中的任何函数。C++ 的基本规则优先于 ARM。
如果其他编译器没有报错,实际上会对您产生危害,因为此代码并不会像预期的那样工作。我们的编译器收到此代码时会发出警告。(此代码是合法的,但它可能不会产生您需要的结果。)
如果您想在重载集中包括基类函数,就必须把基类函数带到当前范围中。如果您是在缺省的标准模式下进行编译,可以加一个使用声明:
class Base { public: virtual int foo(int); virtual double foo(double); }; class Derived :public Base { public: using Base::foo; // add base-class functions to overload set virtual double foo(double); // override base-class version };
E. 库兼容性
- 我怎样才能得到一个完全符合标准的 C++ 标准库 (stdlib)?当前的 libCstd 不支持哪些功能?
- 我需要 C++ 标准模板库 (STL)。我从哪里才能得到?有没有兼容模式 (-compat) 的标准模板库?
- libCstd 缺少哪些标准库功能?
- 缺少标准库功能会造成什么后果?
- 有没有能用于标准流的 tools7 库版本?近期内会有 tools8 吗?
- 我怎样才能得到一个完全符合标准的 C++ 标准库 (stdlib)?当前的 libCstd 不支持哪些功能?
本发行版本包括了 STLport 标准库实现版本 4.5.3,作为可选标准库。STLport 有着良好的性能,可以满足 C++ 标准,而且还有一些深受欢迎的扩展。但是,它在二进制上与缺省使用的标准库并不兼容。
当前的 libCstd 是为 C++ 编译器的 5.0 版而开发的。这个版本不支持模板作为类的成员。标准库的某些部分要求成员模板,这就意味着缺少了一些功能。缺少的功能大部分出现在具有构造函数模板允许隐含类型转换的容器类中。您必须在源代码中编写显式转换来作为解决办法。
从 5.1 版开始,C++ 编译器支持模板作为类的成员,而且可以支持符合标准的库。我们无法不破坏源码和二进制兼容性而更新库,所以我们继续发送带有同样限制的 libCstd。
您可以在 gnu 和 SGI 网站上找到标准库的公共实现,您也可以从 RogueWave 和 Dinkumware 等供应商那里购买库。参看下面关于 STL 的问题。
- 我需要 C++ 标准模板库 (STL)。我从哪里才能得到?有没有兼容模式 (-compat) 的标准模板库?
C++ 编译器现在支持 STLport 标准库实现版本 4.5.3。libCstd 仍是缺省库,但现在有 STLport 产品可供选择。本发行版本既包括一个名为 libstlport.a 的静态归档,也包括一个名为 libstlport.so 的动态库。
发出下面的编译器选项来关闭 libCstd 并使用 STLport:
-library=stlport4
缺省的 C++ 标准库 libCstd 以及 STLport 都包含 STL。您可以使用不同版本的标准库,但这么做是有风险的而且也不能保证良好的结果。
要插入不同的 STL,应使用 -library=no%Cstd 选项并将编译器指向您的头文件和所选的库。如果替代库没有本身的 iostreams 库,而且您可以用“传统”iostreams 来代替标准 iostreams,则应在命令行中加入 -library=iostream。详细指示请参见编译器随附的《C++ 用户指南》中的“替代 C++ 标准库”。可在 http://docs.sun.com 获得该指南。
- libCstd 缺少哪些标准库功能?
原先(在 C++ 5.0 中)标准库的构造不支持那些要求编译器中具有成员模板和部分专门化的特性。虽然从 C++ 5.1 以后具有了这些特性,但不能在标准库中打开它们,因为这会影响后向兼容性。下面列出了各个被禁止的特性所缺少的功能。
被禁止的特性:成员模板函数
在 <complex> 的 complex 类中:
template <class X> complex<T>& operator= (const complex<X>& rhs)
template <class X> complex<T>& operator+= (const complex<X>& rhs)
template <class X> complex<T>& operator-= (const complex<X>& rhs)
template <class X> complex<T>& operator*= (const complex<X>& rhs)
template <class X> complex<T>& operator/= (const complex<X>&)
在 <utility> 的 pair 类中:
template<class U, class V> pair(const pair<U, V> &p);
在 <locale> 的 locale 类中:
template <class Facet> locale combine(const locale& other);
在 <memory> 的 auto_Ptr 类中:
auto_ptr(auto_ptr<Y>&);
auto_ptr<Y>& operator =(auto_ptr<Y>&);
template <class Y> operator auto_ptr_ref<Y>();
template <class Y> operator auto_ptr<Y>();- 在 <list> 的 list 类中:
成员模板排序。
- 在大多数模板类中:
模板构造函数。
被禁止的特性:成员模板类
在 <memory> 的 auto_ptr 类中:
template <class Y> class auto_ptr_ref{};
auto_ptr(auto_ptr(ref<X>&);被禁止的特性:重载部分专门化的函数模板参数
在 <deque>、<map>、<set>、<string>、<vector> 和 <iterator> 中不支持下面的模板函数(非成员):
对于 map、multimap、set、multiset、basic_string、vector、reverse_iterator 和 istream_iterator 类:
bool operator!= ()
对于 map、multimap、set、multiset、basic_string、vector 和 reverse_iterator 类:
bool operator> ()
bool operator>= ()
bool operator<= ()对于 map、multimap、set、multiset、basic_string 和 vector 类:
void swap()
被禁止的特性:带缺省参数的模板类的部分专门化
在 <algorithm> 中不支持下面的模板函数(非成员):
count(), count_if()
在 <iterator> 中不支持下面的模板:
template <class Iterator> struct iterator_traits {}
template <class T> struct iterator_traits<T*> {}
template <class T> struct iterator_traits<const T*>{}
templatetypename iterator_traits ::difference_type distance(InputIterator first, InputIterator last); - 缺少标准库功能会造成什么后果?
有些代码按照 C++ 标准是有效的,但不能编译。
最常见的示例是创建映射时配对中的第一个元素可以是 const 但没有这样声明。需要时成员构造模板将隐含地把 pair<T, U> 转成 pair<const T, U>。由于缺少构造函数,会出现编译错误。
由于不允许更改映射配对中的第一个成员,所以最简单的解决办法是在创建配对类型时使用显式 const。例如,不用 pair<int, T> 而用 <const int, T>;不用 map<int, T> 而用 map<const int, T>。
- 有没有能用于标准流的 tools7 库版本?近期内会有 tools8 吗?
虽然有,但只能用于 C++ 5.3、5.4、5.5 和 5.6。使用 -library=rwtools7_std 命令进行编译和链接该库。
RogueWave 已改变了 Tools.h++ 的工作方式,现在只将其作为 SourcePro 产品的一部分来提供。没有版本 8 的 Tools.h++。
F. 编译时性能
- 我发现 5.0 版和 5.1 版编译器的编译时间非常长 (和 4.2 版比较)。这个问题能在近期解决吗?
- 我们注意到与 4.2 版编译器相比,二进制文件要大很多。这有解决办法吗?
- 能否将单一编译进程分布到多个处理器?更一般地说,多处理器 (MP) 系统的编译时间性能是否总是更好一些?
- 我发现 5.0 版和 5.1 版编译器的编译时间非常长 (和 4.2 版比较)。这个问题能在近期解决吗?
我们在 5.1 版的补丁程序 01、5.2、5.3、5.4、5.5 和 5.6 版中已经缩短了编译时间。如果您对编译器的性能不满意,请考虑下面的建议:
在一些极端情况下,内联会导致巨大的浪费。使用了 -xO4 或 -xO5 选项时,代码发生器会自动内联一些函数。您可能需要使用较低的优化级别(如 -xO3)。您可以用 -xinline 选项来防止优化器自动内联某些特定的函数。
关闭较大函数的显式内联。有关显式内联的讨论,请参见下列内容。
- 我们注意到与 4.2 版编译器相比,二进制文件要大很多。这有解决办法吗?
-g 编译的二进制文件会增大,原因是从 5.0 版开始,编译器会生成很多模板调试信息。5.1 版的编译器对很多种程序大大减小了生成的调试信息。5.2、5.3、5.4、5.5 和 5.6 版的编译器进行了进一步的改进。在很多情况下,二进制文件的大小减小了 25% 到 50% 以上。改进主要体现在使用了名称空间、模板和带多级继承的类分层结构的代码上。
- 能否将单一编译进程分布到多个处理器?更一般地说,多处理器 (MP) 系统的编译时间性能是否总是更好一些?
编译器本身并不是多线程的。多处理器系统的性能会更好一些,因为进行任何一个编译时计算机上都总有很多其他进程在运行。
使用 dmake (编译器随附的工具之一),您就可以同时运行多个编译。
G. 运行时性能
- C++ 是否总是内联用关键词“inline”标记的函数?为什么尽管我编写了内联的函数,却看不到它们被内联?
从根本上说,编译器将 inline 声明视为指导并试图内联该函数。在 5.1 到 5.6 版的编译器中修订了内联算法,使其能理解更多的构造。但是,仍然有它无法成功的情况。限制包括:
从 5.2 到 5.6 版的 C++ 编译器开始,有些极少执行的函数调用不再被扩展。这个变化可以更好地平衡编译速度、输出代码大小和运行时速度。
例如,在静态变量初始化中所用的表达式只执行一次,所以那些表达式里的函数调用不再被扩展。请注意内联函数 func 在静态变量的初始化表达式中被调用时可能不被扩展,但在其他地方仍可被内联。类似地,异常处理程序中的函数调用可能不被扩展,因为那些代码极少执行。
递归函数只内联到第一层调用级别。编译器无法不确定地内联递归函数调用。当前的实现办法停留在对正被内联的任何函数的第一次调用上。
有时甚至对小函数的调用也不被内联。原因是总的扩展大小可能会太大。例如,func1 调用 func2,而 func2 又调用 func3,依此类推。即使这些函数每一个都是小函数,而且也没有递归调用,合并的扩展大小也可能会太大,使得编译器无法扩展所有这些函数。
很多标准的模板函数是很小的,但调用链很深。在这种情况下,只有几级调用会被扩展。
编译器不内联那些包含 goto 语句、循环和 try/catch 语句的 C++ 内联函数。但是,这些函数可以由优化器在 -xO4 级别下内联。
编译器不内联大函数。C++ 编译器和优化器对内联的函数大小都有限制。我们一般推荐使用这个限制。对于特殊情况,请联系技术支持以了解升高或降低此大小限制的内部选项。
虚拟函数不能被内联,即使它在子类中从未被重定义。原因是编译器无法知道别的编译单元是否包含子类和对该虚拟函数的重定义。
请注意在某些早先版本中,带有复杂 if 语句和返回语句的函数不能被内联。这个限制已经取消。而且,对内联函数大小的缺省限制也被升高了。在某些程序中,这些变化会导致更多的函数被内联,从而导致编译变慢,生成的代码变大。
要完全消除 C++ 内联函数的内联,应使用 +d 选项。
优化器可以根据控制流等的结果,在较高的优化级别 (-xO4) 下单独地内联函数。这个内联是自动的,不管您是否把函数声明为“内联”。
2004 年 5 月 27 日更新 |
版权所有 © 2002 Sun Microsystems, Inc.。 保留所有权利。须按照许可条款进行使用。