Bitten by the one definition rule

| 0 Comments

I've just wasted 20 minutes or so on a nasty bug. I'd added a bit of test code and suddenly some other tests were failing but the reason for the failure seemed to be that a class's vtable was getting screwed up and a virtual function was jumping off into hyperspace...

After some time stepping through the code for a while I could see that a function that should have been incrementing a data member was in fact stomping over part of the vtable. It looked like the compiler was failing to allocate the correct size for the object, which seemed unlikely; my view on things that could be compiler bugs is simple, no matter how much it looks like a compiler bug the chances are it's just me being stupid. I was...

I'd a class that caches connections. It's called CConnectionCache and has some virtual functions that are called when various events occur; the idea being that derived classes can override them if they're interested in knowing about the events. The test needed to know about the events so I created a CTestConnectionCache class in the cpp file that held the test code itself. This test cache derived from the cache under test and implemented the notification functions and took note of the notifications as they happened, all of the function bodies were tiny and were declared in the class declaration.

Everything was fine until I wrote the test for the CConnectionCacheWithThreadedPurge (no, really...). This class derives from CConnectionCache and from CThread and implements a periodic purge of the cache using a thread and an event. The cache itself exposes a function that purges items that are older than a supplied value, the threaded purge class simply calls this function with a value every so often. Of course I needed a test shim to allow me to collect the events and CTestConnectionCacheWithThreadedPurge seemed awfully long so I called the class CTestConnectionCache. As both classes were declared and defined in the cpp file that used them things seemed to work except for my wierd bug...

I'd been bitten by the One Definition Rule. In a C++ program you must only one definition of a particular type. You can define the type in multiple files if you want, but they must be the same... I'd broken that rule by calling both test shims by the same name. The compiler hadn't spotted, possibly because it had inlined the methods (after working out what was going on I pulled one of the methods out of the class declaration and I got a duplicate symbol linker error...).

So, the compiler had two objects called the same thing with different data members. It seemed to allocate the right sized object in each file but when it wired up the vtables it always used the methods from the smaller object, the first one that was compiled, and this meant that when some methods were called they were the virtual methods of the wrong object and assumed the wrong memory layout for the object and trashed the vtable...

I guess that's what "undefined behaviour" means ;)

Leave a comment