-1

When I declare a member variable of type std::map in one compilation unit but not the other, I get a segmentation fault when the containing object is being destructed. When I do the same with std::vector, it works just fine.

It was definitely a bug in my case, and I fixed it, but I'm still wondering what's causing the crash.

Here's the code:

foo.hpp:

#ifdef DECLARE_MAP
#include <map>
#endif
#ifdef DECLARE_VECTOR
#include <vector>
#endif
#include <string>

class Foo {
public:
    Foo();

private:
#ifdef DECLARE_MAP
    std::map<std::string, std::string> m;
#endif
#ifdef DECLARE_VECTOR
    std::vector<std::string> v;
#endif
};

foo.cpp:

#include "foo.hpp"

Foo::Foo()
{
}

main.cpp:

#include "foo.hpp"

int main()
{
    Foo f;
}

Works fine with DECLARE_VECTOR:

g++ -DDECLARE_VECTOR -c -o foo.o foo.cpp
g++ -o main main.cpp foo.o

But causes a segmentation fault with DECLARE_MAP:

g++ -DDECLARE_MAP -c -o foo.o foo.cpp
g++ -o main main.cpp foo.o

Reproducible in clang 4.0 and gcc 4.4.7.

Can anybody explain why this happens?

Mankarse
  • 39,818
  • 11
  • 97
  • 141
fhd
  • 3,998
  • 2
  • 23
  • 18
  • Do you realize that `sizeof(Foo)` is different in `main.cpp` and in `foo.cpp`? You should compile everything using the appropriate `-D` flag. – mfontanini Apr 04 '13 at 13:56
  • 3
    You are heavily violating the ODR, what did you expect? – PlasmaHH Apr 04 '13 at 13:56
  • 1
    You're breaking the rules of the language (the ODR as PlasmaHH pointed out). Attempting to explain what happens afterwards isn't really constructive. – Luchian Grigore Apr 04 '13 at 14:00
  • I know that it's a bug, like I said above. But I'm still wondering why this fails for map and, apparently, nothing else. From all I know, the constructor/destructor in foo.o should take care of allocation/deallocation and it should work. – fhd Apr 04 '13 at 14:01
  • @fhd no. When you break the rules, all gloves are off. When you dereference a NULL pointer, the runtime can crash or it can appear to work. Can you guarantee that running that code 100000 with vector won't crash? It could. – Luchian Grigore Apr 04 '13 at 14:03
  • @LuchianGrigore I'm reluctant to just declare the effects beyond human understanding and forget about it. Since both GCC and Clang seem to show exactly the same behaviour, I'm thinking there could be an explanation. – fhd Apr 04 '13 at 14:08
  • @fhd a deleted answer said that `gcc 4.7.2` crashed in both cases. – Luchian Grigore Apr 04 '13 at 14:09
  • @LuchianGrigore Okay okay, I give up. – fhd Apr 04 '13 at 14:12

2 Answers2

2

The problem is because you're doing the compilation in two steps and only in the first step do you define DECLARE_MAP or DECLARE_VECTOR. This results in two translation units that look like this:

  • foo.cpp translation unit:

    // Contents of <map>
    // Contents of <string>
    
    class Foo {
    public:
        Foo();
    
    private:
        std::map<std::string, std::string> m;
    };
    
    int main()
    {
        Foo f;
    }
    
  • main.cpp translation unit:

    // Contents of <map>
    // Contents of <string>
    
    class Foo {
    public:
        Foo();
    
    private:
    };
    
    int main()
    {
        Foo f;
    }
    
    int main()
    {
        Foo f;
    }
    

As you can see, each translation unit has a different definition of Foo. The first has Foo containing a std::map and the second has it without.

This violates the following rule:

There can be more than one definition of a class type [...] in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then

  • each definition of D shall consist of the same sequence of tokens; and

  • [...]

If the definitions of D do not satisfy these requirements, then the behavior is undefined.

As you can see, you have undefined behaviour. Yes, it might appear to work when you have DECLARE_VECTOR, but that is only by chance. It still has undefined behaviour, so anything can happen.

Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
1

You're violating the One Definition Rule, resulting in Undefined Behaviour. This means literally anything can happen. Which includes working for some types involved and not others, or working only when the moon is full.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • I'm not quite willing to write this off as inexplicable voodoo. Clang and GCC are apparently showing the same behaviour, doesn't that mean there could be a reasonable explanation for this? – fhd Apr 04 '13 at 14:09
  • @fhd It's not "inexplicable voodoo." The standard simply says "If you violate the rules, you get Undefined Behaviour. This standard doesn't impose any requirements on Undefined Behvaiour." This means compiler authors don't even have to care about what happens during UB, so what you get is usually just a pretty much random consequence of the compiler's implementation details. Both gcc and clang are open-source; if you want to know why it behaves this way, you can look for yourself. – Angew is no longer proud of SO Apr 04 '13 at 14:12
  • Okay, I give up :) Was hoping to get a straightforward explanation, but I guess there is none. – fhd Apr 04 '13 at 14:17
  • Answer of sftrabbit explains it "straightforward" enough for most of the C++ speaking population. – SChepurin Apr 04 '13 at 14:20