c ++ static-init-fiasco示例

时间:2012-09-02 13:33:31

标签: c++ static-initialization

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

mirror.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_

任务

one.cpp

#include "mirror.h"
Mirror m1;

two.cpp

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

three.cpp

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

等等。最后,

five.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()返回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()   功能并报告结果。如果结果为真,请找出如何操作   更改链接器的链接顺序并将其更改为   结果是假的。

2 个答案:

答案 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

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

答案 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变量时,链接器命令中的对象文件的可能性和顺序与结果有关。