0

I have a solution in visual studio with two projects. The first project generates a static library. It has a macro set for the whole project, we'll call it 'ENABLE'. It also has a class as follows:

BadClass.h

class BadClass
{
public:
    BadClass()
    ~BadClass()
private:
#ifdef ENABLE
    std::mutex mutex1;
#endif
    std::mutex mutex2;
}

BadClass.cpp

BadClass() { }
~BadClass() { }

The second project generates an application which references the first project and uses its generated library. Within one of its functions I create a local instance of BadClass which gets destructed at the end of scope. I do NOT define the matching preprocessor macro 'ENABLE' in this project. I get an exception in the destructor of a mutex.

My assumption was that the constructor & destructor are defined in the library and will behave as the library is setup to compile. However it seems the header being referenced in the application project is changing their behaviours and now they are trying to destruct something that is not there. I don't quite understand why though. Is the application allowed to override the layout? Have I inadvertently created a weird duplicate symbol (so I have two BadClasses; one with the second mutex, one without), or something else?

ajso
  • 73
  • 7
  • 4
    You have a violation of the One Definition Rule. Two modules use what they believe to be the same class, but they don't agree on the size and layout of that class. When code from one module creates an object of that class, and then code from another tries to use that object, that second module essentially operates on random garbage - the object it's given has the wrong data in the wrong places. – Igor Tandetnik Aug 07 '23 at 03:47
  • 1
    By changing the interface you break the ABI. If you need to hide data in your library, consider the [Pimpl idiom](https://stackoverflow.com/a/8972757/1553090) – paddy Aug 07 '23 at 03:47
  • 1
    Even if constructor and destructor are defined in the library, the calling code needs to allocate sufficient space on the stack for the object, and for that, it needs to know at least the object's size. But because it sees a different definition than the library, it ends up allocating too little space, and you end up with a buffer overrun. – Igor Tandetnik Aug 07 '23 at 03:50
  • Why do you even want your class to look different in different setttings? What are you trying to do? Maybe there is a better solution. For example make an abstract baseclass for your BadClass first (an interface) and only use that interface from your client project (and provide a factory method to create an instance). Having such an abstract baseclass also will enable unit testing of client code (you can inject mocks/stubs implementing that interface). – Pepijn Kramer Aug 07 '23 at 04:49
  • @IgorTandetnik Ok, that is what I assumed was happening. My expectation was the sizing would be dictated in the library's code generated for the constructor and thus would destruct based on that. So although the application using it didn't see the same definition it wouldn't matter. – ajso Aug 07 '23 at 23:07
  • `sizeof(BadClass)` is a compile-time constant - it cannot involve a run-time call to the library, but is derived from the definition of the class as seen at that point. Anyway, no matter the mechanics, a program containing ODR violation exhibits undefined behavior (or is ill-formed no diagnostic required; I always forget which). Just say no. – Igor Tandetnik Aug 08 '23 at 01:12
  • @IgorTandetnik The mechanics are what is important to me because there was a gap my understanding of where something is happening (i.e. the compilation of implicit parts of the constructor/destructor). I stumbled upon the issue by accident, quickly pulling the library into a project I don't use it normally to check something else and hitting an unexpected exception because while I know of ODR, I don't has clear a view as I thought of what is happening "under the hood" when it is violated. – ajso Aug 09 '23 at 00:20
  • Consider keeping the structure consistent and use one of the mutex according to the flag ENABLE. – Minxin Yu - MSFT Aug 09 '23 at 09:13

0 Answers0