In Soviet Russia, pointers dangle you

If words like “C++”, “12 inch rotary debugger”, “dangling pointers” (phhwwoooarr!), “caffeine drip” and “early grey hair” are baffling, confusing or frightening to you, it’s probably best that you skip this post. It’s all technical, boring and generally an opportunity for me to document a mistake that I’ve made in the hope that writing about it will embed it so far in my brain that I will never make it again.

C++ is a cracking computer language with a simple philosophy: it’s all YOUR problem. Allocation of memory? Your problem. Freeing resources? Your problem. Knowing what everything is, where everything is and what state it is in? Your problem. C++ and its cousins, C and Objective C, are relatively straightfoward because they do nothing behind your back. They’re completely faithful. Indeed, you could send student C++ out for the evening wearing a shockingly short skirt to get completely plastered and be 100% sure that she’d behave like the perfect lady. For high-performance, real-time simulation software, games, firmware and an endless bundle of other things it’s a fastastic choice – there’s nothing sneaky going on and you’re in complete control.

The down side, though, is that you have to remember to handle the whole caboodle yourself. Memory management, pointers to objects, etc., all become the programmer’s problem. This can be great, as everything behaves logically and as it should, but it is also an incredible source of bugs and issues for novice and experienced programmers alike. Those that have been doing it for a fair while develop strategies to avoid making common errors and build libraries of their own to help ensure that some of the lowest level crapola can be farmed off to well tried and tested code. Some of my list, for example, you can find here.

And then there’s STL – the Standard Template Library. It’s part of the C++ standard and it adds some truly fantastic higher-level functionality to C++: the kind of functionality that users of almost all other computer languages (except, perhaps, CECIL and assembly language) take for granted. With this handy help, though, comes a cost.

C++ and STL: Cousins in crime!

C++: never does anything behind your back, or indeed, anything at all unless you command it. STL, though, does all sorts of things without you knowing but is better at parties and drinks like a fish.

STL is like C++’s naughty cousin. You can rest assured that if C++ comes home in a police car or ends up in hospital, it’s STL’s fault. She’s the sort of person that is a walking temptation machine: “Here, C++, try these pills, they’ll make you feel great!”.The reason for this is quite simple: STL does do things behind your back in the name of giving you a hand: “Here, I’ll hold the door open for you.” strollstrollstroll “That’ll be a dollar”.

It is, though, a price that generally we’re all happy to pay. Fine, there’s a time and a place and sometimes STL’s box of goodies isn’t appropriate but it’s a fantastic time saver and allows the developer to concentrate on solving the problem at hand rather than reinventing wheels themselves. One of the features that we get in STL are the “Standard Containers”. These are, put simply, variable size arrays. Containers of things that expand and shrink all by themselves or at your command. You can delete items, add items, loop through them and all sorts. One of the most popular is the std::vector. Here’s a simple example:

std::vector vecIntegers;
vecIntegers.push_back(10);
vecIntegers.push_back(11);
vecIntegers.push_back(12);

We now have a list of three integers: the numbers 10, 11 and 12. We can do this:

vecIntegers.erase(vecIntegers.begin());

