2

在 Bruce Eckel 的“Thinking in C++”的帮助下学习 C++,卡在练习 32,第 10 章。问题是如何更改链接顺序,Mirror::test() 调用对象 m5 返回 false。这是我的代码。

镜像.h:

#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_

任务

一.cpp

#include "mirror.h"
Mirror m1;

二.cpp

#include "mirror.h"
extern Mirror m1;
Mirror m2 (&m1);

三.cpp

#include "mirror.h"
extern Mirror m2;
Mirror m3 (&m2);

等等。最后,

五.cpp

#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() 返回真。任务说,我应该更改链接顺序,m5.test() 返回 false。我曾尝试使用:

init_priority(优先级)

在标准 C++ 中,保证在命名空间范围内定义的对象按照严格按照它们在给定翻译单元中定义的顺序进行初始化。不保证跨翻译单元的初始化。但是,GNU C++ 允许用户通过指定相对优先级来控制在命名空间范围内定义的对象的初始化顺序 init_priority 属性,这是一个当前介于 101 和 65535 之间的常量整数表达式。较低的数字表示较高的优先级。

但没有运气。

完整的练习文本:

在头文件中,创建一个包含两个数据成员的 Mirror 类:一个指向 Mirror 对象的指针和一个 bool。给它两个构造函数:默认构造函数将 bool 初始化为 true,将 Mirror 指针初始化为零。第二个构造函数将指向 Mirror 对象的指针作为参数,并将其分配给对象的内部指针;它将 bool 设置为 false。添加成员函数test():如果对象的指针非零,则返回通过指针调用的test()的值。如果指针为零,则返回布尔值。现在创建五个 cpp 文件,每个文件都包含 Mirror 标头。第一个 cpp 文件使用默认构造函数定义了一个全局 Mirror 对象。第二个文件将第一个文件中的对象声明为extern,并使用第二个构造函数定义了一个全局Mirror对象,带有指向第一个对象的指针。继续这样做,直到到达最后一个文件,该文件也将包含一个全局对象定义。在该文件中,main() 应该调用 test() 函数并报告结果。如果结果为真,请找出如何更改链接器的链接顺序并更改它,直到结果为假。

4

2 回答 2

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

您将获得和的不同输出tst1tst2例如:

$ ./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 中它是线程安全的。尽管如此,使用全局变量通常是有问题的,您肯定希望尽量减少它们的使用。

于 2012-09-02T13:47:27.567 回答
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 过程后,结果证明所有可能的配置都产生了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,logic_值为假,所以测试函数返回false)。

result进入main函数之前评估变量时,存在这种可能性,并且链接器命令中目标文件的顺序与结果有关。

于 2013-05-29T23:51:51.600 回答