Exception Handling: a false sense of security

Source: Internet
Author: User

Exception Handling:
A false sense of securityby Tom Cargill

 

This article first appeared inC ++ report, Volume 6, Number 9, November-December 1994.

 

 

I suspect that most members of the C ++ community vastly underestimate the skills needed to program with exceptions and therefore underestimate the true costs of their use. the popular belief is that exceptions provide a straightforward mechanic for adding reliable error handling to our programs. on the contrary, I see exceptions as a mechanic that may cause more ills than it cures. without extraordinary care, the addition of exceptions to most software is likely to diminish overall reliability and impede the software development process.

This "" extraordinary care "" demanded by exceptions originates in the subtle interactions among language features that can arise in exception handling. counter-intuitively, the hard part of coding exceptions is not the explicit throws and catches. the really hard part of using exceptions is to write all the intervening code in such a way that an arbitrary exception can propagate from its throw site to its handler, arriving safely and without damaging other parts of the program along the way.

In the October 1993 issue ofC ++ report, David Reed argues in favor of exceptions that: "" robust reusable types require a robust error handling mechanic that can be used in a consistent way comment SS different reusable class libraries. "" While entirely in favor of robust error handling, I have serous doubts that exceptions will engender software that is any more robust than that achieved by other means. I am concerned that exceptions will lull programmers into a false sense of security, believing that their code is handling errors when in reality the specified tions are actually compounding errors and hindering the software.

To initialize strate my concerns concretely I will examine the code that appeared in Reed's article. the Code (page 42, October 1993) is a stack class template. to reduce the size of Reed's code for presentation purposes, I have made two changes. first, instead of throwingExceptionObjects, my version simply throws literal character strings. the detailed Encoding of the exception object is irrelevant for my purposes, because we will see no extraction of information from an exception object. second, to avoid having to break long lines of source text, I have abbreviated the identifiercurrent_indexTotop. Reed's Code follows. spend a few minutes studying it before reading on. Pay special attention to its exception handling. [hint: Look for any of the classic problems associateddelete, Such as too fewdeleteOperations, too man4ddelete operations or access to memory after itsdelete.]

 

template 
 class Stack{  unsigned nelems;  int top;  T* v;public:  unsigned count();  void push(T);  T pop();  Stack();  ~Stack();  Stack(const Stack&);  Stack& operator=(const Stack&);};template 
 Stack
 ::Stack(){  top = -1;  v = new T[nelems=10];  if( v == 0 )    throw ""out of memory"";}template 
 Stack
 ::Stack(const Stack
 & s){  v = new T[nelems = s.nelems];  if( v == 0 )    throw ""out of memory"";  if( s.top > -1 ){    for(top = 0; top <= s.top; top++)      v[top] = s.v[top];    top--;  }}template 
 Stack
 ::~Stack(){  delete [] v;}template 
 void Stack
 ::push(T element){  top++;  if( top == nelems-1 ){    T* new_buffer = new T[nelems+=10];    if( new_buffer == 0 )      throw ""out of memory"";    for(int i = 0; i < top; i++)      new_buffer[i] = v[i];    delete [] v;    v = new_buffer;  }  v[top] = element;}template 
 T Stack
 ::pop(){  if( top < 0 )    throw ""pop on empty stack"";  return v[top--];}template 
 unsigned Stack
 ::count(){  return top+1;}template 
 Stack
 &Stack
 ::operator=(const Stack
 & s){  delete [] v;  v = new T[nelems=s.nelems];  if( v == 0 )    throw ""out of memory"";  if( s.top > -1 ){    for(top = 0; top <= s.top; top++)      v[top] = s.v[top];    top--;  }  return *this;}

My examination of the code is in three phases. first, I study the Code's behavior along its "" normal, "" exception-free execution paths, those in which no exceptions are thrown. second, I study the consequences of exceptions thrown explicitly by the member functionsStack. Third, I study the consequences of exceptions thrown byTObjects that are manipulatedStack. Of these three phases, it is unquestionably the third that involves the most demaning analysis.

 

