1

I'm having a little trouble understanding why my code works the way it does (or doesn't work the way it ought to).

I'm trying to write (in C++) an interface that allows to use some functions operating on unordered_map from the standard template library in C. However, I'd also like to write a namespace that allows to use them in C++ as well.

What I'm asking is not how this can be thone in a different way, but why it works the way it does;

Let's say for a while that I need only two functions: to add elements and write the size of the map. The header is the following:

//project.h
#ifdef __cplusplus
extern "C" {
#endif

void add(int, int);

void give_size();

#ifdef __cplusplus
}
#endif

The source code:

//project.cc
#include <unordered_map>
#include <iostream>
#include "project.h"

using namespace std;

unordered_map<int, int> my_map;

void add(int arg, int val) {
    my_map.insert ({{arg, val}});
}

void give_size() {
    cout << my_map.size() << endl;
}

The interface for C++:

//cproject
namespace pro {
    #include "project.h"
}

and a test:

//test.cc
#include "cproject"
namespace {
    unsigned long test() {
        ::pro::add(1,2);
        ::pro::add(3,4);
        return 0;
    }
    unsigned long dummy = test();
}
int main() {
    ::pro::give_size();
    return 0;
}

And, for completeness, the Makefile:

g++ -Wall -std=c++11 -c -o project.o project.cc
g++ -Wall -std=c++11 -c -o test.o test.cc
g++ test.o project.o -o test

The problem is, of course, that running test outputs 0 instead of 2 - which means that the map disappears somewhere before the test's main.

I was thinking it might be some sort of static initialization order fiasco, however I don't find the attached solution very helpful, since I don't explicitly call objects from the project.cc file in test.cc.

I would appreciate any help with that issue.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Jytug
  • 1,072
  • 2
  • 11
  • 29

1 Answers1

2

Yes, it's the poorly named static initialization order fiasco. Poorly named because the C++ standard calls it "dynamic initialization"; "static initialization" is something different.

which means that the map disappears somewhere before the test's main

Not quite. The problem is that you use the map before it's there, adding values to it. Now it happens that for some map implementations, a zero-initialized state (and that is what is done to all global variables before any dynamic initializers run) is the same as what the default constructor does. So the code in test is executed first and tries to add things to the map, and the map's insert function works just fine, creating nodes, setting internal pointers to the nodes, etc.

Then the actual default constructor of the map runs, resetting those pointers to null, leaking and forgetting all the nodes you created. Your previous insertions are undone, and the map is empty again.

The solutions provided in your link would work; you implicitly call objects through the free functions, even if you don't do it explicitly. There's no real distinction. You still replace the global my_map in project.cc with a function that returns a reference to a function-level static (or pointer, depending on which exact solution you choose). The only difference is that you call this function not from within test.cc, but from within add and give_size.

As a side note, this whole global state thing is generally rather suspect. It's not thread-safe, and it makes it harder to understand what the program is doing. Consider not doing it this way at all.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • Thanks, that was very helpful. To be fair, that wasn't my idea, I'm studying for university class on c++, which often tends to provide unrealistic problems and suggest the least convenient ways of solving them ;-) – Jytug Nov 11 '15 at 19:32
  • @Jytug It's not at all unrealistic that you might encounter such code. I just hope you never write it. – Sebastian Redl Nov 11 '15 at 23:10