-1

I'm using some c++ code in a static library in a macOS App. The c++ code contains the following:

static map<char*, char*> aMap1;


__attribute__((constructor))
static void initialize() {
{
    static map<char*, char*> aMap2;
    printf("map1: %d, map2: %d\n", aMap1.begin() == aMap1.end(), aMap2.begin() == aMap2.end()); // prints map1: 0, map2: 1
}

aMap1.begin() does not equal aMap1.end(), even though aMap1.size() is 0. sizeof(aMap1) is 24, and the 24 bytes at &aMap1 are 0. Calling aMap1.clear() before the for loop makes begin() equal end().

aMap2.begin() does equal aMap2.end(), and the bytes are non-zero.

I was under the impression that aMap1 was initialized automatically, but this appears to be incorrect?

pmdj
  • 22,018
  • 3
  • 52
  • 103
Nick
  • 3,958
  • 4
  • 32
  • 47
  • What's up with the `free()` call? Do you `malloc()` somewhere, and if so, why? – Fred Larson Nov 09 '20 at 17:16
  • This isn't all the code of course, and later in the application elements are added to the map which are allocated with `malloc`. I've left it here to demonstrate that it crashes because it is entering the for loop with an empty list – Nick Nov 09 '20 at 17:18
  • 3
    What we really need is a [mcve]. – Fred Larson Nov 09 '20 at 17:19
  • It probably should not be all zeroes. So your actual problem is that the constructor is not being called? Something like https://stackoverflow.com/questions/6579668/linking-to-static-c-library-on-mac-os-x-global-object-constructor-from-librar –  Nov 09 '20 at 17:20
  • How come there is no `free(i->second);` or `delete i->second;`? (Or does something else own the SomeClass objects?) – Eljay Nov 09 '20 at 17:20
  • I've removed the excess code as it is not relevant. I can't reproduce in a standalone example. – Nick Nov 09 '20 at 17:24
  • @Nick For starters this declaration of std::map map aMap1; does not make a sense. How are you going to compare pointers? – Vlad from Moscow Nov 09 '20 at 17:32
  • 2
    @Nick `I can't reproduce in a standalone example` Then the problem is probably in the code that you removed. Remove only the parts that don't cause the example to reproduce. – eerorika Nov 09 '20 at 17:32
  • The changed code is not reproducing the bug for me. I suspect there is a bug elsewhere is the code that is causing undefined behavior, which may be exhibiting by stomping on the `aMap` object. – Eljay Nov 09 '20 at 17:32
  • 1
    [Can't reproduce](https://godbolt.org/z/M3xdbb) ([catch2 version](https://godbolt.org/z/rzMc6f)). Please provide [mcve]. – Marek R Nov 09 '20 at 17:33
  • someFunc was being called from an `__attribute__((constructor))` function, so it was being called before the aMap1 initializer. I've edited the question to demonstrate. – Nick Nov 09 '20 at 17:51
  • You cannot use an object before it is constructed. – Eljay Nov 09 '20 at 17:54
  • Yes, I'm aware – I just hadn't realised this was the cause. – Nick Nov 09 '20 at 17:55
  • post answer at answers and not within question. – ZF007 Nov 09 '20 at 21:36
  • I was unable to at the time as the question had been closed. It’s reopened now, so I’ve answered it. – Nick Nov 10 '20 at 08:57

2 Answers2

3

First of all, the __attribute__((constructor)) function is an implementation-defined compiler extension, so the C++ standard is no help in understanding its behaviour.

Clang's attribute reference doesn't actually mention it, so we have to go back to GCC's documentation, as clang implements this attribute in order to be compatible with the GNU dialects of C/C++/Objective-C.

The constructor attribute causes the function to be called automatically before execution enters main ().

No specified relative order vs C++ static initialisers so far. However, it soon becomes clear that the constructor function runs before static C++ initialisers:

You may provide an optional integer priority […] A constructor with a smaller priority number runs before a constructor with a larger priority number; […] The priorities for constructor and destructor functions are the same as those specified for namespace-scope C++ objects (see C++ Attributes).

If you then look at the documentation for the init_priority() attribute you find its priority argument to be:

[…] a constant integral expression currently bounded between 101 and 65535 inclusive. Lower numbers indicate a higher priority.

In other words, you can put multiple __attribute__((constructor)) functions in a specific relative order with priority 0…100, and statically initialised C++ objects in an order 101…65535 to override the default behaviour of objects being initialised in the order of definition in a compilation unit, and order being unspecified across compilation units. But this also means that the last constructor function always runs before the first C++ static initialiser.

This explains the behaviour you are seeing. Your constructor function runs before aMap1's constructor. aMap1 is automatically constructed, but not until after the constructor function has completed.

For ways to solve this problem, see the relevant entry in the C++ FAQ.

pmdj
  • 22,018
  • 3
  • 52
  • 103
0

someFunc was being called from an __attribute__((constructor)) function, so it was being called before the aMap1 initializer. To resolve the issue I moved the code to a dispatch block. The function could also be called from main instead of being marked as a constructor.

static map<char*, char*> aMap1;

__attribute__((constructor))
static void initialize() {
    dispatch_async(dispatch_get_main_queue(), ^{
        static map<char*, char*> aMap2;
        printf("map1: %d, map2: %d\n", aMap1.begin() == aMap1.end(), aMap2.begin() == aMap2.end()); // prints map1: 1, map2: 1
    });
}
Nick
  • 3,958
  • 4
  • 32
  • 47
  • The question as edited is reproducible. I've edited my answer to provide a solution. – Nick Nov 10 '20 at 09:47