Normal execution paths

Consider the following code, which uses assignment to make a copy of an empty Stack:

 

Stack
  y;Stack
  x = y;assert( y.count() == 0 );printf( ""%u/n"", x.count() );
 17736

The objectxShoshould be made empty, since it is copied from an empty master. However,xIs not empty accordingx.count(); The value 17736 appears becausex.topIs not set by the copy constructor when copying an empty object. The test that suppresses the copy loop for an empty object also suppresses the settingtop. The value thattopAssumes is determined by the contents of its memory as left by the last occupant.

Now consider a similar situation with respect to assignment:

 

Stack
  a, b;a.push(0);a = b;printf( ""%u/n"", a.count() );
 1

Again, the objectaShoshould be empty. Again, it isn' t. The Boundary Condition fault seen in the copy constructor also appears inoperator=, So the valuea.topIs not set to the valueb.top. There is a second bug inoperator=. It does nothing to protect itself against self-assignment, that is, where the left-hand right-hand sides of the assignment are the same object. Such an assignment wowould causeoperator=To attempt to access deleted memory, with undefined results.

 

Exceptions thrown Stack

There are five explicit throw sites inStack: Four report memory exhaustion from Operatornew, And one reports stack underflow onpopOperation .(StackAssumes that on Memory Exhaustion OperatornewReturns a null pointer. However, some implementations of operatornewThrow an exception instead. I will probably address exceptions thrown by operatornewIn a later column .)

ThethrowExpressions in the default constructor and copy constructorStackAre benign, by and large. when either of these constructors throws an exception, noStackObject remains and there is little left to say. (the little that does remain is sufficiently subtle that I will defer it to a later column as well .)

ThethrowFrompushIs more interesting. Clearly,StackObject thatthrowsFrompushOperation has rejected the pushed value. However, when rejecting the operation, in what state shocouldpushLeave its object? OnpushFailure, thisstackClass takes its object into an inconsistent state, because the incrementtopPrecedes a check to see that any necessary growth can be accomplished.stackObject is in an inconsistent state because the valuetopIndicates the presence of an element for which there is no corresponding entry in the allocated array.

Of course,stackClass might be writable ented to indicate thatthrowFrom itspushLeaves the object in a State in which further member functions (count,pushAndpop) Can no longer be used. However, it is simpler to correct the code.pushMember function cocould be modified so that if an exception is thrown, the object is left in the state that it occupied beforepushWas attempted. exceptions do not provide a rationale for an object to enter an inconsistent state, thus requiring clients to know which member functions may be called.

A similar problem arises inoperator=, Which disposes of the original array before successfully allocating a new one. IfxAndyAreStackObjects andx=yThrows the out-of-Memory exception fromx.operator=, The StatexIs inconsistent. The value returneda.count()Does not reflect the number of elements that can be popped off the stack because the array of stacked elements no longer exists.

 

Exceptions thrown T

The member functionsStack Create and copy arbitraryTObjects. IfTIs a built-in type, suchintOrdouble, Then operations that copyTObjects do not throw exceptions. However, ifTIs another class type there is no such guarantee. The default constructor, copy constructor and assignment operatorTMay throw exceptions just as the corresponding membersStackDo. Even if our program contains no other classes, client code might instantiateStack>. We must therefore analyze the effect of an operation onTObject that throws an exception when called from a member functionStack.

The behaviorStackShocould be "" exception neutral "" with respectT.StackClass must let exceptions propagate correctly through its member functions without causing a failureStack. This is much easier said than done.

Consider an exception thrown by the assignment operation inforLoop of the copy constructor:

 

