ahodgson@athena.mit.edu (Antony Hodgson) (01/31/91)
/* TEST.CPP: This code attempts to read all subdirectories listed in the current directory and write out a message any time it finds one that's not the current directory ("."). There is a subtle bug in this code, however. If we try to perform the test: if ( (F->IsSubdirectory()) && !(F->Name() == ".") ) where F is a pointer to a file descriptor, then we often, but unfortunately unpredictably, get a null pointer assignment message which manifests itself as an overwrite of the Turbo C++ message stored at offset 4. This can be seen by monitoring (char*)4 with the debugger. The program may have to be run a couple of times to generate the null pointer message. The unusual thing is that if we write the above statement as: if ( F->IsSubdirectory() ) if ( !(F->Name() == ".") ) then everything works fine. Since the two forms are logically identical (except that the second requires an extra matching else statement), they should work the same way. The question is: why don't they? I would greatly appreciate any help anyone can offer. I ran this program in a subdirectory with 6 or 7 regular files, one subdirectory, and the ever-present . and .. directories. */ #include <dir.h> #include <dos.h> #include <iostream.h> #include <process.h> #include <string.h> #define TRUE 1 #define FALSE 0 typedef unsigned char boolean; #define CASE_SENSITIVE 0 #define CASE_INSENSITIVE 1 // A very basic string class containing constructors, an assignment operator // and an equality operator (the == operator can perform either case- // sensitive or case-insensitive comparisons). class String { protected: char *S; int ComparisonMode; public: String(); String( char *s, int mode = CASE_SENSITIVE ); String( String &s ); String operator = ( String& s ); boolean operator == ( String& s ); operator const char * () { return S; } ~String() { delete S; } friend ostream& operator << ( ostream &s, String &x ) { return (s << x.S); } }; void AllocationError() { cerr << "Cannot allocate space for new string.\n"; exit(1); } // S is guaranteed to point to something, even if it's a null string ("\x0") String::String() { ComparisonMode = CASE_SENSITIVE; S = strdup( "" ); } String::String( char *s, int mode ) { ComparisonMode = mode; if ( s == NULL ) S = strdup( "" ); else if ( ( S = strdup(s) ) == NULL ) AllocationError(); } String::String( String &s ) { ComparisonMode = s.ComparisonMode; if ( ( S = strdup( s.S ) ) == NULL ) AllocationError(); } String String::operator = ( String& s ) { delete S; S = strdup( s.S ); ComparisonMode = s.ComparisonMode; return *this; } boolean String::operator == ( String& s ) // are the strings equal? { if ( ComparisonMode == CASE_SENSITIVE ) if ( strcmp( s.S, S ) ) return FALSE; // strcmp = 0 if equal else return TRUE; else // CASE_INSENSITIVE { // Duplicate the strings so we can convert them to uppercase String s1 = *this, s2 = s; strupr( s1.S ); strupr( s2.S ); if ( strcmp( s1.S, s2.S ) ) return FALSE; else return TRUE; } } class File { public: struct ffblk f; File( struct ffblk fb ); String Name(); boolean IsSubdirectory(); }; File::File( struct ffblk fb ) { f = fb; } String File::Name() { // We create an intermediate variable here because Turbo C++ // has reportedly had problems returning an instance created // in the return statement. String temp ( f.ff_name, CASE_INSENSITIVE ); return temp; } boolean File::IsSubdirectory() { if ( ( f.ff_attrib & FA_DIREC ) == FA_DIREC ) return TRUE; else return FALSE; } boolean ReadDir() { struct ffblk fb; File *F; // read subdirectories int done = findfirst( "*.*", &fb, FA_DIREC ); String cd( "." ); while (!done) { F = new File( fb ); if ( ( F->IsSubdirectory( ) ) // The following three lines of code all attempt to perform roughly the // same comparison: check if the name is the current subdirectory. // If the first of the three is used, we get null pointer assignments. // Either of the latter two work fine. (Note: if we use 2 if statements, // we must have two matching else statements below). // Uncomment one of the following three lines: && ( !(F->Name() == cd ) ) // this one -> NULL ptr // ) if (( !(F->Name() == cd ) ) // this works // && ( strcmp( F->f.ff_name, "." ) ) // this works ) cout << "found subdirectory not equal to '.'\n"; // else delete F; // we need two delete statements if we // use two if statements above in order // to ensure that unused Fs are deleted. else delete F; done = findnext( &fb ); } return TRUE; } main( int, char ) { ReadDir(); cout << "\n"; }