What the language standards promise and don't promise can make all the difference.
To ask Pete a question about C or C++, send e-mail to petebecker@acm.org, use subject line: Questions and Answers, or write to Pete Becker, C/C++ Users Journal, 1601 W. 23rd St., Ste. 200, Lawrence, KS 66046.
Q
I just recently saw the following code in a discussion on error handling:
1 void f( /* arguments */ ) 2 { 3 // ... 5 Array<double> a(200); 4 try { 6 // use array 7 } 8 catch (Array<double>::Range& r) 9 { 10 if (r.val <MAXR) { 11 a.resize(r.val); 12 // retry algorithm 13 } 14 else { 15 // give up 16 } 17 } 18 }Is it wise to call a function (line 11) of an object that threw an exception? If an object throws an exception that means you are not supposed to use the object; or so I thought. I guess it's possible that the author of the class may write the class so that you can use the object after some exceptions are thrown, and still expect it to function properly. Thanks.
Tim StewartA
Oddly enough, this was the topic of discussion this morning in the libraries working group at the C++ standardization meeting. You may have noticed that the current working paper doesn't say anything about what happens when an operation on one of its containers throws an exception. Under those circumstances, it's definitely not wise to do much of anything with a container once an operation that tries to modify that container throws an exception.
The reason it's not wise, though, is not simply that an operation threw an exception; it's that we don't know what promises the container implementor has made about what happens when an operation throws an exception. If the implementor has promised us, for example, that after the exception has been thrown the container will be in exactly the same state as it was before the function that threw the exception was called, then we can continue to use the container exactly as if nothing had happened. Of course, in that case we should probably avoid doing whatever it was that we were trying to do when the container threw the exception.
You don't mention what promises the writer of the Array class made about what happens to the container when you try to use an index that's outside the valid range for that Array. The promise appears to be that the container can be used just as if the failure had not occurred. That's what the libraries working group calls "commit or rollback." The promise is that the operation will either succeed or it will have no effect. In such a case, resizing the array and trying again is perfectly safe and perfectly sensible.
The implementor can also make a weaker promise, and simply promise us that after an exception has been thrown the container will be in a stable state, without saying anything about what the contents of that container might actually be. Saying that the container is in a stable state merely says that you can destroy it safely, and that's all you should do once you've caught an exception in a situation that only guarantees stability. For example,
#include <iostream> #include <vector> int main() { std::vector<int> vect; vect.push_back(2) // more insertions... try { vect.push_back(3) } catch(...) { cout << "Error in vector\n"; } return 0; }If the code identified by the comment "more insertions..." managed to use up all available memory, then the attempt to insert 3 into the container would fail because the container would try to allocate more memory to hold the added data and that attempted allocation would fail. This failure would be signaled by throwing a bad_alloc exception, and we'd end up in the catch clause. At that point there is no guarantee whatsoever about the contents of vect; the data that we put into it before the final allocation attempt might or might not still be there. All that we know about vect is that when its destructor runs, at the end of main, we won't crash.
As a result of this morning's standardization committee discussion, the containers vector, deque, list, set, multiset, map, and multimap in the C++ Standard Library make that weak guarantee: if any operation on the container throws an exception, the container can be safely destroyed. Further, for each of these container types the member function swap implements commit or rollback. Finally, the list template also guarantees that its member function insert (of a single item) will implement commit or rollback [1] .
That might sound like a somewhat arbitrary collection of templates and members, but there's sound logic behind it. Writing containers that do sensible things when exceptions are thrown is tricky, and the resulting code can be big and slow. Making the basic guarantee in all containers is essential. Without it containers are almost useless.
It's easy and cheap to implement commit or rollback on a linked list, so the Standard says that you can use a linked list insert wherever you need these semantics. It's also fairly easy to implement swap to support commit or rollback, and requiring this allows you to use the other containers in a context where you require commit or rollback by making a copy of the container before the operation. If the operation succeeds you simply destroy the copy. If the operation throws an exception you swap the contents of your container with the contents of the copy, which restores your original container contents, then destroy the copy. That way you get commit or rollback if you want it, without imposing it on every user of the containers in the standard library.
One last point: to get these benefits, you have to make a few promises yourself. When you create a vector<T>, your type T must not throw an exception from its destructor. If another operation on your type does throw an exception, the object itself must be left in a stable state, that is, it must be safe for the container to destroy it. As long as you provide types that obey these rules, the containers in the C++ Standard Library will provide you with a reasonable set of guarantees that will let you write code that works sensibly when exceptions are thrown [2] .
Q
I ran across a bug yesterday that I have not noticed in close to ten years of working with C. What do you think the output should be with the following code? No cheating now, don't compile it yet, work it out on paper with only a pencil, paper, and that ANSI C Standard. You'll probably flip through the pages, find that ol' operator precedence and associativity chart and try to remember just what precedence does that ++ operator have, anyway.
#include <stdio.h> int main(void) { int x = 1; int y = 0; y = x + x + x++; printf("x:%d, y:%d\n",x, y); x = 1; y = 0; y = x++ + x + x; printf("x:%d, y:%d\n",x, y); return 0; }When I chugged through it, I thought the output should be:
x:2, y:5 x:2, y:5Well, it was when I obtained the output of:
x:2, y:3 x:2, y:3that I started to get a little worried. I checked my arithmetic, and checked my code, but the Borland compiler gave me those results. In a mad rush to hold dear what ANSI said was so, I went to the gcc compiler on my UNIX account. Results:
x:2, y:3 x:2, y:5This was beginning to look a lot like a bad twilight zone episode. Next, I tried Microsoft's compiler. Results:
x:2, y:5 x:2, y:5While this was yet a different set of results, at least it matched up with what I expected and knew the correct output should be. On the other hand I was hoping the gcc compiler was right. I always wanted to prove my old math teacher from high school wrong, namely that two mathematical equations that were supposed to be identical actually yielded different results.
What must have happened is, when Borland evaluated the statements they added the values together then processed the ++ operator, treating the ++ operator as having the lowest precedence instead of the greatest as in the ANSI C Standard. gcc, while not ignoring precedence of the ++ operator completely, made it the same as the binary argument + and - operator. Microsoft stuck with the ANSI C standard. However, the moral I shared with my students last night was, never, ever use the ++ or -- operators in arithmetic expressions. While one compiler sticks with the ANSI standard, not all do, and if you want your code to be transportable, don't do it. Second, Always Validate Your Important Calculation and Code the old fashioned way, with pen and paper.
Hope you find this as fun and interesting as I did, and that you might share it with your readers. Not too often that we find a truly Basic Bug with the Basics of C.
Dwayne Hutson
A
The effect of the expression x + x + x + x is undefined. According to clause 6.3 of the C Standard,
Between the previous and next sequence point an object shall have its stored value modified at most once by evaluation of the expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.
Since your expression uses the value of x twice in addition to using it to determine the new value of x, its effect is undefined. The compiler is not required to diagnose this problem; it is allowed to do anything at all, without constraint, when it encounters this code.
To understand this better, let's look at what would happen if the expression were valid. You've stated the rules of operator precedence correctly, but you've relied too heavily on them. There's another rule that says that the order of evaluation of operands is unspecified, and that's the culprit here. Here's how the various rules fit together in your example.
Let's begin with a somewhat different expression:
y = 1 + 2 * 3;Since * has higher precedence than +, this expression is evaluated as if it had been written
y = 1 + (2*3);and not as if it had been written
y = (1+2)*3;That is, the compiler looks first at the * operator and binds it to its operands, 2 and 3. It then looks at the + operator, which has lower precedence than *, and binds it to its operands, 1 and the result of the * operator.
The same sort of thing happens in your example. I've simplified it a bit:
y = x + x++;The ++ operator has higher precedence than +, so the compiler starts there, and binds the ++ operator to the final x. Then it looks at the + operator and binds it to its operands, the first x and the x++. That is, this expression is evaluated as if it had been written
y = x + (x++);and not
y = (x + x)++;That's what the precedence rules get us [3] .
What the precedence rules don't get us is an indication of which operand the compiler should evaluate first. That is, the compiler is permitted to do this arithmetic in either of two ways. It can evaluate the left-hand operand before the right-hand operand, in which case it picks up the value contained in x (the left-hand operand), adds to that the value contained in x (the right-hand operand), then increments the value contained in x. If x starts out with the value 1, the result of evaluating the expression will be that both x and y end up with 2.
However, the compiler is also allowed to do things in the opposite order. It can evaluate the right-hand operand before the left-hand operand, in which case it picks up the value contained in x (the right-hand operand), increments the value contained in x, and then adds the value contained in x, which has already been incremented, to the sum [4] . In this case, if x starts out with the value 1, the result of evaluating the expression will be that x ends up with the value 2, but y has the value 3.
In both of your actual examples, the compiler can evaluate x++ first, second, or third. If x++ is evaluated first, the value of y will be 5. If x++ is evaluated second, the value of y will be 4. If x++ is evaluated third, the value of y will be 3. Because of this unpredictability, the language definition makes this code illegal.
To see what's happening from a different perspective, try writing a similar expression, using function calls instead of variables:
void x1() { printf( "x() called\n" ); } void x2() { printf( "y() called\n" ); } y = x1() + x2();Try this with several compilers and with a couple of different optimization settings for each of your compilers. You'll find that some compilers call x1() first, and some call x2() first [5] . Both are valid in C.
I'd make your moral a little less restrictive: don't use ++ or -- on a variable that's used more than once in an arithmetic expression. You can't tell what's going to happen.
Q
I'm using Borland C++ 3.1 & Trident VGA 1MB, in mode 5D, which is 640x480 with 256 colors. I'm not able to put the pixel after offset address of 65535 (where a segment ends). I've tried both far and huge pointers. In the far pointer it assumes 65536 as 0 and puts the pixel on the (0,0). I define the pointer as (unsigned char far*) & (unsigned char huge *) Please tell me what I do. Thanking You.
M. Zeeshan
A
A You have to be a bit careful using far pointers; they're tricky. The problem, of course, is that on a 16-bit system, when you add one to the unsigned value 65535 you get 0. That's because arithmetic on unsigned types wraps around when you reach the maximum value that an unsigned can hold. The compiler knows how to deal with this when the overflow occurs as a result of incrementing a far pointer: it rolls the offset of the pointer over to 0, and increments the segment value. Try it:
#include <dos.h> #include <stdio.h> int main() { char huge *ptr = MK_FP( 0xA000, 65535 ); printf( "%x:%x\n", FP_SEG(ptr), FP_OFF(ptr) ); ptr++; printf( "%x:%x\n", FP_SEG(ptr), FP_OFF(ptr) ); }The MK_FP macro itself, though, isn't that smart:
#include <dos.h> #include <stdio.h> int main() { char huge *ptr = MK_FP( 0xA000, 65536 ); printf( "%x:%x\n", FP_SEG(ptr), FP_OFF(ptr) ); }The moral here is that you should not rely on the MK_FP macro to handle integer overflows. Make sure that the offset value that you give it is less than 65536.
Q
Just a quick (I think) question: why is this code fine:
class X { int x; public : int operator== (const X &y) { return y.x == x; } }; template <class T> class Y { T x; public : // This is OK int operator== (const Y<T> &y) { return x == y.x; } }; int main () { Y<X> x; }and the following is not?
class X { int x; public : int operator== (const X &y) { return y.x == x; } }; template <class T> class Y {+ T x; public : // This will not compile under GNU g++ v2.7.2. // The error is : // // In method 'int Y<X>::operator ==(const class Y<X> &)': // no match for 'operator ==(class X, class X)' int operator== (const Y<T> &y) { return y.x == x; } }; int main () { Y<X> x; }Thanks in advance for your attention.
Andrea HaardtA
Take a careful look at the line that g++ is complaining about. y is a reference to a const object, which means that y.x, in turn, is const. In order to apply the == operator, the compiler needs to find one that takes a const X as its left operand. The operator== defined in X does not take a const X, it takes a non-const X. So the compiler complains that it can't find an appropriate operator==.
The way to fix this [6] is to write X's operator== so that it takes a const X as its left operand. Like this:
int operator== (const X &y) const { return y.x == x; }Notice that I've added a second const in the function definition. This makes operator== a const member function, that is, one that can be called on a const X. You should do the same for Y::operator== as well.
Now, that fixes the immediate problem, but you might want to consider making operator== an ordinary function instead of a member function. There's nothing in this short example to indicate that it's needed, but often it is. For example, suppose you're writing a complex number class:
class complex { public: complex( double re, double im = 0.0 ); bool operator == ( complex ) const { return c1.real == c2.real && c1.imag == c2.imag ); } private: double real, imaginary; };In a case like this, if operator== is a member it acts a bit strange:
int main() { complex c1( 1.0 ); complex c2( 1.0 ); if( c1 == c2 ) // OK cout << "c1 equals c2\n"; if( c1 == 1.0 ) // OK cout << "c1 equals c2\n"; if( 1.0 == c1 ) // illegal cout << "c1 equals c2\n"; }The first comparison is obviously valid: it compares two objects of type complex, using the operator== defined in the class. The second comparison is a bit more complicated, because the compiler must create an object of type complex from the value 1.0. It does this with the constructor, using the default value of 0.0 for the second argument to the constructor. The third comparison is invalid because the compiler doesn't do type conversions to change one type into another for a member function call. There is no operator== that takes a double as its left operand and a complex as its right operand, so the call is invalid. You could make it work with an explicit cast:
if( complex(1.0) == c1 )but I wouldn't do it that way. Instead, make operator== a global function instead of a member:
class complex { public: complex( double re, double im = 0.0 ); friend bool operator == ( complex, complex ); private: double real, imaginary; }; bool operator == ( complex c1, complex c2 ) { return c1.real == c2.real && c1.imag == c2.imag ); }Now all three of the comparisons work the way you'd expect.
Finally, I strongly recommend using meaningful names for the members of your classes. The proliferation of x's and y's in your code makes it very hard to follow what's happening. In particular, having X's operator == take an argument named y and access a member named x is very confusing. I'll let you get away with it this time, on the assumption that this is a result of abbreviating the code to fit it into a small example. But please don't do this in production code.
Notes
[1] What I've just described is what we intended to do. There's some question whether, in a flurry of changes, we actually made the right changes to the working paper to say this clearly. We'll have to wait for the November meeting to be sure of what's actually guaranteed by the Standard.
[2] Thanks to Dave Abrahams, Greg Colvin, and Matt Austern for their work in pulling together the concepts and the details of exception safety in containers.
[3] Of course, the last version of this expression isn't even valid, because you can't apply ++ to the result of an addition of two integers. But that's not the reason that the compiler chooses the first version.
[4] This is actually a bit oversimplified. The rule is that the compiler must perform the ++ sometime after it uses the value of x, and before moving on to the next statement. So even when it evaluates the second operand first, the compiler could postpone actually doing the increment until after it evaluates the first operand, in which case the result is the same as if the compiler had evaluated the first operand first.
[5] And, further, the order may change even with the same compiler when you change its optimization switches.
[6] That is, fix the problem in the code. In fact, your small program example shouldn't fail, because it doesn't use the operator == anywhere. That means that the compiler shouldn't try to create operator ==, and shouldn't complain that it doesn't work. Most compilers, though, still create all of a template's member functions when you use the template, so they'll complain about this operator ==. Give it more time, though; eventually compilers will catch up to the language definition.
Pete Becker is a consultant who specializes in teaching C and C++ to experienced programmers. He spent eight years in the C++ group at Borland International, both as a developer and manager. He is a member of the ANSI/ISO C++ standardization committee. He can be reached by e-mail at petebecker@acm.org.