Question

Learning C++ with help of "Thinking in C++" by Bruce Eckel,stuck in exercise 32, chapter 10. The question is how to change link order, that Mirror::test() called for object m5 return false. Here is my code.

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_

task

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);

and so on. Finally,

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() returns true. The task says, that I should change linking order, that m5.test() returns false. I have tried to use:

init_priority (priority)

In Standard C++, objects defined at namespace scope are guaranteed to be initialized in an order in strict accordance with that of their definitions in a given translation unit. No guarantee is made for initializations across translation units. However, GNU C++ allows users to control the order of initialization of objects defined at namespace scope with the init_priority attribute by specifying a relative priority, a constant integral expression currently bounded between 101 and 65535 inclusive. Lower numbers indicate a higher priority.

but no luck.

Full exercise text:

In a header file, create a class Mirror that contains two data members: a pointer to a Mirror object and a bool. Give it two constructors: the default constructor initializes the bool to true and the Mirror pointer to zero. The second constructor takes as an argument a pointer to a Mirror object, which it assigns to the object’s internal pointer; it sets the bool to false. Add a member function test( ): if the object’s pointer is nonzero, it returns the value of test( ) called through the pointer. If the pointer is zero, it returns the bool. Now create five cpp files, each of which includes the Mirror header. The first cpp file defines a global Mirror object using the default constructor. The second file declares the object in the first file as extern, and defines a global Mirror object using the second constructor, with a pointer to the first object. Keep doing this until you reach the last file, which will also contain a global object definition. In that file, main( ) should call the test( ) function and report the result. If the result is true, find out how to change the linking order for your linker and change it until the result is false.

Was it helpful?

Solution

You'll need to change the order of the object files when passing them to the linker. This works reasonable for the toplevel code although different compilers use different approaches, i.e., it isn't portable. Also, for libraries you generally can't control the order in which the objects are included. For example, if you have

// 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";

... and you link two programs like this:

g++ -o tst1 file1.cpp file2.cpp file3.cpp
g++ -o tst2 file1.cpp file3.cpp file2.cpp

you will get different output for tst1 and tst2, e.g.:

$ ./tst1
file2.cpp
file3.cpp
$ ./tst2
file3.cpp
file2.cpp

The overall moral is: don't do it. That is: don't use global objects. If you feel you absolutely need to use global objects, encapsulate them into functions, e.g.:

Type& global_value() {
    static Type value; // possibly with constructor arguments
    return value;
}

This way, value is initialized the first time it is accessed and there is no way to access it while it isn't constructed, yet. If you encapsulate all objects like this, you can guarantee that they are constructed in an appropriate order (unless you have a cyclic dependency in which case it can't be made to work and you should seriously rethink your design). The above approach encapsulating objects into function is, unfortunately, not thread-safe in C++ 2003. It is thread-safe in C++ 2011, however. Still, use of global variable is generally problematic and you definitely want to minimize their use.

OTHER TIPS

I was struggling with this exercise too.

I managed to write a small Python script to prepare makefile entries that link and test final executable using all possible permutation of object files:

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)

After executing my make process it turned out, that all of the possible configurations yielded true value.

This is due to the fact, that all global (i.e. static) variables are guaranteed to be initialized before entering main function.

I defined a global bool variable that holds result from a test() function before main, something like this:

#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;
}

Bingo! Some of the objects' permutations yielded false at the progam's output.

All static variables are initialized with zeroes before any of their possible constructors are called. In this exercise, the order in which constructors are called is the clue.

If any object in the depencence chain has not been initialized by constructor when a value of result variable is established, the result is false value (self_ value is 0 and logic_ value is false, so test function returns false).

When a result variable is evaluated before entering main function, there is such possibility and order of object files in a linker command has something to do with the result.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top