Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!swrinde!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: <9011211741.AA02872@cheetah.ece.clarkson.edu> Date: 21 Nov 90 17:41:54 GMT Sender: @grape.ecs.clarkson.edu Organization: Clarkson University Lines: 624 Approved: Clarkson University Name: Turbo-C++ bug list (bug-list-version 1.01.2) Products: Borland Turbo-C++ 1.00 Borland Turbo-C++ 1.01 Author: Marshall P. Cline, Ph.D. 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 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) 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 ============================================================================= ============================================================================= 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 ----------------------------------------------------------------------------- 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. ----------------------------------------------------------------------------- [[[[[[[[[[[[[[[[[[[[[[[ This is NOT fixed in TC++ 1.01 ]]]]]]]]]]]]]]]]]]]]]]] TCCNVT.EXE (the configuration file converter) isn't in the distribution. ----------------------------------------------------------------------------- [[[[[[[[[[[[[[[[[[[[[[[ This is NOT fixed in TC++ 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. ----------------------------------------------------------------------------- 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's comments: iostream.h is big and slow to compile. Other speedups are to remove all the comments from the file. This speeds up 286 machines. ----------------------------------------------------------------------------- needs but doesn't get it. Add this to : #ifndef __IOSTREAM_H #include #endif ----------------------------------------------------------------------------- [[[[[[[[[[[[[[[[[[[[[[[ This is NOT fixed in TC++ 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's comments: 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. ----------------------------------------------------------------------------- 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, and with a collegue 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's comments: 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. ----------------------------------------------------------------------------- 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's comments: TRUE, and fixed as of version 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's comments: 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] ----------------------------------------------------------------------------- : 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 This bug and its work-around is thanks to: Pete Humphrey / pete@edsr.eds.com / 505-345-1863 EDS Research / 5951 Jefferson Street NE / Albuquerque, NM 87109-3432 Borland's comments: 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] ----------------------------------------------------------------------------- This one is thanks to: Zerksis Umrigar/umrigar@bingvaxu.cc.binghamton.edu/SUNY Binghamton NY DISCLAIMER: I have not reproduced this alleged bug -- MPC 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's comments: Could not reproduce. Response: Please let me know if anyone else can reproduce this. If it was a fluke in the particular program (or worse: a user bug such as a wild pointer that trashed the code), I would like to remove this entry from the list PRONTO. Thanks. M.Cline. ----------------------------------------------------------------------------- This is 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: class X { public: X create_copy_of_X() { return *this; } //Return *BY VALUE* int whatever(); //... }; main() { X x; //... if (0 && x.create_copy_of_X().whatever()) stmt; //... } At the close of main(), TC++ calls a destructor on the copy of `x' even though the copy was never properly constructed. This only happens when inline functns are NOT inlined. Borland's comments: True. Fixed in v1.01 ----------------------------------------------------------------------------- [[[[[[[[[[[[[[[[[[[[[[[ This is NOT fixed in TC++ 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'. ----------------------------------------------------------------------------- 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' ----------------------------------------------------------------------------- The following was contributed by Joseph Puglielli, Santa Barbara, CA It has NOT been double checked by the editor of this list. 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 ----------------------------------------------------------------------------- The following was contributed by Joseph Puglielli, Santa Barbara, CA It has NOT been double checked by the editor of this list. 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 }; }; ----------------------------------------------------------------------------- This is 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; } ----------------------------------------------------------------------------- The following is 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"); } ----------------------------------------------------------------------------- The following is 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 ----------------------------------------------------------------------------- The following is 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. ----------------------------------------------------------------------------- This one is thanks to: Mark (M.S.) Lord : It applies to the GREP distributed with both TC++ 1.00 and 1.01. The GREP utility distributed with both TC++ 1.00 and 1.01 does not 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. -----------------------------------------------------------------------------