C++,那些可爱的小陷阱(一)

此系列是为那些读过TC++PL或者具有类似水平的同学准备的,作为系列的第一篇以及有趣的热身,我们来看一个链接问题:

D1.cpp

#include <stdio.h>
struct X {
    X(
int);
    X(
intint);
};
X::X(
int = 0int = 0) { printf("1\n"); }
class D: public X {
}; 
int minus(int a,int b)
{
    D d;
    
return a-b;
}

D2.cpp 

#include <stdio.h>
struct X {
    X(
int);
    X(
intint);
};
X::X(
int = 0) { printf("2\n"); }
class D: public X {
};
int add(int a,int b)
{
    D d;
    
return a+b;
}

用来执行的main.cpp:

#include <stdio.h>
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++相同的事情,表现基本一致。

posted @ 2009-11-17 20:36  winter-cn  阅读(3115)  评论(22编辑  收藏  举报