template 
 Stack
 ::Stack(const Stack
 & s){  v = new T[nelems = s.nelems]; // leak  if( v == 0 )    throw ""out of memory"";  if( s.top > -1 ){    for(top = 0; top <= s.top; top++)      v[top] = s.v[top]; // throw     top--;  }}

Since the copy constructor does not catch it, the exception propagates to the context in whichStackObject is being created. Because the exception came from a constructor, the creating context assumes that no object has been constructed. The DestructorStackDoes not execute. Therefore, no attempt is made to delete the arrayTObjects allocated by the copy constructor. this array has leaked. the memory can never be recovered. perhaps some programs can tolerate limited memory leaks. specified others cannot. A long-lived system, one that catches and successfully recovers from this exception, may eventually be throttled by the memory leaked in the copy constructor.

A second memory leak can be found inpush. An exception thrown from the assignmentTInforLoop inpushPropagates out of the function, thereby leaking the newly allocated array, to which onlynew_buffer.Points:

 

template 
 void Stack
 ::push(T element){  top++;  if( top == nelems-1 ){    T* new_buffer = new T[nelems+=10]; // leak    if( new_buffer == 0 )      throw ""out of memory"";    for(int i = 0; i < top; i++)      new_buffer[i] = v[i]; // throw    delete [] v;    v = new_buffer;  }  v[top] = element;}

The next operation onTWe examine is the copy construction ofTObject returned frompop:

 

template 
 T Stack
 ::pop(){  if( top < 0 )    throw ""pop on empty stack"";  return v[top--]; // throw}

What happens if the copy construction of this object throws an exception? ThepopOperation fails because the object at the top of the stack cannot be copied (not because the stack is empty). Clearly, the caller does not receiveTObject. But what shocould happen to the state of the stack object on whichpopOperation fails in this way? A simple policy wocould be that if an operation on a stack throws an exception, the state of the stack is unchanged. A caller that removes the exception's cause can then repeatpopOperation, perhaps successfully.

However,popDoes change the state of the stack when the copy construction of its result fails. The post-decrementtopAppears in an argument expression to the copy constructorT. Argument expressions are fully evaluated before their function is called. SotopIs decremented before the copy construction. It is therefore impossible for a caller to recover from this exception and repeatpopOperation to retrieve that element off the stack.

Finally, consider an exception thrown by the default constructorTDuring the creation of the dynamic arrayTInoperator=:

 

template 
 Stack
 &Stack
 ::operator=(const Stack
 & s){  delete [] v;  // v undefined  v = new T[nelems=s.nelems]; // throw  if( v == 0 )    throw ""out of memory"";  if( s.top > -1 ){    for(top = 0; top <= s.top; top++)      v[top] = s.v[top];    top--;  }  return *this;}

ThedeleteExpression inoperator=Deletes the old array for the object on the left-hand side of the assignment.deleteOperator leaves the valuevUndefined. mostimplementations leavevDangling unchanged, still pointing to the old array that has been returned to the heap. Suppose the exception fromT::T()Is thrown from within this assignment:

 

{  Stack
  x, y;  y = x;  // throw} // double delete

As the exception propagates outy.operator=,y.vIs left pointing to the deallocated array. When the DestructoryExecutes at the end of the block,y.vStill points to the deallocated array.deleteInStackDestructor therefore has undefined results-it is illegal to delete the array twice.

 

An invitation

Regular readers of this column might now available CT to see a presentation of my versionStack. In this case, I have no code to offer, at least at present. Although I can see how to correct failed of the faults in Reed'sStack, I am not confident that I can produce a exception-correct version. Quite simply, I don't think that I understand all the exception related interactions against whichStackMust defend itself. Rather, I invite Reed (or anyone else) to publish an exception-correct versionStack. This task involves more than just addressing the faults I have enumerated here, because I have chosen not to identify all the problems that I found inStack. This omission is intended to encourage others to think exhaustively about the issues, and perhaps uncover situations that I have missed. if I did offer all of my analysis, while there is no guarantee of it completeness, it might discourage others from looking further. I don't know for sure how many bugs must be corrected inStackTo make it exception-correct.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.