C++,那些可爱的小陷阱(一)
此系列是为那些读过TC++PL或者具有类似水平的同学准备的,作为系列的第一篇以及有趣的热身,我们来看一个链接问题:
D1.cpp
struct X {
X(int);
X(int, int);
};
X::X(int = 0, int = 0) { printf("1\n"); }
class D: public X {
};
int minus(int a,int b)
{
D d;
return a-b;
}
D2.cpp
struct X {
X(int);
X(int, int);
};
X::X(int = 0) { printf("2\n"); }
class D: public X {
};
int add(int a,int b)
{
D d;
return a+b;
}
用来执行的main.cpp:
int minus(int a,int b);
int add(int a,int b);
int main()
{
add(1,2);
minus(1,2);
}
将以上三个cpp文件分别编译并链接成一个应用程序,在运行之前,请先猜个结果。然后运行。
好吧,我想你看到了答案。在你开始思考为什么会是这样的之前,请再做一件事,将D1.cpp和D2.cpp两个文件的内容完全交换,然后重新编译运行一次。
请原谅上面写的比较混乱且没有给出每一步的结果,因为使用VS的C++编译器,这个结果是不确定的。然而毫无疑问,两次运行的结果将是不同的!
在我这里,第一次显示了
2
2
第二次显示了
1
1
OMG,文件居然影响了程序结果!所以请不要总是相信编译器,这个例子来自C++标准,它用以说明一个重要的准则:One Defination Rule,简写ODR
ODR在C++标准中被解释为:
1.任何编译单元都不能包含变量、函数、枚举、类或者模板的定义一次以上。
2.所有程序必须且只能包含一次其中用到的所有非内联函数和对象。
3.在需要类的完整定义的编译单元中,类的定义必须且只能出现一次。
4.(好bt的一条啊,恰好这一条可以解释我们的程序)包括类、枚举、类模板......(具体有哪些请自己看spec)在内的一些定义可以在一个程序中出现多次,但是必须满足以下条件:
(1)所有定义的token序列必须相同(token你可以认为就是有效的语言要素,出了空白、换行注释之类的)
(2)所有的命名查找必须指向同一个实体,也就是说,你不能搞一些命名空间 typedef之类的,让这些相同的token表示不同的意义
(3)所有运算符必须表示同一个重载
(4)对于你要定义的实体中的所有带默认参数的函数,默认参数必须满足以上三条
(5)对于类定义,构造函数中调用的基类构造函数必须是同一个
总而言之,这个第四条的意思就是不同的定义之间不能有任何歧义
所以按照标准,应该无法通过链接器,这里似乎VC++实现的不是很理想,产生了一个未定义行为,既没有给出警告也没有报错。晚上回去看看g++的表现,也欢迎知道更多细节的朋友指点。
PS.g++似乎做了跟VC++相同的事情,表现基本一致。