cline@cheetah.ece.clarkson.edu (Marshall Cline) (11/22/90)
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 <foo.h>. 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"; <file.h> 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. ----------------------------------------------------------------------------- <iostream.h> always includes <mem.h> which slows compilation some. In fact <iostream.h> doesn't need `NULL', and only needs memcpy() if _BIG_INLINE_ is defined, which will probably be rare. Therefore the line #include <mem.h> // to get memcpy and NULL can be replaced with: #ifdef _BIG_INLINE_ #include <mem.h> // to get memcpy #endif Since nearly everything includes <iostream.h>, and since relatively few things will want _BIG_INLINE_, this should be a winner. Note however that some code might assume <iostream.h> pulls in <mem.h>. 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. ----------------------------------------------------------------------------- <iomanip.h> needs <iostream.h> but doesn't get it. Add this to <iomanip.h>: #ifndef __IOSTREAM_H #include <iostream.h> #endif ----------------------------------------------------------------------------- [[[[[[[[[[[[[[[[[[[[[[[ This is NOT fixed in TC++ 1.01 ]]]]]]]]]]]]]]]]]]]]]]] There is no <new.h>. I constructed the following work-alike. It should go into your standard include directory (where <iostream.h> 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 <iostream.h> 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 <iostream.h> 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 <stream.h>) is nice, however AT&T treats the inclusion of <stream.h> as an alias to <iostream.h>. Therefore you should rename it from <stream.h> to <oldstream.h>, and let <stream.h> simply be: #include <iostream.h> It is notable that a number of posters on comp.lang.c++ have been confused by including <stream.h> thinking they were getting <iostream.h>... 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 <stream.h> 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] ----------------------------------------------------------------------------- <generic.h>: 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 <generic.h>: #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 <iostream.h> #include <fstream.h> 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 <dos.h> *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 <dos.h> // 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 <stdio.h> 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 <MLORD@bnr.ca.bitnet>: 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. -----------------------------------------------------------------------------
cline@cheetah.ece.clarkson.edu (Marshall Cline) (12/01/90)
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 <foo.h>. 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"; <file.h> 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 <iostream.h> always includes <mem.h> which slows compilation some. In fact <iostream.h> doesn't need `NULL', and only needs memcpy() if _BIG_INLINE_ is defined, which will probably be rare. Therefore the line #include <mem.h> // to get memcpy and NULL can be replaced with: #ifdef _BIG_INLINE_ #include <mem.h> // to get memcpy #endif Since nearly everything includes <iostream.h>, and since relatively few things will want _BIG_INLINE_, this should be a winner. Note however that some code might assume <iostream.h> pulls in <mem.h>. 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 <iomanip.h> needs <iostream.h> but doesn't get it. Add this to <iomanip.h>: #ifndef __IOSTREAM_H #include <iostream.h> #endif ----------------------------------------------------------------------------- Severity: missing header file Versions: occurs in both TC++ 1.00 and 1.01 There is no <new.h>. I constructed the following work-alike. It should go into your standard include directory (where <iostream.h> 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 <iostream.h> 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 <iostream.h> 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 <stream.h>) is nice, however AT&T treats the inclusion of <stream.h> as an alias to <iostream.h>. Therefore you should rename it from <stream.h> to <oldstream.h>, and let <stream.h> simply be: #include <iostream.h> It is notable that a number of posters on comp.lang.c++ have been confused by including <stream.h> thinking they were getting <iostream.h>... 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 <stream.h> 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 <generic.h>: 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 <generic.h>: #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 <iostream.h> 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 <iostream.h> #include <fstream.h> 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 <dos.h> *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 <dos.h> // 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 <stdio.h> 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 <MLORD@bnr.ca.bitnet>: 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 <fstream.h> #include <stdlib.h> #include <string.h> 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 "<<fname<<"\n"; exit(1); } } ~persist() { dskfile.close(); delete fname; } }; class constant : public persist { unsigned int k; public: constant(char *fn) : persist(fn) { dskfile >> 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 <fstream.h> #include <stdlib.h> #include <string.h> 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 .
cline@cheetah.ece.clarkson.edu (Marshall Cline) (01/01/91)
Name: Turbo-C++ bug list (bug-list-version 1.01.6) 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 17 Dec 90 1.01.5 31 Dec 90 1.01.6 (the last of the 1990 versions!) 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 Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 Several classlib\include\*.h have #include "foo.h" rather than <foo.h>. 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"; <file.h> 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 Workaround: Provided below 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 Workaround: None 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 Workaround: Put a copy of builtins.mak in your current working directory 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 Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 <iostream.h> always includes <mem.h> which slows compilation some. In fact <iostream.h> doesn't need `NULL', and only needs memcpy() if _BIG_INLINE_ is defined, which will probably be rare. Therefore the line #include <mem.h> // to get memcpy and NULL can be replaced with: #ifdef _BIG_INLINE_ #include <mem.h> // to get memcpy #endif Since nearly everything includes <iostream.h>, and since relatively few things will want _BIG_INLINE_, this should be a winner. Note however that some code might assume <iostream.h> pulls in <mem.h>. 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 Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 <iomanip.h> needs <iostream.h> but doesn't get it. Add this to <iomanip.h>: #ifndef __IOSTREAM_H #include <iostream.h> #endif ----------------------------------------------------------------------------- Severity: Missing header file Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 There is no <new.h>. I constructed the following work-alike. It should go into your standard include directory (where <iostream.h> 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 Workaround: Provided below 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 <iostream.h> 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 Workaround: Provided below 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 <iostream.h> 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 Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 The 1.2 streams package (called <stream.h>) is nice, however AT&T treats the inclusion of <stream.h> as an alias to <iostream.h>. Therefore you should rename it from <stream.h> to <oldstream.h>, and let <stream.h> simply be: #include <iostream.h> It is notable that a number of posters on comp.lang.c++ have been confused by including <stream.h> thinking they were getting <iostream.h>... 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 <stream.h> 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 Workaround: Provided below 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 <generic.h>: 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 <generic.h>: #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: ?? Workaround: Provided below Versions: ?? Disclaimer: I haven't reproduced this bug, and Borland wasn't able to either Thanks to: Zerksis Umrigar/umrigar@bingvaxu.cc.binghamton.edu/SUNYBinghamtonNY 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 Workaround: None provided 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 <iostream.h> 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 Workaround: Provided below 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: Probably the same serious bug in code generator as above Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 Thanks to: Jamshid Afshar <jamshid@emx.utexas.edu> The above explicit 2-parameter ctor in a by-value return statement is apparently part of a bigger pattern. The problem appears to be that a by-value return statement always calls the copy constructor to copy the returned object back into the caller's frame. However when the returned expression is already constructed in the caller's frame, this causes a `copy to self'. Basically then, whenever the returned object from a return-by-value function is constructed right inside the `return' statement, this will copy the object to itself. Fortunately this doesn't always cause problems. Ex: copying a complex number to itself will simply copy the real and the imaginary parts to themselves, which won't change a thing. However the String example above shows how it can be disastrous. The following demonstrates this more general pattern: #include <iostream.h> #include <stdlib.h> class X { int x; public: X() : x(1) { } X(const X& f); }; X::X(const X& f) : x(f.x) { if (this==&f) {cout << "Copying an object to itself!!" << endl; abort();} } X bar() { X x; return x; } // create temp local X, then copy it back to caller X foo() { return bar(); } // return a return value; copies to itself; BOOM! main() { X x = foo(); return 0; } ----------------------------------------------------------------------------- Severity: Non-serious missed feature Workaround: None provided 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: ?? Workaround: None provided 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 <iostream.h> #include <fstream.h> 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: ?? Workaround: None provided 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: ?? Workaround: Provided below 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 <dos.h> *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 <dos.h> // 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 Workaround: None provided 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 <stdio.h> 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 Workaround: None provided 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 Workaround: None provided 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 Workaround: Non-detailed workaround provided below Versions: Occurs in both TC++ 1.00 and 1.01 Disclaimer: I haven't personally checked this -- MPC Thanks to: Mark (M.S.) Lord <MLORD@bnr.ca.bitnet>: 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 Workaround: Provided below 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 <fstream.h> #include <stdlib.h> #include <string.h> 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 "<<fname<<"\n"; exit(1); } } ~persist() { dskfile.close(); delete fname; } }; class constant : public persist { unsigned int k; public: constant(char *fn) : persist(fn) { dskfile >> 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 <fstream.h> #include <stdlib.h> #include <string.h> 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. ----------------------------------------------------------------------------- Severity: Inconsistency w/ C language defn of unsigned'ness of short and int Workaround: None provided Versions: 1.00 and possibly 1.01 Disclaimer: I haven't personally checked this -- MPC Thanks to: Leonid A. Broukhis <leo@s514.ipmce.su> Inst Prec Mech&Comp Equip, Moscow, USSR X/OPEN part 4, C Language definition, 2.5.5, 2.5.6.1, 2.5.6.6 defines the type conversion rules as follow: 2.5.5 Unsigned Whenever an unsigned integer and a plain integer are combined, the plain int is converted to unsigned and the result is unsigned. ... In a 2's complement representation, this conversion is conceptual; there is no actual change in the bit pattern. 2.5.6 Arithmetic Conversions 1. Any operands of type char or short are converted to int, and any operands of type unsigned char or unsigned short are converted to unsigned int. 6. [ Otherwise ], if either operand is unsigned, the other is converted to unsigned and that is the type of the result. Ex: The following two programs work differently: main() {int i=3; unsigned int u=0xffff; if (u<i) puts("!!");} //no output main() {int i=3; unsigned short u=0xffff; if (u<i) puts("!!");} //outputs `!!' ----------------------------------------------------------------------------- Severity: Makefile produced by PRJ2MAK may need some manual editing Workaround: Provided below Versions: 1.00 and possibly 1.01 Disclaimer: I haven't personally checked this -- MPC Thanks to: Joe Dubner/HP/Spokane WA 99220/509-921-3514/dubner@hpspkla.hp.com The makefile that PRJ2MAK version 1.0 produces doesn't include the overlay manager library in huge-model, overlayed projects. Then, naturally enough, TLINK fails with a warning: Warning: Overlays generated and no overlay manager included Similar errors appear for undefined symbols that are referenced in the code (ex: __OvrInitExt and __OvrInitEms). A suitable workaround appears to be to edit the makefile to include: \tc\lib\overlay.lib+ in the linker section, right after linking the graphics library. ----------------------------------------------------------------------------- Severity: Uncommon bug in the parse of: delete [num] (ptr-expr) Workaround: Provided below Versions: 1.00 and possibly 1.01 Disclaimer: I haven't personally checked this -- MPC Thanks to: Bradley Keister/Physics Dept/Carnegie Mellon U keister@poincare.phys.cmu.edu The following is ++ified code from Numerical Recipes: double* get_vector(int nl, int nh) { double* v; v = new double[nh-nl+1]; return v-nl; } void free_vector(double* v, int nl) { delete[4](v+nl); } ^^^^^^^^^^^^^^^ TC++ gives error: "Too few params in call to 'delete(void far*, unsigned int)'" Replacing it with "delete v+nl;" gives "Value of type void is not allowed..." Replacing it with "delete v;" will cause TC++ to not complain, though `v' must then be bumped by hand with an extra step, and then it will generate incorrect code if `p' points to an object whose class has a destructor (``delete[]p'' and ``delete p'' are different --- see ARM p.64-65). The workaround is: void free_vector(double* v, int nl) { v += nl; delete [4] v; } Though naturally the 4 (----^^^----) will have to be replaced with something more meaningful. When TC++ becomes compliant with C++ 2.1, we can just say: void free_vector(double* v, int nl) { v += nl; delete [] v; } Hopefully by then the bug will be fixed, and we'll be able to simply say: void free_vector(double* v, int nl) { delete [] (v+nl); } PS: the original example compiles under cfront. ----------------------------------------------------------------------------- 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 .
garnett@roadster.bellcore.com (Michael Garnett) (01/03/91)
In article <9101010531.AA05142@cheetah.ece.clarkson.edu>, cline@cheetah.ece.clarkson.edu (Marshall Cline) writes: |> |> Severity: ?? |> Workaround: None provided |> 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 }; }; |> |> ------------------------------------------------------------------------ ------ C++ does not support type nesting until 2.1. TC++ is a 2.0 compiler. Hence, the name conflict. In 2.1, their names are A::x and B::x. ------------------------------------------------------------------------ --------- ********* Michael S. Garnett ** o ** Information Networks Research ** _- -_ ** Ph: 201-829-4591 ** / \ ** Internet: garnett@thumper.bellcore.com * | | * UUCP: ...!bellcore!thumper!garnett ** | | ** ** /_______\ ** // I speak for only myself, and NOT any company or ** o ** // organization directly or indirectly associated with ********* // Bell Communications Research
Marshall Cline <cline@cheetah.ece.clarkson.edu> (05/01/91)
Name: Turbo-C++ bug list (bug-list-version 1.01.8) Products: Borland Turbo-C++ 1.00 Borland Turbo-C++ 1.01 Borland Borland-C++ 2.00 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 Without FTP: You can also get it by sending electronic mail: To: archive-server@sun.soe.clarkson.edu Subject: send pub/Turbo-C++/bug-report This will help those who don't have ftp. 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 17 Dec 90 1.01.5 31 Dec 90 1.01.6 (the last of the 1990 versions!) 05 Mar 91 1.01.7 (beginning of the 2.0 comments) 09 Apr 91 1.01.8 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 Coordination: Starting with bug-report version 1.01.7, I've changed the format some. New bugs now appear at the top of the list. This will make it easier for `old' readers to get a quick update. Also, `missed features' and other `non-serious' problems are moved to the bottom of the list. ######### ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug report version 1.01.8 starts here ######### ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug-report: 1.01.8 Severity: Not a bug, but something to be careful of... Workaround: Several given Versions: 2.0 and before Thanks to: Jamshid Afshar <jamshid@emx.utexas.edu> Also to: Sidney Markowitz <sidney@borland.com> `abort()' calls `_exit(3)', which does NOT close files. This behavior is allowed by ANSI C, but the results can be rather surprising. Ex: compile the following VROOM program with ``bcc -ml -Yo main.cpp ovl.cpp'': // main.cpp #include <dos.h> void f(); int main() { _OvrInitExt(0,0); f(); return 0;} // ovl.cpp #include <stdlib.h> #include <iostream.h> void f() { cerr << "f() called\n"; abort(); } Since `abort()' is called from inside an overlay, a 16K XMS file handle is left allocated after yor get your DOS prompt back. The *behavior* is like a memory leak, so it is probably undesirable (though still `legal' in the ANSI-C sense). Workarounds: * Call exit(3) instead of abort() so the cleanup routines are called and VROOMM's handles are released. * Write your own `myabort()' which calls exit(3) instead of _exit(3). * Modify the defn of `abort()' to call exit(3) instead of _exit(3). [the defn of abort() is in c0.asm]. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.8 Severity: Default class-object params don't mix with precompiled headers Workaround: Several provided below Versions: 2.00 Thanks to: Tony Hodgson / ahodgson@hstbme.mit.edu //File Bar.h: class Foo { public: Foo() { } }; class Bar { public: Bar(Foo f = Foo()) { } }; //File main.cpp: #include <iostream.h> #include "Bar.h" main() { Bar b; cout << "Done.\n"; } * If one builds this program with precompiled headers ON, it runs fine. * If you then touch main.cpp and recompile, the .obj and .exe files have a different size and the program crashes your system when you run it. * This happens in the IDE under any memory model and regardless of the state of the DEBUG INFO IN OBJ option. * If the Bar ctor is changed to ``Bar::Bar(int b=1)'', all works fine. * The bug doesn't show up on top-level functions; ex: void f(Foo f = Foo()); Workarounds: (1) Turn precompilation off :-( (2) Construct a static *object* of class `Foo()', and use that *object* as an initializer. ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug report version 1.01.7 and earlier starts here ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug-report: 1.01.7 Severity: istream::getline(char* buff, int maxlen, char delim) keeps the '\n' Workaround: Provided below Versions: 1.00 and 1.01 Fixed in: Fixed in 2.0 Thanks to: Michael Schwartz/U.Denver/New College/mschwartz@diana.cair.du.edu Typical scenario: char buffer[100]; cin.getline(buffer, 100); istream::getline(char* buffer, int maxlen, char delim='\n') copies the delim (usually '\n') into the buffer. This is non-standard. Early drafts of the upcoming ANSI-C++ standard show that conforming implementations must extract the '\n' from the istream, but *not* insert it into the buffer. A workaround would be to redefine getline(char*,int,char) so it calls get(char*,int,char), then calls ignore() to eat the delim char. Ex: class istream .... { //... istream& get(char* buff, int lim, char delim='\n') { get(buff, lim, delim); ignore(); } //... }; This is technically incorrect due to the fact that a conforming implementation will leave the `delim' char in the stream if it appears when *exactly* `lim' chars have already been read. A more complicated version could handle this by checking ``istream::gcount()''. The above also has the problem of when `lim' is reached by `get()' in that it eats the `lim+1' char in `ignore()'. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.7 Severity: The `placement new' operator is missing from <new.h> Workaround: Provided below Versions: <new.h> was introduced in 2.0 If you have a 1.xx compiler, use my <new.h> replacement below There is no `placement new' in <new.h>. This is one of the main reasons to include <new.h>, so it its omission is unfortunate. To fix it, add the following line to <new.h>: void _FAR* operator new(unsigned, void _FAR* place) { return place; } The `unsigned' should be `size_t' in a portable <new.h>, but Borland chose to use `unsigned' rather than to #include another header to get `size_t'. For pre-2.0 users, the rest of <new.h> is given in another bug-report below. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.7 Severity: Memory limitation error in the `README' Workaround: Get more memory :-) Versions: 2.0 The `README' says: > Pg. 158 In the first paragraph it says you need at least 576k of > extended memory. The correct amount is 320k. But an actual test of `bccx test.cpp' on my 1MB machine reveals: > Error: 384 KB of extended memory is available, but 450 KB is required. ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug report version 1.01.6 and earlier starts here ######### ######### ######### ######### ######### ######### ######### ######### ######### Severity: Missing header file Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 Fixed in: Fixed in 2.0 There is no <new.h>. I constructed the following work-alike. It should go into your standard include directory (where <iostream.h> 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. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: modified in 1.01.8 Severity: Serious bug in iostreams Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 Fixed in: 2.0 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 when seeking past end-of-file [Lippman p.402]. This can be fixed by changing "ios::operator void*()" in <iostream.h> 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; } UPDATE! The ANSI-C++ streams spec will insist that the ``while(cin>>chunk)'' idiom works. Borland C++ 2.0 gets it right. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.00.1 Severity: Serious bug in code generator Workaround: Provided below 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 <iostream.h> 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 Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01, and in BC++ 2.0 The 1.2 streams package (called <stream.h>) is nice, however AT&T treats the inclusion of <stream.h> as an alias to <iostream.h>. Therefore you should rename it from <stream.h> to <oldstream.h>, and let <stream.h> simply be: #include <iostream.h> It is notable that a number of posters on comp.lang.c++ have been confused by including <stream.h> thinking they were getting <iostream.h>... 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 <stream.h> 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: <This one may not be a bug at all; read the disclaimer> Workaround: Provided below Versions: ?? Disclaimer: I haven't reproduced this bug, and Borland wasn't able to either Thanks to: Zerksis Umrigar/umrigar@bingvaxu.cc.binghamton.edu/SUNYBinghamtonNY 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 Workaround: None provided 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 <iostream.h> 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 Workaround: Provided below 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; } WORKAROUND #1: put an explicit ``if (this == &S)'' test at the head of the copy ctor (credit for this clean and effective solution goes to Stephen Bellantoni, sjb@cs.toronto.edu). Ex: String::String(const String& S) { if (this == &S) return; //Protect against TC++ 1.00 & 1.01 bug data = new char[S.len+1]; //If &S == this, changes S.data also!! strcpy(data, S.data); //Copy GARBAGE into itself len = S.len; } Naturally this won't work for those things that need an initialization list, but for things that have identical semantics for assignment as for copy-ctor, this should suffice. WORKAROUND #2: 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. WORKAROUND #3: 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. Ex: complex foo() { //... complex my_return_value = complex(re, im); return my_return_value; } Unfortunately workaround #3 won't be as fast since it requires an extra copy ctor and the temporary (``my_return_value'') has to be `destructed'. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Probably the same serious bug in code generator as above Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 Thanks to: Jamshid Afshar <jamshid@emx.utexas.edu> The above explicit 2-parameter ctor in a by-value return statement is apparently part of a bigger pattern. The problem appears to be that a by-value return statement always calls the copy constructor to copy the returned object back into the caller's frame. However when the returned expression is already constructed in the caller's frame, this causes a `copy to self'. Basically then, whenever the returned object from a return-by-value function is constructed right inside the `return' statement, this will copy the object to itself. Fortunately this doesn't always cause problems. Ex: copying a complex number to itself will simply copy the real and the imaginary parts to themselves, which won't change a thing. However the String example above shows how it can be disastrous. The following demonstrates this more general pattern: #include <iostream.h> #include <stdlib.h> class X { int x; public: X() : x(1) { } X(const X& f); }; X::X(const X& f) : x(f.x) { if (this==&f) {cout << "Copying an object to itself!!" << endl; abort();} } X bar() { X x; return x; } // create temp local X, then copy it back to caller X foo() { return bar(); } // return a return value; copies to itself; BOOM! main() { X x = foo(); return 0; } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Serious problem with ifstreams Workaround: Provided below Versions: 1.00 and 1.01 Fixed in: 2.0 Thanks to: Joseph Puglielli, Santa Barbara, CA Also to: Michael Schwartz/U.Denver/New College/mschwartz@diana.cair.du.edu #include <fstream.h> class InFile { ifstream ifs; public: InFile(const char* filename); //... }; #if 1 InFile::InFile(const char* filename) : ifs(filename) { } //Works #else InFile::InFile(const char* filename) { ifs.open(filename); } //Fails //This fills the buffer with garbage! #endif The basic problem appears to be that fstreambuf::open(const char*) is unreliable. Use the constructor version instead. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: ?? Workaround: Provided below 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 <dos.h> *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 <dos.h> // EXPLICITLY declares _stklen int a(void) { return 0; } // Implicitly causes _stklen to be declared unsigned _stklen = 20000; main() { a(); return 0; } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Limitation of the preprocessor Workaround: None provided 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: Inconsistency w/ C language defn of unsigned'ness of short and int Workaround: None provided Versions: 1.00 and possibly 1.01 Disclaimer: I haven't personally checked this -- MPC Thanks to: Leonid A. Broukhis <leo@s514.ipmce.su> Inst Prec Mech&Comp Equip, Moscow, USSR X/OPEN part 4, C Language definition, 2.5.5, 2.5.6.1, 2.5.6.6 defines the type conversion rules as follow: 2.5.5 Unsigned Whenever an unsigned integer and a plain integer are combined, the plain int is converted to unsigned and the result is unsigned. ... In a 2's complement representation, this conversion is conceptual; there is no actual change in the bit pattern. 2.5.6 Arithmetic Conversions 1. Any operands of type char or short are converted to int, and any operands of type unsigned char or unsigned short are converted to unsigned int. 6. [ Otherwise ], if either operand is unsigned, the other is converted to unsigned and that is the type of the result. Ex: The following two programs work differently: main() {int i=3; unsigned int u=0xffff; if (u<i) puts("!!");} //no output main() {int i=3; unsigned short u=0xffff; if (u<i) puts("!!");} //outputs `!!' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.8 Severity: Makefile produced by PRJ2MAK still needs some manual editing Workaround: Provided below Versions: 2.0 Thanks to: Joe Dubner/HP/Spokane WA 99220/509-921-3514/dubner@hpspkla.hp.com [[NOTE THAT THIS IS A DIFFERENT BUG FROM THE VERSION 1.01 PRJ2MAK BUG BELOW]] The makefile that PRJ2MAK version 2.0 produces includes a spurious "/o" flag. This is an excerpt from the Makefile that PRJ2MAK produced from a .PRJ file: # *Explicit Rules* pn.exe: pn.cfg $(EXE_dependencies) $(TLINK) /v/x/c/d/o/P-/L\BC\LIB @&&| /o- c0h.obj+ Note that the "/o" option in the TLINK line is incorrect. "/o" means "overlay the following modules or libraries", but there is no module or library following the "/o"; there's just another cmd line option. TLINK fails with: Fatal: Unable to open file 'p- Pc$;Y]UJZ' (edited somewhat) The workaround is to edit the makefile produced by PRJ2MAK and delete the "/o" in the TLINK line. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: modified in 1.01.8 Severity: Makefile produced by PRJ2MAK may need some manual editing Workaround: Provided below Versions: 1.01 Fixed in: 2.0 Disclaimer: I haven't personally checked this -- MPC Thanks to: Joe Dubner/HP/Spokane WA 99220/509-921-3514/dubner@hpspkla.hp.com [[NOTE THAT THIS IS A DIFFERENT BUG FROM THE VERSION 2.0 PRJ2MAK BUG ABOVE]] The makefile that PRJ2MAK version 1.01 produces doesn't include the overlay manager library in overlayed projects. TLINK then fails with: Warning: Overlays generated and no overlay manager included Similar errors appear for undefined symbols that are referenced in the code (ex: __OvrInitExt and __OvrInitEms). A suitable workaround appears to be to edit the makefile to include: \tc\lib\overlay.lib+ in the linker section, just prior to linking the fp87.lib or emu.lib library. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Uncommon bug in the parse of: delete [num] (ptr-expr) Workaround: Provided below Versions: 1.00 and possibly 1.01 Disclaimer: I haven't personally checked this -- MPC Thanks to: Bradley Keister/Physics Dept/Carnegie Mellon U keister@poincare.phys.cmu.edu The following is ++ified code from Numerical Recipes: double* get_vector(int nl, int nh) { double* v; v = new double[nh-nl+1]; return v-nl; } void free_vector(double* v, int nl) { delete[4](v+nl); } ^^^^^^^^^^^^^^^ TC++ gives error: "Too few params in call to 'delete(void far*, unsigned int)'" Replacing it with "delete v+nl;" gives "Value of type void is not allowed..." Replacing it with "delete v;" will cause TC++ to not complain, though `v' must then be bumped by hand with an extra step, and then it will generate incorrect code if `p' points to an object whose class has a destructor (``delete[]p'' and ``delete p'' are different --- see ARM p.64-65). The workaround is: void free_vector(double* v, int nl) { v += nl; delete [4] v; } Though naturally the 4 (----^^^----) will have to be replaced with something more meaningful. When TC++ becomes compliant with C++ 2.1, we can just say: void free_vector(double* v, int nl) { v += nl; delete [] v; } Hopefully by then the bug will be fixed, and we'll be able to simply say: void free_vector(double* v, int nl) { delete [] (v+nl); } PS: the original example compiles under cfront. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: The effects of this bug will show up randomly :-) Workaround: Repair stdlib.h as indicated below Versions: 1.00 Fixed in: 1.01 Thanks to: Jim Seymour [qintar@agora.UUCP, qintar@agora.rain.com] Cipher Systems Inc, PO Box 329, North Plains, OR 97133 Also to: George Theall/SSWS14@penndrls.upenn.edu/SSWS14@penndrls.bitnet Social Sci Computing/Univ Penn/Philadelphia, PA 19104/215-898-3419 Also to: Per Westermark/Dept of inf.proc./University of Umea, Sweden mapwk@cs.umu.se `random(int)' is an inline function [C++] and a macro [C] that produces a random num from 0 to N-1. It's defined in <stdlib.h> as: #ifdef __cplusplus inline int _Cdecl random(int __num) { return (int)(((long)rand()*__num)/RAND_MAX); } #else #define random(num) (int)(((long)rand()*(num))/RAND_MAX) #endif This definition is wrong, as `random(n)' occasionally returns large numbers. The solution is to replace ``RAND_MAX'' by ``((unsigned)RANDMAX + 1)''. Ie: replace the above definitions with (hold on to your hat!): #ifdef __cplusplus inline int _Cdecl random(int __num) { return (int)((long)rand()*__num)/(unsigned(RAND_MAX)+1); } #else #define random(num) (int)(((long)rand()*(num))/((unsigned)RAND_MAX+1)) #endif Note that I used ``unsigned(RAND_MAX)'' in the C++ version (just for fun :-). ######### ######### ######### ######### ######### ######### ######### ######### ######### Missed features and other non-serious bugs start here ######### ######### ######### ######### ######### ######### ######### ######### ######### Severity: Non-serious incompatibility in rarely used header file Workaround: Provided below 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 <generic.h>: 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 <generic.h>: #define name2 _Paste2 #define name3 _Paste3 #define name4 _Paste4 Borland says: TRUE. As to why . . . copyright issues. My response: I hope that doesn't mean every compiler will be forced to use its own (unique and non-portable) naming scheme! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Not serious Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 Several classlib\include\*.h have #include "foo.h" rather than <foo.h>. 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"; <file.h> 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 Workaround: Provided below Versions: 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: Bug in support tool Workaround: Put a copy of builtins.mak in your current working directory 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 Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 <iostream.h> always includes <mem.h> which slows compilation some. In fact <iostream.h> doesn't need `NULL', and only needs memcpy() if _BIG_INLINE_ is defined, which will probably be rare. Therefore the line #include <mem.h> // to get memcpy and NULL can be replaced with: #ifdef _BIG_INLINE_ #include <mem.h> // to get memcpy #endif Since nearly everything includes <iostream.h>, and since relatively few things will want _BIG_INLINE_, this should be a winner. Note however that some code might assume <iostream.h> pulls in <mem.h>. 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 Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 <iomanip.h> needs <iostream.h> but doesn't get it. Add this to <iomanip.h>: #ifndef __IOSTREAM_H #include <iostream.h> #endif - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Non-serious missed feature in integrated environment Workaround: None provided 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 Workaround: Non-detailed workaround provided below Versions: Occurs in both TC++ 1.00 and 1.01 Disclaimer: I haven't personally checked this -- MPC Thanks to: Mark (M.S.) Lord <MLORD@bnr.ca.bitnet>: 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: Missed feature Workaround: None Versions: Occurs in both TC++ 1.00 and 1.01 TCCNVT.EXE (the configuration file converter) isn't in the distribution. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Non-serious missed feature Workaround: None provided 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: Non-serious missed optimization Workaround: None provided 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 <stdio.h> 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"); } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Marshall Cline PS: If your company is interested in on-site C++/OOD training, drop me a line! -- 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 ..
Marshall Cline <cline@sun.soe.clarkson.edu> (07/01/91)
Name: Turbo-C++ bug list (bug-list-version 1.01.9) Products: Borland Turbo-C++ 1.00 Borland Turbo-C++ 1.01 Borland Borland-C++ 2.00 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 Without FTP: You can also get it by sending electronic mail: To: archive-server@sun.soe.clarkson.edu Subject: send pub/Turbo-C++/bug-report This will help those who don't have ftp. 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 17 Dec 90 1.01.5 31 Dec 90 1.01.6 (the last of the 1990 versions!) 05 Mar 91 1.01.7 (beginning of the 2.0 comments) 09 Apr 91 1.01.8 03 Jun 91 1.01.9 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 Coordination: Starting with bug-report version 1.01.7, I've changed the format some. New bugs now appear at the top of the list. This will make it easier for `old' readers to get a quick update. Also, `missed features' and other `non-serious' problems are moved to the bottom of the list. ######### ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug report version 1.01.9 starts here ######### ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug-report: 1.01.9 Severity: Serious (wrong code generated) bug in huge arrays Workaround: None Versions: 1.01 (it is unknown whether 2.0 also has this bug) Thanks: Carl Mascott/31 Englewood Ave/Brookline,MA 02146/617-566-6867 cmascott@world.std.com The following demonstrates a bug in compile-time calculation of addr of an elem in a static huge array. Examine the assembly language output of TCC 1.01. Regardless of the segment in which the element resides, TCC uses the first segment. TC appears to do the same thing. long huge huge_l[17500]; main() { long huge *hpl = &huge_l[17000]; } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.9 Severity: IDE debugger displays elements of static huge arrays incorrectly Workaround: None Versions: 1.01 (it is unknown whether 2.0 also has this bug) Thanks: Carl Mascott/31 Englewood Ave/Brookline,MA 02146/617-566-6867 cmascott@world.std.com 1. Compile the following program within the IDE. 2. Set a breakpoint on "return 0;". 3. Set watches on i, huge_l[i] and huge_l[17000]. 4. Run the program to the breakpoint. You will see two different and incorrect values displayed for huge_l[17000]. The correct value is 17000. long huge huge_l[17500]; main() { for (int i = 0; i < 17500; ++i) huge_l[i] = i; i = 17000; return 0; } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.9 Severity: Serious: far data is sometimes put in the wrong group Workaround: None Versions: 1.00, 1.01 and 2.00 Thanks: Blake McBride <root@blakex.raidernet.com> File fd1.c: char* far abc[10000]; main() { } File fd2.c: extern char* far abc[]; char* far abc[10000]; main() { } Execute the following: tcc -a -d -N -O -G -IC:\TC\INCLUDE -ml -M -ls fd1.c tcc -a -d -N -O -G -IC:\TC\INCLUDE -ml -M -ls fd2.c If you compare the two map files You'll find that fd1.map shows the far data in in segment FD15_FAR, group (none) which is correct. Although fd2.map also shows a far data area (FD25_FAR), its length is 0! If you look closer you'll find the data in segment _BSS, group DGROUP!! The only difference between fd1.c and fd2.c is an extern declaration which shouldn't make any difference when the data is declared in that module. This problem shows up very often with include files and global variables. ######### ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug report version 1.01.8 starts here ######### ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug-report: 1.01.8 Severity: Serious, but rarely comes up Workaround: One given below Versions: At least 1.01 Thanks: Paul Vojta <vojta@math.berkeley.edu> Also: M. D. Lawler <mdlawler@bsu-cs.bsu.edu> The space allocated by sbrk() may be overwritten by fopen() in huge, large, and compact memory models only. fopen() appears to round to a paragraph boundary incorrectly. This problem may (or may not!) also occur with malloc() in place of fopen(). Demonstration: save the following as `brktest.c' (so fopen() can open a file). #include <stdio.h> char* sbrk(); main() { char* ptr; FILE* file; ptr = sbrk(31); strcpy(ptr, "This string -has- thirty bytes"); puts(ptr); file = fopen("brktest.c", "r"); puts(ptr); } Workaround: * Insert an extra ``sbrk(15);'' stmt between the calls to sbrk() and fopen(). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.8 Severity: Not a bug, but something to be careful of... Workaround: Several given Versions: 2.0 and before Thanks: Jamshid Afshar <jamshid@emx.utexas.edu> Also: Sidney Markowitz <sidney@borland.com> `abort()' calls `_exit(3)', which does NOT close files. This behavior is allowed by ANSI C, but the results can be rather surprising. Ex: compile the following VROOM program with ``bcc -ml -Yo main.cpp ovl.cpp'': // main.cpp #include <dos.h> void f(); int main() { _OvrInitExt(0,0); f(); return 0;} // ovl.cpp #include <stdlib.h> #include <iostream.h> void f() { cerr << "f() called\n"; abort(); } Since `abort()' is called from inside an overlay, a 16K XMS file handle is left allocated after yor get your DOS prompt back. The *behavior* is like a memory leak, so it is probably undesirable (though still `legal' in the ANSI-C sense). Workarounds: * Call exit(3) instead of abort() so the cleanup routines are called and VROOMM's handles are released. * Write your own `myabort()' which calls exit(3) instead of _exit(3). * Modify the defn of `abort()' to call exit(3) instead of _exit(3). [the defn of abort() is in c0.asm]. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.8 Severity: Default class-object params don't mix with precompiled headers Workaround: Several provided below Versions: 2.00 Thanks: Tony Hodgson / ahodgson@hstbme.mit.edu //File Bar.h: class Foo { public: Foo() { } }; class Bar { public: Bar(Foo f = Foo()) { } }; //File main.cpp: #include <iostream.h> #include "Bar.h" main() { Bar b; cout << "Done.\n"; } * If one builds this program with precompiled headers ON, it runs fine. * If you then touch main.cpp and recompile, the .obj and .exe files have a different size and the program crashes your system when you run it. * This happens in the IDE under any memory model and regardless of the state of the DEBUG INFO IN OBJ option. * If the Bar ctor is changed to ``Bar::Bar(int b=1)'', all works fine. * The bug doesn't show up on top-level functions; ex: void f(Foo f = Foo()); Workarounds: (1) Turn precompilation off :-( (2) Construct a static *object* of class `Foo()', and use that *object* as an initializer. ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug report version 1.01.7 and earlier starts here ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug-report: 1.01.7 Severity: istream::getline(char* buff, int maxlen, char delim) keeps the '\n' Workaround: Provided below Versions: 1.00 and 1.01 Fixed in: Fixed in 2.0 Thanks: Michael Schwartz/U.Denver/New College/mschwartz@diana.cair.du.edu Typical scenario: char buffer[100]; cin.getline(buffer, 100); istream::getline(char* buffer, int maxlen, char delim='\n') copies the delim (usually '\n') into the buffer. This is non-standard. Early drafts of the upcoming ANSI-C++ standard show that conforming implementations must extract the '\n' from the istream, but *not* insert it into the buffer. A workaround would be to redefine getline(char*,int,char) so it calls get(char*,int,char), then calls ignore() to eat the delim char. Ex: class istream .... { //... istream& get(char* buff, int lim, char delim='\n') { get(buff, lim, delim); ignore(); } //... }; This is technically incorrect due to the fact that a conforming implementation will leave the `delim' char in the stream if it appears when *exactly* `lim' chars have already been read. A more complicated version could handle this by checking ``istream::gcount()''. The above also has the problem of when `lim' is reached by `get()' in that it eats the `lim+1' char in `ignore()'. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.7 Severity: The `placement new' operator is missing from <new.h> Workaround: Provided below Versions: <new.h> was introduced in 2.0 If you have a 1.xx compiler, use my <new.h> replacement below There is no `placement new' in <new.h>. This is one of the main reasons to include <new.h>, so it its omission is unfortunate. To fix it, add the following line to <new.h>: void _FAR* operator new(unsigned, void _FAR* place) { return place; } The `unsigned' should be `size_t' in a portable <new.h>, but Borland chose to use `unsigned' rather than to #include another header to get `size_t'. For pre-2.0 users, the rest of <new.h> is given in another bug-report below. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.7 Severity: Memory limitation error in the `README' Workaround: Get more memory :-) Versions: 2.0 The `README' says: > Pg. 158 In the first paragraph it says you need at least 576k of > extended memory. The correct amount is 320k. But an actual test of `bccx test.cpp' on my 1MB machine reveals: > Error: 384 KB of extended memory is available, but 450 KB is required. ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug report version 1.01.6 and earlier starts here ######### ######### ######### ######### ######### ######### ######### ######### ######### Severity: Missing header file Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 Fixed in: Fixed in 2.0 There is no <new.h>. I constructed the following work-alike. It should go into your standard include directory (where <iostream.h> 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. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: modified in 1.01.8 Severity: Serious bug in iostreams Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 Fixed in: 2.0 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 when seeking past end-of-file [Lippman p.402]. This can be fixed by changing "ios::operator void*()" in <iostream.h> 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; } UPDATE! The ANSI-C++ streams spec will insist that the ``while(cin>>chunk)'' idiom works. Borland C++ 2.0 gets it right. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.00.1 Severity: Serious bug in code generator Workaround: Provided below 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 <iostream.h> 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 Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01, and in BC++ 2.0 The 1.2 streams package (called <stream.h>) is nice, however AT&T treats the inclusion of <stream.h> as an alias to <iostream.h>. Therefore you should rename it from <stream.h> to <oldstream.h>, and let <stream.h> simply be: #include <iostream.h> It is notable that a number of posters on comp.lang.c++ have been confused by including <stream.h> thinking they were getting <iostream.h>... 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 <stream.h> 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: <This one may not be a bug at all; read the disclaimer> Workaround: Provided below Versions: ?? Disclaimer: I haven't reproduced this bug, and Borland wasn't able to either Thanks: Zerksis Umrigar/umrigar@bingvaxu.cc.binghamton.edu/SUNYBinghamtonNY 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 Workaround: None provided Versions: Occurs in TC++ 1.00; Borland says it's fixed in 1.01 Thanks: 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 <iostream.h> 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 Workaround: Provided below 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; } WORKAROUND #1: put an explicit ``if (this == &S)'' test at the head of the copy ctor (credit for this clean and effective solution goes to Stephen Bellantoni, sjb@cs.toronto.edu). Ex: String::String(const String& S) { if (this == &S) return; //Protect against TC++ 1.00 & 1.01 bug data = new char[S.len+1]; //If &S == this, changes S.data also!! strcpy(data, S.data); //Copy GARBAGE into itself len = S.len; } Naturally this won't work for those things that need an initialization list, but for things that have identical semantics for assignment as for copy-ctor, this should suffice. WORKAROUND #2: 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. WORKAROUND #3: 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. Ex: complex foo() { //... complex my_return_value = complex(re, im); return my_return_value; } Unfortunately workaround #3 won't be as fast since it requires an extra copy ctor and the temporary (``my_return_value'') has to be `destructed'. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Probably the same serious bug in code generator as above Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 Thanks: Jamshid Afshar <jamshid@emx.utexas.edu> The above explicit 2-parameter ctor in a by-value return statement is apparently part of a bigger pattern. The problem appears to be that a by-value return statement always calls the copy constructor to copy the returned object back into the caller's frame. However when the returned expression is already constructed in the caller's frame, this causes a `copy to self'. Basically then, whenever the returned object from a return-by-value function is constructed right inside the `return' statement, this will copy the object to itself. Fortunately this doesn't always cause problems. Ex: copying a complex number to itself will simply copy the real and the imaginary parts to themselves, which won't change a thing. However the String example above shows how it can be disastrous. The following demonstrates this more general pattern: #include <iostream.h> #include <stdlib.h> class X { int x; public: X() : x(1) { } X(const X& f); }; X::X(const X& f) : x(f.x) { if (this==&f) {cout << "Copying an object to itself!!" << endl; abort();} } X bar() { X x; return x; } // create temp local X, then copy it back to caller X foo() { return bar(); } // return a return value; copies to itself; BOOM! main() { X x = foo(); return 0; } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Serious problem with ifstreams Workaround: Provided below Versions: 1.00 and 1.01 Fixed in: 2.0 Thanks: Joseph Puglielli, Santa Barbara, CA Also: Michael Schwartz/U.Denver/New College/mschwartz@diana.cair.du.edu #include <fstream.h> class InFile { ifstream ifs; public: InFile(const char* filename); //... }; #if 1 InFile::InFile(const char* filename) : ifs(filename) { } //Works #else InFile::InFile(const char* filename) { ifs.open(filename); } //Fails //This fills the buffer with garbage! #endif The basic problem appears to be that fstreambuf::open(const char*) is unreliable. Use the constructor version instead. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: ?? Workaround: Provided below Versions: ?? Disclaimer: I haven't personally checked this -- MPC Thanks: 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 <dos.h> *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 <dos.h> // EXPLICITLY declares _stklen int a(void) { return 0; } // Implicitly causes _stklen to be declared unsigned _stklen = 20000; main() { a(); return 0; } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Limitation of the preprocessor Workaround: None provided Versions: ?? Disclaimer: I haven't personally checked this -- MPC Thanks: 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: Inconsistency w/ C language defn of unsigned'ness of short and int Workaround: None provided Versions: 1.00 and possibly 1.01 Disclaimer: I haven't personally checked this -- MPC Thanks: Leonid A. Broukhis <leo@s514.ipmce.su> Inst Prec Mech&Comp Equip, Moscow, USSR X/OPEN part 4, C Language definition, 2.5.5, 2.5.6.1, 2.5.6.6 defines the type conversion rules as follow: 2.5.5 Unsigned Whenever an unsigned integer and a plain integer are combined, the plain int is converted to unsigned and the result is unsigned. ... In a 2's complement representation, this conversion is conceptual; there is no actual change in the bit pattern. 2.5.6 Arithmetic Conversions 1. Any operands of type char or short are converted to int, and any operands of type unsigned char or unsigned short are converted to unsigned int. 6. [ Otherwise ], if either operand is unsigned, the other is converted to unsigned and that is the type of the result. Ex: The following two programs work differently: main() {int i=3; unsigned int u=0xffff; if (u<i) puts("!!");} //no output main() {int i=3; unsigned short u=0xffff; if (u<i) puts("!!");} //outputs `!!' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.8 Severity: Makefile produced by PRJ2MAK still needs some manual editing Workaround: Provided below Versions: 2.0 Thanks: Joe Dubner/HP/Spokane WA 99220/509-921-3514/dubner@hpspkla.hp.com [[NOTE THAT THIS IS A DIFFERENT BUG FROM THE VERSION 1.01 PRJ2MAK BUG BELOW]] The makefile that PRJ2MAK version 2.0 produces includes a spurious "/o" flag. This is an excerpt from the Makefile that PRJ2MAK produced from a .PRJ file: # *Explicit Rules* pn.exe: pn.cfg $(EXE_dependencies) $(TLINK) /v/x/c/d/o/P-/L\BC\LIB @&&| /o- c0h.obj+ Note that the "/o" option in the TLINK line is incorrect. "/o" means "overlay the following modules or libraries", but there is no module or library following the "/o"; there's just another cmd line option. TLINK fails with: Fatal: Unable to open file 'p- Pc$;Y]UJZ' (edited somewhat) The workaround is to edit the makefile produced by PRJ2MAK and delete the "/o" in the TLINK line. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: modified in 1.01.8 Severity: Makefile produced by PRJ2MAK may need some manual editing Workaround: Provided below Versions: 1.01 Fixed in: 2.0 Disclaimer: I haven't personally checked this -- MPC Thanks: Joe Dubner/HP/Spokane WA 99220/509-921-3514/dubner@hpspkla.hp.com [[NOTE THAT THIS IS A DIFFERENT BUG FROM THE VERSION 2.0 PRJ2MAK BUG ABOVE]] The makefile that PRJ2MAK version 1.01 produces doesn't include the overlay manager library in overlayed projects. TLINK then fails with: Warning: Overlays generated and no overlay manager included Similar errors appear for undefined symbols that are referenced in the code (ex: __OvrInitExt and __OvrInitEms). A suitable workaround appears to be to edit the makefile to include: \tc\lib\overlay.lib+ in the linker section, just prior to linking the fp87.lib or emu.lib library. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Uncommon bug in the parse of: delete [num] (ptr-expr) Workaround: Provided below Versions: 1.00 and possibly 1.01 Disclaimer: I haven't personally checked this -- MPC Thanks: Bradley Keister/Physics Dept/Carnegie Mellon U keister@poincare.phys.cmu.edu The following is ++ified code from Numerical Recipes: double* get_vector(int nl, int nh) { double* v; v = new double[nh-nl+1]; return v-nl; } void free_vector(double* v, int nl) { delete[4](v+nl); } ^^^^^^^^^^^^^^^ TC++ gives error: "Too few params in call to 'delete(void far*, unsigned int)'" Replacing it with "delete v+nl;" gives "Value of type void is not allowed..." Replacing it with "delete v;" will cause TC++ to not complain, though `v' must then be bumped by hand with an extra step, and then it will generate incorrect code if `p' points to an object whose class has a destructor (``delete[]p'' and ``delete p'' are different --- see ARM p.64-65). The workaround is: void free_vector(double* v, int nl) { v += nl; delete [4] v; } Though naturally the 4 (----^^^----) will have to be replaced with something more meaningful. When TC++ becomes compliant with C++ 2.1, we can just say: void free_vector(double* v, int nl) { v += nl; delete [] v; } Hopefully by then the bug will be fixed, and we'll be able to simply say: void free_vector(double* v, int nl) { delete [] (v+nl); } PS: the original example compiles under cfront. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: The effects of this bug will show up randomly :-) Workaround: Repair stdlib.h as indicated below Versions: 1.00 Fixed in: 1.01 Thanks: Jim Seymour [qintar@agora.UUCP, qintar@agora.rain.com] Cipher Systems Inc, PO Box 329, North Plains, OR 97133 Also: George Theall/SSWS14@penndrls.upenn.edu/SSWS14@penndrls.bitnet Social Sci Computing/Univ Penn/Philadelphia, PA 19104/215-898-3419 Also: Per Westermark/Dept of inf.proc./University of Umea, Sweden mapwk@cs.umu.se `random(int)' is an inline function [C++] and a macro [C] that produces a random num from 0 to N-1. It's defined in <stdlib.h> as: #ifdef __cplusplus inline int _Cdecl random(int __num) { return (int)(((long)rand()*__num)/RAND_MAX); } #else #define random(num) (int)(((long)rand()*(num))/RAND_MAX) #endif This definition is wrong, as `random(n)' occasionally returns large numbers. The solution is to replace ``RAND_MAX'' by ``((unsigned)RANDMAX + 1)''. Ie: replace the above definitions with (hold on to your hat!): #ifdef __cplusplus inline int _Cdecl random(int __num) { return (int)((long)rand()*__num)/(unsigned(RAND_MAX)+1); } #else #define random(num) (int)(((long)rand()*(num))/((unsigned)RAND_MAX+1)) #endif Note that I used ``unsigned(RAND_MAX)'' in the C++ version (just for fun :-). ######### ######### ######### ######### ######### ######### ######### ######### ######### Missed features and other non-serious bugs start here ######### ######### These include (even serious) bugs in the IDE environment ######### ######### ######### ######### ######### ######### ######### ######### ######### Bug-report: 1.01.9 Severity: Mouse related bug will crash the IDE Workaround: None tested Versions: Borland C++, version 2.00 of BCX Thanks: Alan Howlett/74 The Chantrys/Farnham/Surrey/England/GU9 7AL Also: Kelvin Hill/kelvin@thed.uk22.bull.com/kelvin@kelvin.uk22.bull.com Fault: Load a text file into the IDE, then select Windows/Resize to alter the window size. Select the base of the text file window with the mouse, and the IDE crashes (if doesn't crash immediately, repeat, perhaps using another .c or .cpp file). Error needs a re-boot to restart the system. Error displays: Unexpected interrupt/exception 0D at 1C27:104E. GP at 0020 4FCA EC 0140 GP at 0020 59A0 EC 05C4 System: Cannot recover from exception System: Kernel cannot continue. Cannot load the file C:\COMMAND.COM No memory System details: 12 MHz 80286 AT, 5MB RAM, DRDOS 5.0 Genius Mouse Driver, Version 8.02, Kye Corp. Borland C++, licence no. TA142D102D10216643. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.9 Severity: BC and BCX fail to load. Workaround: Use a different mouse driver. Versions: Borland C++, version 2.00 of BCX. Thanks: Alan Howlett/74 The Chantrys/Farnham/Surrey/England/GU9 7AL Also: Kelvin Hill/kelvin@thed.uk22.bull.com/kelvin@kelvin.uk22.bull.com Use of Z-Nix mouse driver v5.00 crashes BC and BCX during loading. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bug-report: 1.01.9 Severity: Kills mouse. Workaround: None tested. Versions: Borland C++, version 2.00 of BCX. Thanks: Alan Howlett/74 The Chantrys/Farnham/Surrey/England/GU9 7AL Also: Kelvin Hill/kelvin@thed.uk22.bull.com/kelvin@kelvin.uk22.bull.com Fault: Shelling to DOS, either via the menu's or as part of a Windows compilat- ion causes the mouse to go dead. Gmouse.com recognises the existance of the previous version after a mouse crash, so it won't load again. Gtest.com can then restore the mouse! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Serious but easy-to-fix incompatibility in rarely used header file Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 Thanks: Pete Humphrey / pete@edsr.eds.com / 505-345-1863 EDS Research / 5951 Jefferson Street NE / Albuquerque NM 87109-3432 <generic.h>: 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 <generic.h>: #define name2 _Paste2 #define name3 _Paste3 #define name4 _Paste4 Borland says: TRUE. As to why . . . copyright issues. My response: I hope that doesn't mean every compiler will be forced to use its own (unique and non-portable) naming scheme! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Not serious Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 Several classlib\include\*.h have #include "foo.h" rather than <foo.h>. 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"; <file.h> 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 Workaround: Provided below Versions: 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: Bug in support tool Workaround: Put a copy of builtins.mak in your current working directory 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 Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 <iostream.h> always includes <mem.h> which slows compilation some. In fact <iostream.h> doesn't need `NULL', and only needs memcpy() if _BIG_INLINE_ is defined, which will probably be rare. Therefore the line #include <mem.h> // to get memcpy and NULL can be replaced with: #ifdef _BIG_INLINE_ #include <mem.h> // to get memcpy #endif Since nearly everything includes <iostream.h>, and since relatively few things will want _BIG_INLINE_, this should be a winner. Note however that some code might assume <iostream.h> pulls in <mem.h>. 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 Workaround: Provided below Versions: Occurs in both TC++ 1.00 and 1.01 <iomanip.h> needs <iostream.h> but doesn't get it. Add this to <iomanip.h>: #ifndef __IOSTREAM_H #include <iostream.h> #endif - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Non-serious missed feature in integrated environment Workaround: None provided Versions: ?? Disclaimer: I haven't personally checked this -- MPC Thanks: 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 Workaround: Non-detailed workaround provided below Versions: Occurs in both TC++ 1.00 and 1.01 Disclaimer: I haven't personally checked this -- MPC Thanks: Mark (M.S.) Lord <MLORD@bnr.ca.bitnet>: 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: Missed feature Workaround: None Versions: Occurs in both TC++ 1.00 and 1.01 TCCNVT.EXE (the configuration file converter) isn't in the distribution. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Severity: Non-serious missed feature Workaround: None provided 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: Non-serious missed optimization Workaround: None provided Versions: ?? Disclaimer: I haven't personally checked this -- MPC Thanks: 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 <stdio.h> 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"); } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From jamshid@emx.utexas.edu Fri May 3 16:08:41 1991 Return-Path: <@sun.soe.clarkson.edu:jamshid@emx.utexas.edu> Date: Thu, 2 May 91 17:25:36 -0500 From: Jamshid Afshar <jamshid@emx.utexas.edu> Posted-Date: Thu, 2 May 91 17:25:36 -0500 To: cline@sun.soe.clarkson.edu Subject: Window's OpenFile bug Article 2169 of comp.windows.ms.programmer: Path: ut-emx!cs.utexas.edu!sdd.hp.com!elroy.jpl.nasa.gov!decwrl!borland.com!sidney From: sidney@borland.com (Sidney Markowitz) Newsgroups: comp.windows.ms.programmer Subject: using OpenFile in BC++ (was Re: MS Windows Classes) Message-ID: <1991Apr30.115545.5197@borland.com> Date: 30 Apr 91 11:55:45 GMT References: <1991Apr23> <149000023@primerd> <1991Apr30.025244.5815@mnemosyne.cs.du.edu> Organization: Borland International Lines: 26 ebergman@isis.UUCP (Eric Bergman-Terrell) writes: >What problem did you have calling OpenFile from BC++ 2.0? Care to post a >code fragment? Did you call it in? What did they say? [...] >BTW I just checked my Windows application - I'm using OpenFile to make >sure a file exists (but not to actually open it). Seems to work OK In BC++, the handle that is returned by OpenFile() or _lopen() can be passed to _lread(), _lwrite() and the other SDK file I/O functions, but not to read(), write(), and similar RTL file handle I/O functions. The handle that is returned by open() can be used by read(), write(), etc., but not by _lread(), _lwrite(), etc. The problem is that both OpenFile() and open() use some auxillary data structures in parallel with the DOS file handles, and OpenFile() is compatible with the Windows SDK and MSC RTL, while open() is backwards compatible with the TC++ RTL. Call it a bug or a compatibility issue (it's at least a bug in the documentation that mention of it was left out), but in any case the workaround is to use only SDK functions or only RTL functions for handle file I/O. The low level RTL routines _read(), _write(), etc., appear to work with both types of handles. -- sidney markowitz <sidney@borland.com> Marshall Cline -- 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 ..