Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!usc!zaphod.mps.ohio-state.edu!rpi!clarkson!cheetah.ece.clarkson.edu!cline From: cline@cheetah.ece.clarkson.edu (Marshall Cline) Newsgroups: comp.lang.c++ Subject: Monthly posting of Turbo-C++ bug list Message-ID: <9012010531.AA05322@cheetah.ece.clarkson.edu> Date: 1 Dec 90 05:31:13 GMT Lines: 775 Approved: Clarkson University Name: Turbo-C++ bug list (bug-list-version 1.01.4) Products: Borland Turbo-C++ 1.00 Borland Turbo-C++ 1.01 Author: Marshall P. Cline ECE department Clarkson University Potsdam, NY 13676 Voice: 315-268-3868 Secretary: 315-268-6511 FAX: 315-268-7600 Internet: cline@sun.soe.clarkson.edu Alternate: bh0w@clutx.clarkson.edu Bitnet: BH0W@CLUTX UUnet: uunet!clutx.clarkson.edu!bh0w Copyright: The Author releases this to the Public Domain NO WARRANTY: This list is distributed with NO WARRANTY WHATSOEVER. It is NOT guaranteed that this information is free from error; parts of this were donated by others, etc. Availability: This is freely available via anonymous ftp from: sun.soe.clarkson.edu [128.153.12.3] in the file: ~ftp/pub/Turbo-C++/bug-report Revisions: BugList-Date BugList-Version 11 Jun 90 1.00.1 09 Aug 90 1.00.2 14 Aug 90 1.00.3 29 Aug 90 1.00.4 20 Sep 90 1.00.5 14 Nov 90 1.01.1 (some TC++ v.1.01 info added) 21 Nov 90 1.01.2 (some responses from Borland) 26 Nov 90 1.01.3 30 Nov 90 1.01.4 Copyright: This file is PUBLIC DOMAIN (including the contained code fragments). That means no one (including myself) can restrict its use/distribution. In particular, you can't copyright it. No one can. Not even me. Contributions: If you have a TC++ bug to report, please email to the above addresses. But please try to find/send a work-around to the problem/bug. Also please explicitly state that your comments/code are public domain. The ``Borland says'' comments are from: borland/long.messages #27, from lbenner, 9387 chars, Fri Nov 9 18:58:20 1990 TITLE: Borland's Comments to Emartinson's Bug List Borland has added comments to the items listed in this file originally posted as #25 from emartinson. Lori Borland ============================================================================= ============================================================================= Severity: not serious Versions: occurs in both TC++ 1.00 and 1.01 Several classlib\include\*.h have #include "foo.h" rather than . This will only cause a problem if you have an indentically named header file in your current working directory (#include "file.h" starts in current dir, then searches the "standard places"; only searches standard places). Note that TC++ by default doesn't find the classlib header files; if you want it to, add the line to turboc.cfg: -IC:\TC\CLASSLIB\INCLUDE ----------------------------------------------------------------------------- Severity: not serious inconsistency in classlib's header files Versions: occurs only in TC++ 1.00; fixed in 1.01 Some include files have #ifndef __FILENAME_H, others have #ifndef _FILENAME_H. (inconsistent #leading underscores). See for example sortable.h vs set.h, etc. Obviously this won't cause any problems; it's just an inconsistency that could be corrected as time permits. ----------------------------------------------------------------------------- Severity: missed feature Versions: occurs in both TC++ 1.00 and 1.01 TCCNVT.EXE (the configuration file converter) isn't in the distribution. ----------------------------------------------------------------------------- Severity: bug in support tool Versions: occurs in both TC++ 1.00 and 1.01 [Borland says 1.01 fixed this, but my 1.01 still exhibits this behavior] `make -n' looks for and reads `builtins.mak' ONLY if it's in the current dir. Naturally this is a bug, since make can't give an accurate picture of what a real `make' would do without the macros and implicit rules in `builtins.mak'. >Craig Orsinger/craig@slovax.wa.com/R&DAssoc/3625 Perkins L SW/Tacoma,WA 98499: >This also happens if you call "make" from your makefile. I don't know if this >occurs if the makefile and "make" program are on the same disk partition, but >it definitely does when they are not. My work-around was to use the new >"search path" feature of make to tell it where the "builtins.mak" file is. ----------------------------------------------------------------------------- Severity: not serious Versions: occurs in both TC++ 1.00 and 1.01 always includes which slows compilation some. In fact doesn't need `NULL', and only needs memcpy() if _BIG_INLINE_ is defined, which will probably be rare. Therefore the line #include // to get memcpy and NULL can be replaced with: #ifdef _BIG_INLINE_ #include // to get memcpy #endif Since nearly everything includes , and since relatively few things will want _BIG_INLINE_, this should be a winner. Note however that some code might assume pulls in . Borland says: iostream.h is big and slow to compile. Other speedups are to remove all the comments from the file. This speeds up 286 machines. My response: If you know iostream.h is big and slow to compile, why should you make it approx 10% bigger and slower? ----------------------------------------------------------------------------- Severity: non-serious bug in header file Versions: occurs in both TC++ 1.00 and 1.01 needs but doesn't get it. Add this to : #ifndef __IOSTREAM_H #include #endif ----------------------------------------------------------------------------- Severity: missing header file Versions: occurs in both TC++ 1.00 and 1.01 There is no . I constructed the following work-alike. It should go into your standard include directory (where is, for example): // new.h // Author: Dr. Marshall Cline/ECE Dept/Clarkson Univ/Potsdam,NY 13676 // Email: cline@sun.soe.clarkson.edu // Phone: Voice: 315-268-6511; Fax: 315-268-7600 // Copyright: The Author releases this to the Public Domain, 9-July-90. // Date: 9-July-1990 // Please include these acknowledgements when distributing this file #ifndef __NEW_H #define __NEW_H #ifndef _SIZE_T #define _SIZE_T typedef unsigned size_t; #endif void* operator new(size_t size, void* ptr); // _new_handler is a ptr to a parameterless function returning void extern void (*_new_handler)(); void (*set_new_handler(void (*replacement_handler)()))(); #endif __NEW_H Borland says: NEW.H is NOT defined by AT&T 2.0 C++ specs. The contents were in C++ specs 1.2 and is making a comeback in the upcoming ANSI C++ spec. We do have the underlying code, ie _new_handler support, but it is not documented. It would be nicer to use a typedef for the void function pointer, for the above work-around. ----------------------------------------------------------------------------- Severity: serious bug in iostreams Versions: occurs in both TC++ 1.00 and 1.01 Bug in istream: an extremely common C++ main input loop is something like: while (cin >> chunk) chunk.do_something(); This works under the condition that istream::operator void* returns 0 (false) after the input stream reads past EOF (or encounters an error). TC++'s iostream.h is consistent with its documentation [programmer's guide p.183] in stating that this operator returns 0 only when istream::fail() is true (when failbit, badbit or hardfail are set), but unfortunately `fail' doesn't get set after you've read past EOF. The correct operation is to return 0 (false) on the read after you've run into EOF as well [Lippman p.384], which CAN BE accomplished by the _bad bit being set wnen seeking past end-of-file [Lippman p.402]. This can be fixed by changing "ios::operator void*()" in as follows: inline _Cdecl ios::operator void* () { return (state & (eofbit|failbit|badbit|hardfail)) ? 0 : this; } NB: the `official' (if there is such a thing in these pre-ANSI-C++ days) behavior of istream::operator>> isn't clear. I've discussed this matter with both Borland, as Rob Murray, who is in charge of certain C++ libraries inside AT&T, and no one is yet sure what is really ``supposed'' to happen. The above patch (checking the eofbit) appears to work correctly, but it may be that a more comprehensive solution is eventually in order. In any event, most people's code doesn't run around checking individual bits inside an ios, so the above is probably `safe'. Borland says: Although this code happens to work in AT&T's CFRONT implementation of C++, there is no documentation stating how this code should operate. To test for EOF we recommend breaking the statement down ie ifstream joe; while (!joe.eof()) { joe >> ch; } This should be up for debate by the C++ developers. ----------------------------------------------------------------------------- Severity: serious bug in code generator Versions: only in TC++ 1.00; fixed in 1.01. There is an error in TCC that isn't in TC (they generate different code). [Borland is as surprised that they'd behave differently as I was; I'd imagine the internals are identical, and this assumption has be confirmed by Borland]. When a virtual fn is called from a non-virtual inline member, the virtualness of the call vanishes. Compile the following with `tcc -vi': #include class B { public: virtual void virt(); void nonvirt() { cout << "B::nonvirt() calling "; virt(); } }; class D : public B { public: void virt(); }; main() { D d; (&d)->nonvirt(); // B::nonvirt() should call D::virt() d.nonvirt(); // ditto } void B::virt() { cout << "B::virt()\n"; } void D::virt() { cout << "D::virt()\n"; } Unfortunately both of these call B::nonvirt(). Ie:Both d.nonvirt() & (&d)->nonvirt() translate to "call near ptr @B@virt$qv". Obviously these should be virtual function calls. This is a serious error, as calling a virtual from a non-virtual is fairly common. Note: if B::virt() is a pure virtual (another legal operation, but perhaps not as common), the call to "@B@virt$qv" generates a linker error (there is no B::virt()!). If B::nonvirt() is a regular (non-inline) function (either by moving it out of the class, or by `-v' or `-vi-'), TCC generates the correct code. Strangely enough, TC appears to *always* generate the correct code. Borland says: TRUE, and fixed as of version 1.01. ----------------------------------------------------------------------------- Severity: serious illegal naming of header file Versions: occurs in both TC++ 1.00 and 1.01 The 1.2 streams package (called ) is nice, however AT&T treats the inclusion of as an alias to . Therefore you should rename it from to , and let simply be: #include It is notable that a number of posters on comp.lang.c++ have been confused by including thinking they were getting ... Borland says: TRUE, but this is a 2.0 implementation and have included oldstreams as an added feature. My response: Including `old' streams was nice, since it will give users a chance to compile old code. Thank you. However the header file name is reserved, and should not be used as a method to include 1.2 streams: ``The stream.h include directive continues to be supported. It is treated as an alias for the iostream.h header file.'' [Lippman, `C++ Primier,' A/W, 1989, p.372] ----------------------------------------------------------------------------- Severity: non-serious incompatibility in rarely used header file Versions: occurs in both TC++ 1.00 and 1.01 Thanks to: Pete Humphrey / pete@edsr.eds.com / 505-345-1863 EDS Research / 5951 Jefferson Street NE / Albuquerque, NM 87109-3432 : Instead of using the usual "name2" style macros, Borland invented their own set of macros for concatenating pieces of names. Any code using the Stroustrup-style macros (eg. code compatable with g++, CC, or Zortech C++) will break. A work-around is to stick the following on the bottom of your : #define name2 _Paste2 #define name3 _Paste3 #define name4 _Paste4 Borland says: TRUE. As to why . . . copyright issues. [I hope that doesn't mean every compiler will be forced to use its own naming scheme! M.Cline] ----------------------------------------------------------------------------- Severity: ?? Versions: ?? Disclaimer: I haven't reproduced this bug, and Borland wasn't able to either Thanks to: Zerksis Umrigar/umrigar@bingvaxu.cc.binghamton.edu/SUNYBinghamton NY TC++ signals a compiler error when the LAST default parameter for a constructor is initialized by a constructor for some other class. A workaround is to add an extra dummy default parameter of some predefined type like int. Ex: if A and B are classes, then: This will be an error: A::A(B b = B(...)); But this is ok: A::A(B b = B(...), int dummy=0); Borland says: Could not reproduce. Response: Please let me know if anyone else can reproduce this. If it was a fluke in the particular program (or a wild pointer that trashed the code), I would like to remove this entry from the list PRONTO. Thanks. M.Cline. ----------------------------------------------------------------------------- Severity: serious bug in code generator Versions: occurs in TC++ 1.00; Borland says it's fixed in 1.01 Thanks to: Jamshid Afshar/jamshid@ccwf.cc.utexas.edu When an object is created as a side effect of an expression, and the expression is short-circuited, the destructor for the object seems to get called anyway. Ex: compile this with `tcc -vi-': extern "C" int rand(); #include class X { int xyz; int pqr; public: X create_copy_of_X() { return *this; } //Return *BY VALUE* X(); X(const X&); int whatever(); ~X(); }; void f(); void g(); main() { X x; if (rand()==0 && x.create_copy_of_X().whatever()) f(); else g(); } void f() { cout << "yes\n"; } void g() { cout << "no\n"; } X::X() : xyz(rand()), pqr(rand()) { cout << "constructing an X at " << ((void*)this) << "\n"; } X::~X() { cout << "destructing an X at " << ((void*)this) << "\n"; } X::X(const X& x) : xyz(x.xyz), pqr(x.pqr) { cout << "copying an X from " << ((void*)&x) << " to " << ((void*)this) << "\n"; } X::whatever() { return rand(); } At the close of main() (in fact, before `f()' is even called), TC++ calls a destructor on the copy of `x' even though the copy was never constructed. Borland says: True. Fixed in v1.01 ----------------------------------------------------------------------------- Severity: serious bug in code generator Versions: occurs in both TC++ 1.00 and 1.01 An explicit 2-parameter ctor in a by-value return statement is sometimes very useful in practice. Ex: a string concatenation operation, as it is typically implemented, uses much too much CPU; ex: class String { char* data; int len; friend String operator+(const String& A, const String& B); public: String(const char*); //... }; String operator+(const String& A, const String& B) { /*line-1*/ char* mem = new char[A.len + B.len + 1]; /*line-2*/ strcpy(mem, A.data); /*line-3*/ strcat(mem, B.data); /*line-4*/ String ans = mem; /*line-5*/ delete mem; /*line-6*/ return ans; } /*line-7*/ Couting freestore operations: line 1 uses a `new', line 4 uses a `new', line 5 uses a `delete', line uses string's copy ctor which uses another `new', and line 7 `delete's the temporary string `ans'. Final score: 3 new, 2 delete's. A smart compiler will construct `ans' in the location of the return value, which will reduce this to 2 new's and 1 delete. But a simple strategy can reduce it even further to 1 `new' and zero (0) delete's (even if the compiler isn't very smart): class String { char* data; int len; friend String operator+(const String& A, const String& B); String(char* _data, int _len) : data(_data), len(_len) { } public: String(const char*); //... }; String operator+(const String& A, const String& B) { /*line-1*/ char* mem = new char[A.len + B.len + 1]; /*line-2*/ strcpy(mem, A.data); /*line-3*/ strcat(mem, B.data); /*line-4*/ return String(mem, A.len + B.len); } /*line-5*/ The private constructor String::String(char*,int) is what I call a `trust-me' ctor. Ie: it is only usable by trusted routines, which I enforced by making it private. It just creates a String by copying the *pointer* & the length. The pointer is *assumed* to be a pointer allocated by `new char[...]', since ``String::~String()'' will `delete' the pointer. Furthermore the `length' is assumed to be correct. Final score: there's only ONE `new' in operator+ (on line 1) and ZERO `delete' operations. There are no dangling pointers, and no memory `leaks'. Ok, now to TC++'s problem: Line 4 [[``return String(mem, A.len + B.len);'']] is interpreted by TC++ to mean ``construct the return value right on the stack'' (ie: not a temporary object, but the actual return location). TC++ does the right thing by not creating a temporary, otherwise it would have to copy to the return location and then `destruct' the temporary. HOWEVER, TC++ then goes ahead an treats the constructed object as if it *were* a temporary, and it tries to copy it to the return location, which is where it already is! Thus the copy ctor is called with `this' pointing to *itself*. This is disasterous if copy ctor allocates memory; Ex: String::String(const String& S) { data = new char[S.len+1]; //If &S == this, changes S.data also!! strcpy(data, S.data); //Copy GARBAGE into itself len = S.len; } WORKAROUNDS: [1] If there's a one-parameter ctor that will automatically convert to the return value, then you don't need an explicit constructor in the return stmt. [2] If you need a more-than-one parameter ctor (ex: ``return complex(re,im)'', then you can construct a temporary and return the temporary by value. For example: complex foo() { //... complex my_return_value = complex(re, im); return my_return_value; } Unfortunately this won't be as fast since it requires an extra copy ctor and the temporary (``my_return_value'') has to be `destructed'. ----------------------------------------------------------------------------- Severity: non-serious missed feature Versions: occurs in both TC++ 1.00 and 1.01 Missed Feature: There is a subtle C++ bug in the code below. Even though the initialization list in String::String(const char*) looks like Len will get initialized before s, in fact the initialization order is fixed by the position within the class, which is just the opposite. Thus an arbitrary and unpredictable amount of storage will be allocated! (I put this `missed feature' in the `bug' report because TC++ users are used to TC++'s *excellent* warnings, and they may be puzzled if the compiler silently accepts code which is patently wrong. For example, TC++ complains about locals that are used before they're initialized, and this is an analogous situation.) class String { char* s; unsigned Len; public: String(const char* p); // char* --> String //... }; String::String(const char* p) : Len(strlen(p)), s(new char[Len+1]) { /////// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ strcpy(s, p); /////// Missed feature: TC++ should warn here } /////// Ex: `Len used before it is initialized' ----------------------------------------------------------------------------- Severity: ?? Versions: ?? Disclaimer: I haven't personally checked this -- MPC Thanks to: Joseph Puglielli, Santa Barbara, CA The #if/#else/#endif bracketed pair that follows should be functionally equivalent, but they're not: #include #include void error(const char*); #if 1 class Reader { ifstream input; char data[100]; public: Reader(const char* fname) { if (!input.open(fname, ios::nocreate | ios::binary)) error("can't open file in Reader::Reader(const char*)"); } ~Reader(const char* fname) { input.close(); } void getin() { memset(data, '\0', 100); if (!input) error("Reader::getin() -- input file is trashed"); for (int i = 0; i < 100 && !input.eof(); ++i) { char c; input.get(c); data[i] = c; } } }; main() { Reader r("input.txt"); r.getin(); } #else main() { ifstream input; char data[100]; char* fname = "input.txt"; if (!input.open(fname, ios::nocreate|ios::binary)) error("can't open file"); memset(data, '\0', 100); if (!input) error("main() -- input file is trashed"); for (int i = 0; i < 100 && !input.eof(); ++i) { char c; input.get(c); data[i] = c; } input.close(); } #endif ----------------------------------------------------------------------------- Severity: ?? Versions: ?? Disclaimer: I haven't personally checked this -- MPC Thanks to: Joseph Puglielli, Santa Barbara, CA The following two `x's shouldn't conflict, since `x' is a local `type' in each class. However they do conflict. class A { enum x { zero, one, two }; }; class B { enum x { apple, orange, pear }; }; ----------------------------------------------------------------------------- Severity: ?? Versions: ?? Disclaimer: I haven't personally checked this -- MPC Thanks to: Scott Schram / 72561,207 on compuserve / sshram on BIX: When you compile with -N (stack checking ON), TC++ declares _stklen at the first function definition (when it emits code to do the stack-overflow check). The only way you can use it to extend the stack is to include *BEFORE* your first function definition. Ex: this will fail: int a(void) { return 0; } // Implicitly causes _stklen to be declared unsigned _stklen = 20000; // This *RE*-declares _stklen... main() { a(); return 0; } Instead use this: #include // EXPLICITLY declares _stklen int a(void) { return 0; } // Implicitly causes _stklen to be declared unsigned _stklen = 20000; main() { a(); return 0; } ----------------------------------------------------------------------------- Severity: non-serious missed optimization Versions: ?? Disclaimer: I haven't personally checked this -- MPC Thanks to: Kevin Routley - Digital Equipment Corp, routley@tle.enet.dec.com BITFIELD OPERATION PERFORMANCE Bitfield operations are not optimal. See Example 1 for an example program. Testing the first bit of a multi-bit bitfield structure is done in 20 (8086) cycles with the TEST instruction: TEST WORD PTR [BP-06], 0001 The third bit in a multi-bit bitfield structure is testing by performing a pair of shift instructions, requiring 25 cycles: MOV AX, WORD PTR [BP-06] SHR AX, 1 SHR AX, 1 TEST AX, 0001 Larger bitfield structures use the multiple shift instruction to test higher bits. For example, the eighth bit requires seven shifts and 61 cycles to test: MOV AX, WORD PTR [BP-06] MOV CL, 7 SHR AX, CL TEST AX, 0001 This poor optimization places severe penalties on those programs that might choose to use bitfields to store flags instead of char objects. As far as space is concerned, it is more efficient to use a bitfield structure for three or more boolean flags rather than chars. The bitwise & operator illustrates that all three tests above could have been performed with individ- ual TEST instructions (a total of 63 cycles compared to the 106 cycles required by the above tests): TEST WORD PTR [BP-06], 0001 TEST WORD PTR [BP-06], 0004 TEST WORD PTR [BP-06], 0080 In fact, a series of similar (& or |) operations on bitfield structure elements could be optimized to a single TEST instruction. EXAMPLE BITFIELD PROGRAM: #include main() { struct foo { unsigned flag1 : 1; unsigned flag2 : 1; unsigned flag3 : 1; unsigned flag4 : 1; unsigned flag5 : 1; unsigned flag6 : 1; unsigned flag7 : 1; unsigned flag8 : 1; } flags; char lflag1 = 0; char lflag2 = 1; char lflag3 = 0; char lflag4 = 1; flags.flag1 = flags.flag8 = 1; if ( flags.flag1 && flags.flag8 && lflag2 && lflag4 ) printf("True\n"); else printf("False\n"); } ----------------------------------------------------------------------------- Severity: limitation of the preprocessor Versions: ?? Disclaimer: I haven't personally checked this -- MPC Thanks to: Kevin Routley - Digital Equipment Corp, routley@tle.enet.dec.com MACRO EXPANSION LIMIT The limitation of 4096 characters for a macro expansion is too small for non-toy programs. This can prevent Turbo C++ from being used as a development environment for large programs. Ideally a macro expansion should only be limited by the compiling machine's memory (if that, using memory swapping). As an example, multiple data structures can be easily and conveniently initialized from a single instance of the entered information (illustrated in Example 2. If table_data contains more than a few lines with more than a couple of items, and the author uses spaces to insure readibility, you can easily exceed the 4096 character limit. EXAMPLE: STRUCT INIT. FROM A SINGLE SET OF DATA /* setup the data to init all the structures with */ #define table_data \ X( "text1", 0, CONST1, 1, 1, 0, struct1, 1 ) \ X( "text1234", 0, CONST1234, 1, 1, 0, struct2, 1 ) \ . . . \ X( "text99", 0, CONST99, 0, 0, 0, structure99, 0 ) /* setup the macro to init table1. Only some of the fields are used.*/ #define X (string, foo, bar, fum, foobar, dataptr, flag )\ { foo, bar, fum, flag } /* table1 is an array of structs, each with four int fields*/ table1[MAX_TABLE] = { table_data }; #undef X /* setup a different macro to init table2. */ /* Different fields are used this time. */ #define X (string, foo, bar, fum, foobar, dataptr, flag )\ { flag, string } table2[MAX_TABLE] = { table_data }; #undef X ----------------------------------------------------------------------------- Severity: non-serious missed feature in integrated environment Versions: ?? Disclaimer: I haven't personally checked this -- MPC Thanks to: Kevin Routley - Digital Equipment Corp, routley@tle.enet.dec.com TC.EXE DISPLAY CUSTOMIZATION I am unable to customize the colors of the integrated environment so that they display reasonably on my monochrome VGA monitor. TCINST has an option "Mode for Display". No matter which option I select (e.g. Black and White, or Monochrome) to customize TC.EXE to have, I always get the same default color display mode when I invoke TC.EXE. The default mode is very difficult to read on my monochrome display. ----------------------------------------------------------------------------- Severity: non-serious error in support tool Versions: occurs in both TC++ 1.00 and 1.01 Disclaimer: I haven't personally checked this -- MPC Thanks to: Mark (M.S.) Lord : The GREP utility doesn't correctly display its permanent option settings. Ex: turn on the UNIX-style option and then display the options. The problem should be immediately obvious. You can patch the GREP.EXE by locating the table of screen offsets used internally for this purpose. ----------------------------------------------------------------------------- Severity: serious bug inside stream and/or fstream classes Versions: ?? Disclaimer: I haven't personally checked this -- MPC Thanks to: James J. Hartley / Dept CS / Univ of Missouri-Rolla jamesh@cs.umr.edu/jamesh@cs.umr.edu@umrvmb.bitnet/...!uunet!cs.umr.edu!jamesh //The following code represents the bug in its simplest form: #include #include #include class persist { char *fname; protected: fstream dskfile; // file stream declared in base class public: persist(char *fn) { fname = new char [strlen(fn) + 1]; strcpy(fname, fn); dskfile.open(fname, ios::in | ios::out); if (!dskfile) { cerr << "error in opening "<> k; } // I/O in derived class unsigned int get_constant() { return k; } }; main() { constant k("CONSTANT.TXT"); cout << "k = " << k.get_constant() << "\n"; // gives flaky results return 0; } As you can see, I am declaring a file stream instantiation in a base class and perform input in a derived class. Unpredictable values are displayed from both the integrated development environment and from the command-line. In talking to Lori in Technical Support at Borland, problems with declaring streams within classes have been reported before and is currently on their bug database. I found streams can be declared and used correctly within the scope of a member function (constructor). This can be seen in the following code: #include #include #include class constant { unsigned int k; char *fname; public: constant(char *fn) { // moves the stream to the constructor's scope fname = new char[strlen(fn) + 1]; strcpy(fname, fn); fstream dskfile(fname, ios::in | ios::out); dskfile >> k; dskfile.close(); } unsigned int get_constant() { return k; } ~constant() { delete fname; } }; main() { constant k("CONSTANT.TXT"); cout << "k = " << k.get_constant() << "\n"; // output is correct! return 0; } The obvious workaround is to revert to the file I/O conventions used in C (fopen() & fclose()). In simulating persistent objects, I have used this method and have gotten consistent results. ----------------------------------------------------------------------------- PS: If your company is interested in on-site C++/OOD training, drop me a line! PPS: Career search in progress; ECE faculty; research oriented; will send vita. -- Marshall Cline / Asst.Prof / ECE Dept / Clarkson Univ / Potsdam, NY 13676 cline@sun.soe.clarkson.edu / Bitnet:BH0W@CLUTX / uunet!clutx.clarkson.edu!bh0w Voice: 315-268-3868 / Secretary: 315-268-6511 / FAX: 315-268-7600 . Brought to you by Super Global Mega Corp .com