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";
}