This article is more than 1 year old

Variables Won't Constants Aren't:

A random walk through constant values in C++

I first read the lament to the capricity of programming in the title of this piece in Creative Computing, years before C was devised. But it’s still true that there is no way, not even one, of being absolutely sure that a value you set in C or C++ won’t change.

This is despite the fact that in the 30 years following the creation of C, C and C++ have acquired new keywords, features and libraries and have been generally enhanced to a point where my ancient battered copy of Kernighan and Richie hides in a cupboard and refuses to even sit on the desk where Visual C++ 2005 lives.

C++ gives the programmer a menu of methods to help him/her protect his/her data. At least, that’s what’s implied if you don’t read Stroustrup too closely. It’s not that you’re being lied to; it’s just that someone is being rather economical with the truth.

It’s tempting to think that, given:

const int MyConst = 42;
double MyVals[MyConst];
for (int i=0; i != MyConst; i++) {
    // ...
}

you won’t ever go off the end of the array. Tempting, but wrong; and it is quite trivial to make it happen:

int& x = (int&) MyConst;
x = 43;

The default in C++ is variables, not constants; and thus you probably won’t even get a warning if you do such a thing. And so you implicitly, but silently, cast away constness.

This is one of the many reasons that classical C style casts are frowned upon in most coding standards. But for most programmers these days, the only time that they ever review someone else’s code is at interview [and not always then – Ed] - so probably no-one will notice if they aren’t following standards.

The accepted, but still rather worrying, technique is:

int& CPlusCast = const_cast<int&>(MyConst);

[Am I the only one who thinks this cast has the wrong name? – Dominic]

OK, but when you’re hunting a bug, then you might well be tempted to think that this means that MyConst can’t change. Yet it might or might not mean that; and of course now that constness is gone, MyConst can be passed into a function with a variable parameter signature.

Even specifying:

void Increment(const int& x)

doesn’t mean it won’t ever change - as we’ve seen. More than that, since threads are coming back into mainstream programming, your MyConst value may change randomly at some random point during execution.

You can always try to specify that a function mustn’t change any class variables, which would be really handy if your class is supposed to be inherited from, but again, this can be changed later.

Const functions only protect members of the class, and the language also stops you getting around this by getting the const function to call a non-const member. But the syntactic protection does not extend to globals; although if you use them you’re not really into this level of discipline anyway.

And it just wouldn’t be C++ if there weren’t yet another workaround available. So, we can specify member variables as mutable, and thus make them prey to random change by the sort of people who think “const member function” actually means that only externally observable states should remain constant; and that the function should be free to meddle with its private housekeeping variables.

But sometimes you need to expose some internal structure like the STL string or MFC CString, so that you can use the character buffer with older code. STL does the right, but fallible, thing and so the c_str() member returns a const pointer; which at least takes some effort if you’re determined to corrupt it. MFC doesn’t even try.

STL also has const_iterators, which of course aren’t constant (because there wouldn’t be much point having an index that didn’t change); but the idiom is that you can’t directly change the values of the collection, which is both safer and potentially rather faster. An iterator that you know won’t change things can not only be cached, but is also rather easier to multithread.

Now, so far I’ve only complained about intentional changes to variables. We also have to live with the possibility of memory being trashed, since const is just a syntactic sugar coating for the bitter reality of the underlying memory model. Hence, in Visual C++ 2005, I can illustrate a simplified example of a const value being trampled on simply for being too close to another on the stack:

void HeisenBug(void) {
    char a = 42;
    char Bug[5];
    strcpy(Bug, "123456789abcdefghijk");
}

Note that this mucks up the value “before” the Bug array; and that this is compiler-dependent. This effect may be anything from benign to subtly malicious.

Of course, if you’re in the IDE of a decent debugger like Visual Studio 2005, then this effect will be caught; but if you’re in production code, you simply get a silent change of the value. The debug code will contain traps for this, but these disappear in release; and of course optimised code is the most vulnerable. Live fast die young. And so we get a “Heisenbug”, an error that changes when you try to observe it in the debugger. This could still happen if the “constant” string was #defined, because the value has to be somewhere, even if the compiler has folded long strings.

Now, modern operating systems allow you to write-protect blocks of memory; but that is both non-portable and deeply ugly - and you’ve probably worked out for yourself that it can be turned off by the same API. In older compilers, it is also entirely possible for explicit constants to be changed; and Microsoft actually used to advise developers to write code such as:

char* Fname = " ImpliedVol"; // Note the space at the start of the name
Fname[0] = (BYTE) strlen(Fname);

to cope with BASIC-style APIs that required the length of the string to be in the first byte. It worked - yes, really; a large percentage of the world’s derivative pricing makes its way into dealing systems that way - but Microsoft now makes all literal strings read only, which is more elegant. Although it does mean that, a whole pile of its example code now won’t work anymore.

And, of course, any memory value can be hit by a stray pointer.

However, it’s not all bad as const can also be a big hint to the compiler to make better code. If you have a big vector of some class, then default pass by value means that a simple utility algorithm like:

template <typename T> double Sum(vector<T> vec)

may end up calling the constructor many times. As it happens, STL is smart enough to keep this to a minimum; but if we write:

template <typename T> double Sum(const vector<T>& vec)

then we are telling the compiler quite explicitly that vec is basically just an alias to the variable in the calling function; and then it’s much easier for the compiler to generate direct inline access. Thus, const references can make your code both faster and more robust.

So, to summarise, C++ does do constants, and often they are useful, but they are never completely trustworthy. ®

More about

TIP US OFF

Send us news


Other stories you might like