C++ 标准规定,无后缀的十进制整型常量当作 int 处理(如果值符合 int),否则当作 long int 处理。如果值不符合 long int,结果将不确定。
在 32 位模式中,类型 int 和 long 具有相同的大小和数据范围。C++ 编译器遵循 1990 C 标准规则,将 INT_MAX+1 和 LONG_MAX 之间的值当作无符号的 long 处理。这种处理在某些程序中会产生意外的结果。
1999 C 标准更改了无后缀的十进制整数的规则,这样它们就永远不会被当作无符号的类型来处理。类型为 int、long 或 long long 中第一个可以表示值的类型。
C++ 编译器在标准模式中遵循 C99 规则,但在 -compat=4 模式中继续遵循 C90 规则。(在 -compat=4 模式中,编译器的行为类似于 C++ 4.2 编译器。)
如果您希望大型十进制整数被当作无符号的整数,则简便的解决方法是使用 u 或 U 后缀。同样,可以对其他类型使用其他后缀。例如:
// note: 2147483648 == (INT_MAX+1) 2147483648 // (signed) long long 2147483648LL // (signed) long long 2147483648U // same as 2147483648u |
某些 C++ 语句有可能解释成声明或者表达式语句。C++ 消除歧义规则为:如果一个语句可以处理成声明,那么它就是声明。
早期版本的编译器会错误解释类似于下面的代码:
struct S { S(); }; struct T { T( const S& ); }; T v( S() ); // ??? |
编程人员也许本来打算在最后一行定义变量 v,并且用类型为 S 的临时变量对它进行初始化。早期版本的编译器会这样解释这条语句。
但是在声明环境里,构造符号 "S()" 也可以是抽象声明符(不带标识符),表示“没有返回值类型为 S 的参数的函数”。在这种情况下,该语句会自动转换为函数指针 "S(*)()"。这样该语句仍可作为函数 v 的声明,该函数有一个函数指针类型的参数,返回类型为 T 的值。
当前版本的编译器可以正确地解释该语句,但这未必是编程人员所需要的结果。
可以使用两种方法来修改上述代码以便不产生歧义:
T v1( (S()) ); // v1 is an initialized object T v2( S(*)() ); // v2 is a function |
第一行中另加的圆括号表明它不是 v1 作为函数声明的有效语法,所以它的唯一可能解释是“利用类型为 S 的临时值进行初始化的类型为 T 的目标”。
同样,构造符号 "S(*)()" 不可能是一个值,所以它的唯一可能解释是函数声明。
第一行也可以改写为:
T v1 = S();
虽然这时语句含义非常清楚,但这种形式的初始化有时会创建一个额外的临时变量,而一般情况下不会发生这种情况。
建议不要编写与下面语句类似的代码,因为它的含义不清楚,不同的编译器可能会提供不同的结果。
T v( S() ); // not recommended
下面的模板语法是无效的,但 Sun C++ 编译器 4 和 5.0 版并不报告这个错误。在标准模式(缺省模式)下编译时,C++ 编译器 5.1 版本及以后的所有版本都会报告这个语法错误。
template<class T> class MyClass<T> { ... }; // definition error template<class T> class MyClass<T>; // declaration error |
在这两种情况下,MyClass<T> 中的 <T> 无效,必须删除,如下例所示:
template<class T> class MyClass { ... }; // definition template<class T> class MyClass; // declaration |
模板选项 -instances=static(或 -pto)在与 -xcrossfile 或 -xipo 选项组合时无效。使用该组合的程序会经常发生链接失败。
如果使用 -xcrossfile 或 -xipo 选项,请使用缺省的模板编译模型 -instances=global 进行替代。
通常,不要使用 -instances=static(或 -pto)。它不再有任何优点,此外,C++ 用户指南中还对其缺点进行了说明。
-xlang=f77 命令行选项导致编译进程遇到链接程序错误。要避免该错误,并仍包含相应的运行时库,应改为使用 -xlang=f77,f90 进行编译。
以下情况会导致链接问题。
函数在一个地方声明带有一个 const 参数,而在另一个地方又声明带有一个非 const 参数。
示例:
void foo1(const int); void foo1(int); |
这两个声明是等效的,但编译器会将其重整为两个不同的名称。要避免这个问题,则不应将值参数声明为 const。例如,在任何位置都使用 void foo1(int);,包括该函数定义体。
函数有两个具有相同复合类型的参数,但只有一个参数是用 typedef 声明的。
示例:
class T; typedef T x; // foo2 has composite (that is, pointer or array) // parameter types void foo2(T*, T*); void foo2(T*, x*); void foo2(x*, T*); void foo2(x*, x*); |
所有的 foo2 声明都是等效的,并且应该重整相同的名称。但是,编译器只会重整部分声明的名称。为了避免这个问题,应该统一使用 typedef。
如果您无法统一使用 typedef,解决方法是:在定义该函数的文件中使用弱符号,使得声明与函数的定义一致。例如:
#pragma weak "__1_undefined_name" = "__1_defined_name" |
请注意,某些重整名称依赖于目标体系结构。(例如,在 SPARC V9 体系结构 (-m64) 中,size_t 是 unsigned long,而在其他体系结构中是 signed int。)在这种情况下,会出现两个版本的重整名称,分别对应两个模式。这时必须使用两个 pragma,并用适当的 #if 指令对其进行控制。
在兼容模式 (-compat) 下,C++ 编译器会错误地重整成员函数指针的链接名称。此错误会导致 demangler 以及其他一些调试工具(例如 dbx 和 c++filt)报告该成员函数有多余的前导参数,该参数包括对该成员函数所在类的引用。要更正此问题,请添加 -Qoption ccfe -abiopt=pmfun1 标志。请注意,使用这个标志进行编译的源代码可能会与不使用此标志编译的源代码在二进制上不兼容。在标准模式(缺省模式)下,不会出现该问题。
如果您使用 -instances=extern 编译,则使用模板和静态对象的程序会出现未定义符号的链接时错误。使用缺省设置 -instances=global 则不会出现问题。编译器不支持对模板中的非全局名称空间作用域目标的引用。请看以下示例:
static int k; template<class T> class C { T foo(T t) { ... k ... } }; |
在本示例中,一个模板类的成员引用了静态名称空间作用域的变量。请记住,名称空间作用域包含文件作用域。编译器不支持模板类的成员引用静态名称空间作用域的变量。另外,如果模板在其他的编译单元实例化,那么每个实例都会指向不同的 k,这破坏了 C++ 一次定义规则,代码的行为将会不可预测。
下面的方法也是可行的,但这取决于您如何使用 k,以及它应有的功能。第二个选项仅可供属于类成员的函数模板使用。
可以为变量提供外部链接属性:
int k; // not static |
所有的实例都使用同一个 k。
也可以使这个变量成为类的静态成员:
template<class T> class C { static int k; T foo(T t) { ... k ... } }; |
静态类成员具有外部链接属性。每个 C<T>::foo 的实例都使用不同的 k。而 C<T>::k 的一个实例可以被其他函数共享。此选项可能是您需要的选项。
在名称空间内使用 #pragma align 时,必须使用重整名称。例如,在下面的代码中,#pragma align 语句是无效的。要更正此问题,应将 #pragma align 语句中的 a、b 和 c 替换为其重整名称。
namespace foo { #pragma align 8 (a, b, c) // has no effect //use mangled names: #pragma align 8 (__1cDfooBa_, __1cDfooBb_, __1cDfooBc_) static char a; static char b; static char c; } |
早期 C++ 编译器发行版并不支持 C++ 标准要求的函数重载。当前发行版修正了调用重载函数时出现的许多错误。具体来讲,当函数调用确实出现多义性时,编译器有时会选择某个函数;或者在函数调用实际上未产生多义性时,编译器会发出警告,指出此调用具有多义性。
采用某些解决方法来避免多义性消息已经没有必要了。也许您会看到以前未报告的新多义性错误。
导致函数调用多义性的一个主要原因在于仅重载内置类型的子集。
int f1(short); int f1(float); ... f1(1); // ambiguous, "1" is type int f1(1.0); // ambiguous, "1.0" is type double |
要解决此问题,要么根本不重载 f1,要么重载所有未提交的类型:int、unsigned int、long、unsigned long 和 double。(也许还有 long long、unsigned long long 以及 long double 类型)。
导致多义性的另一个主要原因在于:类中的类型转换函数,尤其是当您也重载了运算符或构造函数时。
class T { public: operator int(); T(int); T operator+(const T&); }; T t; 1 + t // ambiguous |
该操作有多义性是因为它可能被处理成:
T(1) + t // overloaded operator 1 + t.operator int() // built-in int addition |
可以提供重载运算符,也可以提供类型转换函数,但同时提供它们则会产生多义性。
实际上,类型转换函数自身也经常会导致多义性,而且往往在不应该进行转换时进行转换。如果您确实需要类型转换,最好使用命名的函数,而不是类型转换函数。例如,使用 int to_i t();,而不是 operator int();。
更改后,1 + t 操作就不再具有多义性了。它只能解释为 T(1) + t。如果您希望有其他的解释,必须写入 1 + t.to_int()。