rdr@mdavcr.UUCP (Randolph Roesler) (12/08/89)
We all know that C++ is a better C, with OO and prototypes and better type matching and etc. What we also know is that C++ is complex, nearly as complex as ADA. All those rules about operator new (which really should be called operator allocate), operator = (assignment), copy constructors, protected, private, public, ...... 10 Million and One rules to learn. And some of these rules are very subtle, like this one I discovered the other day (which I will detail shortly) In the old days, we KNEW what was going on, and in which order things happened. Maybe, if good garbage collection was around (and cheap and efficient), we could all stop worring about these issues. But for now, C++ is the best we have so WE better improve it (or retire). We had lint in the old days to catch things like if ( x = 0 ) then something_imporant(); which is almost certainly an error. What we need is lint++ -- an expert code reviewer which can catch problems like that above, and the more subtle problems, like that detailed below. I would like to collect a list of lint-like heuristics for C++, things that the compiler cannot be expected to catch, but which are: o likely program bugs o unsafe coding practices o portablity issues o etc. Now is the time to collect these types of rules, while we are all learning. It is now that we will be discovering creative new ways shoot ourselves in the foot (feet if you are a good shot :)). Please e-mail in your contributions and I will summarize to the net. Contributions that express a rule like: Constructor(s) with dynamic memory allocation without assigment operator(s) might cause heap problems !! (see sample problem below) and a more mundane one: Assignment within condition expression of an if statement might be mistype of ==. are preferred, but I am not greedy, give what you can. Heuristics which address mainly theological points of style are too abstract -- let's avoid those. Now, to start things off, here is my very subtle problem which started this whole train of thought. Can you spot the error ? #include <string.h> #include "ComIdent.hh" ComIdent::ComIdent() { string = 0; } ComIdent::ComIdent( const char * const token ) { if ( token ) { string = new char[strlen(token)+1]; strcpy( string,token ); } else string = 0; } ComIdent::ComIdent( const ComIdent & token ) { if ( token.string ) { string = new char[strlen(token.string)+1]; strcpy( string,token.string ); } else string = 0; } ComIdent::~ComIdent() { if ( string ) delete[strlen(string)+1] string; } Did you see the problem? I did not --- it took me 19 hours or so to find out what was going on. I eventually had to look at the C++ 2.0 produced C code to figure it out. Here is the problem: Statements like ComIndent ident_new = ident_old; fail because a) Copy constructor is called to copy ident_old into a temp ComIdent object. Memory is malloc'ed (by new) OK. (I made the copy constructor to avoid the problem you are about to discover; unfortunately, I did not understand that C++ creates temps on initialization. Too bad.) b) Default memberwise copy is performed from temp into ident_new. Pointer to character array is member of ComIdent types, and is copied faithfully. (I had thought the copy constructor would be used here.) c) Destructor is called to free memory of temp. Newly allocated string is freed. d) Next new overwrites data area of string (which is pointed to by the now invalid ident_new). So simple ? Everyone knows that ! (So do I.) Well, it so happens that indent_new was passed to another constructor as the first argument several function calls later. What I saw was that C++ was making an allocation error because the pointer value of this was the same as the constructor's first value. Not only that, but the program did not crash, instead it just printed "(null)" all over the place. The fix --- delete the copy constructor and replace with an assignment one. ComIdent & ComIdent::operator = ( const ComIndent & token ) { if ( string ) delete[strlen(string)+1] string; if ( token.string ) { string = new char[strlen(token.string)+1]; strcpy( string,token.string ); } else string = 0; } The point --- if I had read about this problem (or had lint++) this might not have cost me so much time. So emails away ! -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It's not the size of your signature that Randy Roesler counts - it's how you use it! MacDonald Dettwiler & Assc. email ...!uunet!van-bc!mdavcr!rdr BC Canada 604-278-3411
kenny@hpcuhc.HP.COM (Kenneth Lee) (12/09/89)
/ hpcuhc:comp.lang.c++ / rdr@mdavcr.UUCP (Randolph Roesler) / 7:41 pm Dec 7, 1989 / > > We had lint in the old days to catch things like > > if ( x = 0 ) then > something_imporant(); > > which is almost certainly an error. I know it's certainly an error, but what are you trying to point out? Are you trying to say lint should do one of the following: 1. tell you a syntax error if ( expression ) then statement where then is not a keyword in C. 2. tell you a logic error if ( something which is always false ) thisWillNeverGetExecute(); Are these the jobs of lint? kenny
Roy.Browning@f506.n106.z1.fidonet.org (Roy Browning) (12/11/89)
> ComIdent::~ComIdent() > { > if ( string ) > delete[strlen(string)+1] string; > } > The fix --- delete the copy constructor and replace with an assignment > one. Randolph: I have sucessfully used both a copy constructor and an overloading assignment operator in a test class. However from my limited reading on C++ I believe your problem is your destructor. From reading Lippman pages 240 & 241 you are deleting an array of pointers stored in "string" where "[x]" is the number to "free()". From the source code I've seen all "delete" does is to call "free()" and pass it a "char *". Telling "delete" there are "[X]" pointers stored at location "string" should cause some VERY unusual problems if I'm correct. I could use a lint myself, Roy Browning
jeenglis@nunki.usc.edu (Joe English) (12/13/89)
Roy.Browning@f506.n106.z1.fidonet.org (Roy Browning) writes: > > > ComIdent::~ComIdent() > > { > > if ( string ) > > delete[strlen(string)+1] string; > > } > I believe your problem is your destructor. From reading Lippman >you are deleting an array of pointers stored in "string" where "[x]" is the >number to "free()". From the source code I've seen all "delete" does is to >call "free()" and pass it a "char *". Telling "delete" there are "[X]" >pointers stored at location "string" should cause some VERY unusual problems >if I'm correct. Actually, delete[X] p; is used when p points at an array of X objects with destructors. It calls the destructor on each of the X objects but only passes "p" to free() (or the equivalent function.) Since 'char' doesn't have a destructor, the above code is equivalent to just 'delete string;' --Joe English jeenglis@nunki.usc.edu
rdr@mdavcr.UUCP (Randolph Roesler) (12/13/89)
In article <590001@hpcuhc.HP.COM> kenny@hpcuhc.HP.COM (Kenneth Lee) writes: >/ hpcuhc:comp.lang.c++ / rdr@mdavcr.UUCP (Randolph Roesler) / 7:41 pm Dec 7, 1989 / >> >> We had lint in the old days to catch things like >> >> if ( x = 0 ) then ^^^^ >> something_imporant(); >> >> which is almost certainly an error. > >I know it's certainly an error, but what are you trying to point out? > Yes -- I made a typing error -- I typed what I say, rather than what C++ accepts. Catching this error is the compilers job. The possible error of the assignment rather than a comparison would be a lint tool's job. What I want from you (dear net reader), is hueristic rules about situations in C++ which the compiler can not or will not warn you about. -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It's not the size of your signature that Randy Roesler counts - it's how you use it! MacDonald Dettwiler & Assc. email ...!uunet!van-bc!mdavcr!rdr BC Canada 604-278-3411
rdr@mdavcr.UUCP (Randolph Roesler) (12/13/89)
In article <5457.25845C8E@urchin.fidonet.org> Roy.Browning@f506.n106.z1.fidonet.org (Roy Browning) writes: > > > ComIdent::~ComIdent() > > { > > if ( string ) > > delete[strlen(string)+1] string; > > } > > > The fix --- delete the copy constructor and replace with an assignment > > one. > >Randolph: > > I have sucessfully used both a copy constructor and an overloading >assignment operator in a test class. However from my limited reading on C++ I >believe your problem is your destructor. From reading Lippman pages 240 & 241 >you are deleting an array of pointers stored in "string" where "[x]" is the >number to "free()". From the source code I've seen all "delete" does is to >call "free()" and pass it a "char *". Telling "delete" there are "[X]" >pointers stored at location "string" should cause some VERY unusual problems >if I'm correct. > > I could use a lint myself, > > Roy Browning > Thanks for the reply Roy. The program is working fine now, so I do not think that the destructor is the problem. My use of delete is symmetric with may use of new. I.e. some_type * xxxx = new some_type [size]; delete [size] xxxx; In this case, some_type is `char', and thus, neither new or delete need to call constructors/destructors. The key passage in the manual is on page 21 (SUN C++ docs, section 5.3.4), which says (much left out): The form delete [ expression ] cast_expression is used to delete arrays.... destructors (if any) ... will be invoked. Still, you bring up a good point. If some_type was orginal typedefined as character, and you left out the [ expression ] construction from the delete statement, everything would probably work out fine. (I guess I should try this !). If, latter, a change my typedefine of some_type to some other type with constructors and destructors, then the code would probably start to fail. Could a tool be written to track varible usage and warn you about this type of situations ? Any more situations like this ? -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It's not the size of your signature that Randy Roesler counts - it's how you use it! MacDonald Dettwiler & Assc. email ...!uunet!van-bc!mdavcr!rdr BC Canada 604-278-3411
Roy.Browning@f506.n106.z1.fidonet.org (Roy Browning) (12/14/89)
> > > ComIdent::~ComIdent() > > > { > > > if ( string ) > > > delete[strlen(string)+1] string; > > > } > Actually, delete[X] p; is used when p points at an > array of X objects with destructors. It calls the > destructor on each of the X objects but only passes > "p" to free() (or the equivalent function.) > > Since 'char' doesn't have a destructor, the above > code is equivalent to just 'delete string;' Joe: Ouch, guess I deserved that. Couched my answer in very loose 'C' jargon in the C++ newsgroup and got my head handed to me <grin>. I habitually use imprecise terminology relying on the overall meaning rather than the exact words. Will have to revert to a policy of only posting tested code. Spent a couple of hours proving that the code below would loose random pointers to free(). Even modified my library to to demonstrate exactly what was occurring. main() { char* str = "Test string"; String* a = (String*) new String; *a = str; cout << a; delete[5] a; exit(0); } Then I looked at your reply and the original message and observed that I had only proven that I had misread the original missive. You are quite correct, "delete[x] string" calls free only once deallocating the string. I stand corrected, Roy Browning
Roy.Browning@f506.n106.z1.fidonet.org (Roy Browning) (12/14/89)
> From: rdr@mdavcr.UUCP (Randolph Roesler) > Date: 12 Dec 89 21:51:32 GMT > Organization: MacDonald Dettwiler, Richmond, B.C., Canada > Message-ID: <701@acrux.mdavcr.UUCP> > Newsgroups: comp.lang.c++ Roland: What I so badly related in the previous message is that I find the syntax listed below sufficient for deallocating character arrays. foo() { char* string = new char[number of characters]; ..... delete string; } However the syntax below is what is suggested by Lippman for an array of pointers to objects. The String destructor is invoked for each of the "size" elements of tbl2. foo() { String tbl[ size ]; String *tbl2 = new String[ size ]; ..... while (cin >> tbl[i]); .... delete [ size ] tbl2; } I mistakely assumed that your use of "new" was to return an array of object pointers rather than an array of characters, my mistake. Roy Browning