我一直试图找出g++
的某些边界,特别是链接(C ++)目标文件。在发出询问之前,我发现了以下的好奇心,我尽量压缩。
档案common.h
#ifndef _COMMON_H
#define _COMMON_H
#include <iostream>
#define TMPL_Y(name,T) \
struct Y { \
T y; \
void f() { \
std::cout << name << "::f " << y << std::endl; \
} \
virtual void vf() { \
std::cout << name << "::vf " << y << std::endl; \
} \
Y() { \
std::cout << name << " ctor" << std::endl; \
} \
~Y() { \
std::cout << name << " dtor" << std::endl; \
} \
}
#define TMPL_Z(Z) \
struct Z { \
Y* y; \
Z(); \
void g(); \
}
#define TMPL_Z_impl(name,Z) \
Z::Z() { \
y = new Y(); \
y->y = name; \
std::cout << #Z << "(); sizeof(Y) = " << sizeof(Y) << std::endl; \
} \
void Z::g() { \
y->f(); \
y->vf(); \
}
#endif
使用a.cpp
g++ -Wall -c a.cpp
#include "common.h"
TMPL_Y('a',char);
TMPL_Z(Za);
TMPL_Z_impl('a',Za);
使用b.cpp
g++ -Wall -c b.cpp
#include "common.h"
TMPL_Y('b',unsigned long long);
TMPL_Z(Zb);
TMPL_Z_impl('b',Zb);
文件main.cpp
已编译并与g++ -Wall a.o b.o main.cpp
#include "common.h"
struct Y;
TMPL_Z(Za);
TMPL_Z(Zb);
int main() {
Za za;
Zb zb;
za.g();
zb.g();
za.y = zb.y;
return 0;
}
./a.out
的结果是
a ctor
Za(); sizeof(Y) = 8
a ctor // <- mayhem
Zb(); sizeof(Y) = 12
a::f a
a::vf a
a::f b // <- mayhem
a::vf b // <- mayhem
现在,我原本希望g++
给我一些讨厌的名字,试图将a.o
和b.o
联系在一起。特别是za.y = zb.y
的任命是邪恶的。不仅g++
根本不抱怨,我希望它将具有相同名称(Y
)的不兼容类型链接在一起,但它完全忽略b.o
中的辅助定义(resp。 b.cpp
)。
我的意思是我没做什么 sooo 远远不够。两个编译单元可以对本地类使用相同的名称是非常合理的,尤其是。在一个大项目中。
这是一个错误吗?任何人都能对这个问题有所了解吗?
答案 0 :(得分:5)
引用Bjarne Stroustrup的“The C ++ Programming Language”:
9.2链接
必须在所有翻译单元中一致地使用函数,类,模板,变量,名称空间,枚举和枚举器的名称,除非它们明确指定为本地。
程序员的任务是确保每个名称空间,类,函数等在其出现的每个翻译单元中正确声明,并且所有引用同一实体的声明都是一致的。 [...]
答案 1 :(得分:1)
在您的示例中,您可以将Y的定义放在匿名命名空间中,如下所示:
#define TMPL_Y(name,T) \
namespace { \
struct Y { \
T y; \
void f() { \
std::cout << name << "::f " << y << std::endl; \
} \
virtual void vf() { \
std::cout << name << "::vf " << y << std::endl; \
} \
Y() { \
std::cout << name << " ctor" << std::endl; \
} \
~Y() { \
std::cout << name << " dtor" << std::endl; \
} \
}; \
}
这实际上为每个编译单元创建了一个唯一的命名空间,实际上,您具有唯一的Y,并且链接器将能够正确关联。
至于声明
za.y = zb.y;
当然,这仍然会产生不可预测的结果,因为这两种类型是不相容的。
答案 2 :(得分:0)
在许多情况下,C ++编译器不需要捕获错误。其中许多是例如通过一次分析一个翻译单元无法检测到的错误。
例如,如果您只是在头文件中声明
,则不会使用模板制作复杂的案例void foo(int x);
然后在不同的翻译单元中为函数提供两个不同的定义,C ++编译器不是必需,以便在链接时给出错误。
请注意,这显然不是不可能发生的错误,因为实际上甚至可能存在两个不同的标题,其中全局函数具有相同的签名,而项目的一部分使用一个标题,而另一个标题使用另一个标题。
如果您在具有不同声明和不同实现的两个不同头文件中声明某个类Foo
,则会发生同样的情况。
这种滥用命名只是编译器无需捕获的一种错误。