在Bruce Eckel的“Thinking in C ++”的帮助下学习C ++,坚持练习32,第10章。 问题是如何更改链接顺序,Mirror :: test()调用对象m5返回false。 这是我的代码。
#ifndef MIRROR_H_
#define MIRROR_H_
class Mirror {
public:
Mirror() {logic_ = true; self_ = 0;};
Mirror(Mirror *ptr) {self_ = ptr; logic_ = false;};
bool test() {
if (self_ != 0) {
return self_->test();
} else {
return logic_;
}
};
private:
bool logic_;
Mirror *self_;
};
#endif // MIRROR_H_
任务
#include "mirror.h"
Mirror m1;
#include "mirror.h"
extern Mirror m1;
Mirror m2 (&m1);
#include "mirror.h"
extern Mirror m2;
Mirror m3 (&m2);
等等。最后,
#include "mirror.h"
#include <iostream>
extern Mirror m4;
Mirror m5 (&m4);
int main(int argc, char* argv[]) {
std::cout << m5.test() << std::endl;
}
m5.test()返回true。任务说,我应该更改链接顺序,m5.test()返回false。我试过用:
init_priority(优先级)
在标准C ++中,在命名空间范围内定义的对象保证按照严格按照它们的顺序初始化 给定翻译单元中的定义。不保证 翻译单元的初始化。但是,GNU C ++允许 用户控制定义的对象的初始化顺序 具有init_priority属性的命名空间范围,通过指定a 相对优先级,一个目前有界的常数积分表达式 介于101和65535之间。数字越小表示越高 优先级。
但没有运气。
完整的练习文字:
在头文件中,创建一个包含两个数据的类Mirror 成员:指向Mirror对象和bool的指针。给它两个 构造函数:默认构造函数将bool初始化为true和 镜像指针为零。第二个构造函数作为 参数指向Mirror对象的指针,它指定给它 对象的内部指针;它将bool设置为false。添加会员 function test():如果对象的指针非零,则返回 通过指针调用的test()的值。如果指针为零, 它返回了布尔。现在创建五个cpp文件,每个文件都包含 镜像头。第一个cpp文件定义了一个全局Mirror对象 使用默认构造函数。第二个文件声明了对象 第一个文件作为extern,并使用。定义一个全局Mirror对象 第二个构造函数,带有指向第一个对象的指针。继续 直到你到达最后一个文件,它也将包含一个全局文件 对象定义。在该文件中,main()应该调用test() 功能并报告结果。如果结果为真,请找出如何操作 更改链接器的链接顺序并将其更改为 结果是假的。
答案 0 :(得分:5)
将对象文件传递给链接器时,需要更改它们的顺序。尽管不同的编译器使用不同的方法,即它不可移植,但这对于顶层代码是合理的。此外,对于库,您通常无法控制对象的包含顺序。例如,如果你有
// file1.cpp
int main() {
}
// file2.cpp
#include <iostream>
static bool value = std::cout << "file2.cpp\n";
// file3.cpp
#include <iostream>
static bool value = std::cout << "file3.cpp\n";
...你链接两个这样的程序:
g++ -o tst1 file1.cpp file2.cpp file3.cpp
g++ -o tst2 file1.cpp file3.cpp file2.cpp
您将获得tst1
和tst2
的不同输出,例如:
$ ./tst1
file2.cpp
file3.cpp
$ ./tst2
file3.cpp
file2.cpp
总体道德是:不要这样做。那就是:不要使用全局对象。如果您觉得绝对需要使用全局对象,请将它们封装到函数中,例如:
Type& global_value() {
static Type value; // possibly with constructor arguments
return value;
}
这样,value
在第一次被访问时被初始化,并且在未构造时无法访问它。如果你封装了这样的所有对象,你可以保证它们以适当的顺序构造(除非你有一个循环依赖,在这种情况下它不能工作,你应该认真地重新考虑你的设计)。不幸的是,上述将对象封装到函数中的方法在C ++ 2003中并不是线程安全的。但是,它在C ++ 2011中是线程安全的。尽管如此,使用全局变量通常是有问题的,你肯定希望尽量减少它们的使用。
答案 1 :(得分:1)
我也在努力练习这个练习。
我设法编写了一个小的Python脚本来准备makefile条目,使用所有可能的目标文件排列链接和测试最终的可执行文件:
import itertools
for perm in itertools.permutations([1, 2, 3, 4, 5]):
print '\tg++ u0{0}.o u0{1}.o u0{2}.o u0{3}.o u0{4}.o -o $@ && ./main.exe'.format(*perm)
执行make make后,结果是所有可能的配置都产生了true
值。
这是因为保证在进入main
函数之前初始化所有全局(即静态)变量。
我定义了一个全局bool
变量,该变量保存test()
之前main
函数的结果,如下所示:
#include "mirror.h"
#include <iostream>
extern Mirror m4;
Mirror m5 (&m4);
bool result = m5.test();
int main(int argc, char* argv[]) {
std::cout << result << std::endl;
}
宾果!一些对象的排列在程序的输出处产生false
。
在调用任何可能的构造函数之前,所有静态变量都用零初始化。在本练习中,调用构造函数的顺序是线索。
如果在建立result
变量值时构造函数尚未初始化从属链中的任何对象,则结果为false
值(self_
值为0且{{ 1}} value为false,因此test函数返回logic_
)。
在输入false
函数之前评估result
变量时,链接器命令中的对象文件的可能性和顺序与结果有关。