… and pop! The first one has gone (the experienced STL users are currently frothing and steaming saying “hey, you should be using a deque for that, because vectors are designed for adding and removing at the end only, doofus”. I know, but one likes to keep examples simple without having to introduce the whole of STL, so forgive me.

Here’s another example where we use a vector to store a more complex object: one that has names and ages:

// Declare a structure we'll use to group name and age:
struct NameAndAge
{
	// This, the string type, is also an STL goodie: C++ has no string type as a 
	// basic type:
	std::string	name;		

	// Hahaha! See here for the reason that the C99/C++0x type is used:
	uint16_t		age;	      // Age can't be negative.. or CAN IT?

	// Object constructors to help us:
	NameAndAge() 
	{ return; }
	NameAndAge(const std::string& name_, const uint16_t age_)
	: name(name_), age(age_) 
	{ return; }
	NameAndAge(const NameAndAge& rhs)
	: name(rhs.name), age(rhs.age)
	{ return; }

	// Assignment operator so that we don't cock up the strings as bitwise copies
	// of this object using = will go tits up depending on the std::basic_string 
	// implementation:
	NameAndAge& operator = (const NameAndAge& rhs)
	{ name = rhs.name; age = rhs.age; return (*this); }
};

// Declare a vector type of NameAndAges:
typedef std::vector<NameAndAge> VecNameAndAge;
VecNameAndAge vecNamesAndAges;

// Add some:
vecNamesAndAges.push_back(NameAndAge("Rattlesnake", 10));
vecNamesAndAges.push_back(NameAndAge("The Germs", 2));
vecNamesAndAges.push_back(NameAndAge("Irregular Pigeon", 85));
vecNamesAndAges.push_back(NameAndAge("Giraffe family", 22));

Even if you’re not a programmer, hopefully you can get the gist of that. We end up with a vector containing four objects for four people: Rattlesnake, The Germs, Irregular Pigeon and the Giraffe Gamily. So much wonderful stuff that would normally be your problem has been handled by the vector: it has allocated the memory for you, it has copied the objects across, it has resized itself when it ran out of room… oh, the joy! The joy.

Now, I’m going to start on the road to the bug that took me the best part of a day to track down because I’d made an assumption and not documented that assumption adequately (see item 8 on my list). Here’s another brief snippet that goes in after the above one:

typedef std::vector<NameAndAge*> VecPointersToNameAndAge;
VecPointersToNameAndAge vecPointersToNameAndAges;

// Create another vector of POINTERS TO the items on the first one:
for (VecNameAndAge::iterator itNameAndAge = vecNamesAndAges.begin(); 
	itNameAndage != vecNamesAndAges.end(); ++itNameAndAge)
{
	NameAndAge& thisNameAndAge = *itNameAndAge;
	vecPointersToNameAndAges.push_back(&thisNameAndAge);
}

Danger! Danger! Danger! Wooo-woooo-woooo-woooo! Frankly, if I was doing something like this, alarm bells should have gone off like mad but I had a special case (aren’t they all?) and it was a really, really good way of getting around the problem I had: many objects of different types, all of which inherited a serialisation base class, all of which needed to be serialised… so, after creating all the objects (which were constant, created once at load time and they never changed after that) I simply dumped pointers to them (pointers are the ‘address of’ an object) onto another vector. Then, when it came to data-save-time, I could whizz through the whole lot dead quick and serialise ’em.

And, do you know what, I would have got away with it, too, if it wasn’t for for the unchangable changing.

When you write an application which has a long life-span (many releases over many years) then eventually, regardless of any clairvoyant planning, it will end up doing things that you never imagined. In my case, the bold bit a couple of paragraphs up changed. I had a new feature and it would save me an enormous amount of time if I just inherited an existing object, made a couple of changes to it through overloading and then dumped it onto the array of that object type.

Bingo, it all worked fine.

For a few hours.

Then I had a crash. That’s odd, it was in the serialisation code. I’d not touched the serialisation code in months so what the flying flock of pigeons was going on? Ok, tried a different configuration file (this particular application changes into different applications depending on its configuration – it’s a doppelapplicationgänger) and it worked OK. Right, back to the configuration I was working on, all seems OK… can’t have fixed itself, but I figured I’d wait until the next crash. The data was inconclusive last time (by inconclusive, I mean “I refused to believe it”).

Ah yes, crash number 2. Ok, breakpoints and started stepping through the serialisation code. For some odd reason, one of the objects that was being written was corrupted. So I looked at the list of objects: they were all fine, but the one that was on the serialisation list wasn’t there. How’s that possible? I put the pointer to it on at load-time, I don’t remove anything from the main object list so how could that pointer become invalid?

Honestly, what blog would be complete without this classic Internet picture?

And then I remembered why I had copy constructors: to control how the objects copy themselves when the vector reallocates its memory. This was the Picard face-palm moment: adding the object had exceeded the memory that the vector had allocated so it did its groovy magic behind the scenes: it allocated new memory, copied everything across and continued. This, of course, meant that there was a shiny new set of clones all at new memory locations. My pointers to the objects were pointing to the old objects. The ones that were not there any more. Dangling pointers to random rubbish somewhere – the kind of random rubbish that makes programs go pop. STL had simply moved the furniture around and my code had tried to sit on the sofa at the old position whilst carrying a tray of priceless FabergĂ© Eggs.

The C++ standard is quite clear on all this (see 23.2.4.2 and more) and much has been said on the subject elsewhere, I just forgot some of the inner workings of a very complex application. I’d not commented the pointer-vector adequately with warnings and I’d not commented the object-vectors that they were to be treated as constants after loads. I could have controlled the reallocations to avoid this by using vector::reserve() to pre-reserve space before allocations would occur, because, as the standard notes:

Reallocation invalidates all the references, pointers, and iterators referring to the elements in the sequence. It is guaranteed that no reallocation takes place during insertions that happen after a call to reserve() until the time when an insertion would make the size of the vector greater than the size specified in the most recent call to reserve().

… but, well, I’d forget I’d done that and one day the application would have more objects than I’d reserved and cryptically crash again. Then I’d lose another couple of days as a result of mixing and matching the way things had been done.

No matter how much care you take, there is a cost of doing things in a hurry. There is an even bigger cost of not documenting why you did things the way you did them. In this case, I solved it properly: took a couple of days out of the main schedule to fix it under the hood, and fix it good so that I could not be baffled by this again. I also wrote this blog post in case I do make this mistake again. Hopefully it’ll pop up on google and I can save myself spending a wonderful sunny spring day with bloodshot eyes staring at the debugger mumbling “that simply can’t be possible. It can’t be possible, it just can’t be possible…”


PS: If you’re a C++ programmer and you don’t have these books, then may I suggest you order them now. They’re incredibly helpful and help you develop good defensive strategies that will ensure you avoid precisely the kind of issue I just had

PPS: I have not actually compiled any of the code in this article, so if it fails, meh, you get what you pay for 🙂

This entry was posted in Software development and tagged , , , , , , . Bookmark the permalink.

One Response to In Soviet Russia, pointers dangle you

  1. Pingback: Stop the universe, someone knows everything | Cobras Cobras