[comp.lang.c++] Borland Turbo C++ performance issue and tutorial errors

toma@tekgvs.LABS.TEK.COM (Tom Almy) (08/03/90)

I submitted this to the msdos.programmer group last week, but after looking
at the C++ group (to which I don't normally subscribe) I decided to post here
as well.

I have found several bugs in the Turbo C++ tutorial that make me believe
this was really a rush job (or they don't know how to write C++ programs.)
But there are several performance issues of (Turbo) C++ that should be 
mentioned.

It has already been pointed out that Turbo C++ is slower compiling a C program
that Turbo C V2.0. But the problem gets worse with C++ programs (the larger
header files are mostly to blame). Executables are bigger as well. For 
instance, compare hello.c with hello.cpp:

			C	C++
Compile time: 		2 sec	3.8 sec
Executable size:	6074	17852

(The c program does printf("Hello world"), while the c++ program does
 cout << "Hello world".


The calc program example from the Stroustrup book (with addition of trig
functions) compiles to a 35440 byte file, while the same program rewritten
in JPI Modula-2 (just to test it out) is 11510. (The source file in C++ is
60% the size, though!).

============================

But to the meat of the bugs. I've always been concerned about dynamic storage
management problems in C++, which the destructor functions are suppossed to
circumvent. I have felt the entire scheme was too problematic, and I still
think so because the Borland tutorial writer can't get it correct!

In program listdemo.cpp, the quantity of free memory is reported before
and after running the bulk of the program (which demonstrates dynamic lists
and constructor/descructor operation). But the numbers aren't the same!
The problem is in the constructor function for Lists, which creates the
list head. A list object consists of just one item, a pointer to a node.
But the definition of the constructor is:

List::List () {
   Node *N;
   N = new Node;
   N->Item = NULL;
   N->Next = NULL;
   Nodes = NULL;             // sets node pointer to "empty"
                             // because nothing in list yet
}

Note that the constructor generates a Node, which it initializes and then
ignores. Since N is a Node* rather than a Node, the Node doesn't get
deleted. But the whole thing is stupid. This function works correctly and
doesn't leave the garbage around:

List::List () {
   Nodes = NULL;             // sets node pointer to "empty"
                             // because nothing in list yet
}

and it is in better agreement with the comment!

=======================

The second problem, actually a pair of problems, occurs in xstring.cpp.

The String class has two elements, a pointer to char, char_ptr, and
an integer, length. A descructor is defined to delete the character
string (which is fine).

The program overloads the + operator to do a string concatenation in
the line:

CString = AString + BString;

The example in the book (which is corrected on the disk) modifies
This to hold the concatenated string. Of course this doesn't work
because This is AString, and thus AString gets modified as a side
effect.

But the example does have an unfortunate side effect, it wastes the
creation of a String.  The function:

String String::operator+ (String& Arg)
{
  String Temp( length + Arg.length );
  strcpy(Temp.char_ptr, char_ptr);
  strcat(Temp.char_ptr, Arg.char_ptr);
  return Temp;
}


Since the scope of Temp is the function, the return Temp command uses
the constructor to build a copy of Temp (which gets returned) and
then the String destructor is called to delete Temp. I guess this is
correct operation, but it surely is inefficient, and I can't figure
out a way around it.

The biggest problems is the lack of a String class overload of the =
operator.  The standard C++ (and C) assignment operation occurs,
copying the temporary into CString, element by element. Unfortunately
this wipes out the string that was already assigned to AString, and
it can never be reclaimed.  The fix is to write:

String &String::operator= (String& Arg)
{
	delete char_ptr;
	length = Arg.length;
	char_ptr = new char [length + 1];
	strcpy(char_ptr,Arg.char_ptr);
	return *this;
}

perhaps there is a cleaner way of doing this.


Tom Almy
toma@tekgvs.labs.tek.com
Standard Disclaimers Apply

steve@taumet.com (Stephen Clamage) (08/05/90)

toma@tekgvs.LABS.TEK.COM (Tom Almy) writes:

|It has already been pointed out that Turbo C++ is slower compiling a C program
|that Turbo C V2.0. But the problem gets worse with C++ programs (the larger
|header files are mostly to blame). Executables are bigger as well. For 
|instance, compare hello.c with hello.cpp:
|			C	C++
|Compile time: 		2 sec	3.8 sec
|Executable size:	6074	17852
|(The c program does printf("Hello world"), while the c++ program does
| cout << "Hello world".

You have to be very careful in such comparisons.  Any C++ program
using streams includes the entire stream i/o package, which is very
large.  It has much more functionality than does printf(), so it is
bigger.  You could also compare a C program which used
	write(1, "Hello, world", 12);
in place of printf() and get a smaller program -- because write has
much less functionality than printf().

Besides, you can use printf() instead of streams in C++ if you wish.

|The calc program example from the Stroustrup book (with addition of trig
|functions) compiles to a 35440 byte file, while the same program rewritten
|in JPI Modula-2 (just to test it out) is 11510. (The source file in C++ is
|60% the size, though!).

There can be no validity to a claim that two programs written in two
different programming languages are the "same".  It is a well-known
theorem in the theory of computation that one cannot *in general*
determine whether two programs have the same behavior.

Apart from that, you are looking at
a) Different functionality of C++ stream i/o vs Modula-2 i/o.
b) Differences in implementation of floating-point operations. (Are
you linking in comparable FP libraries?  Does one of the programs
include both 80x87 and software FP?)
c) Differences in quality of code generation.

IMHO, comparing executable file size for toy programs is an entirely
irrelevant exercise.  You are really comparing runtime library size,
which completely swamps anything in the toy program.  In the above
case, we can see that *without floating-point*, the basic Turbo C++
runtime library for any program using stream i/o is 17K, assuming
debugging was in fact disabled.  If you really just want to say
hello, use write() instead of using stream i/o.  If you are writing
a clone of 1-2-3 or WordPerfect, the 40% reduction in source file size
seems far more relevant than a few percent difference in executable file
size.
-- 

Steve Clamage, TauMetric Corp, steve@taumet.com

diamond@tkou02.enet.dec.com (diamond@tkovoa) (08/06/90)

In article <380@taumet.com> steve@taumet.com (Stephen Clamage) writes:

>There can be no validity to a claim that two programs written in two
>different programming languages are the "same".  It is a well-known
>theorem in the theory of computation that one cannot *in general*
>determine whether two programs have the same behavior.

False and true, respectively.  No machine (or no program, if you prefer)
can correctly check all possible pairs of programs.  Every machine will
fail (loop forever) for at least some cases.

However, some machines can check SOME programs.  Some humans can too.
I am sure that you can verify, and you can write a program to verify,
that the following two programs (executed by processors conforming to
their respective standards; and using the ASCII standard character set
or, if you prefer, similar ISO version) are the same:

int main(int argc, char **argv) {      PROGRAM junk(OUTPUT);
  printf("Hello, world.\n");           BEGIN
  return 0;                              WRITELN('Hello, world.');
}                                      END.

(In fact I thought of giving an example with null programs, but didn't
because it might mislead as to the amount of generality that this
case has.)
-- 
Norman Diamond, Nihon DEC     diamond@tkou02.enet.dec.com
This is me speaking.  If you want to hear the company speak, you need DECtalk.

steve@taumet.com (Stephen Clamage) (08/06/90)

diamond@tkou02.enet.dec.com (diamond@tkovoa) writes:

|In article <380@taumet.com> steve@taumet.com (Stephen Clamage) writes:
|>There can be no validity to a claim that two programs written in two
|>different programming languages are the "same"...

|However, some machines can check SOME programs...
|I am sure that you can verify, and you can write a program to verify,
|that the following two programs (executed by processors conforming to
|their respective standards; and using the ASCII standard character set
|or, if you prefer, similar ISO version) are the same:

|int main(int argc, char **argv) {      PROGRAM junk(OUTPUT);
|  printf("Hello, world.\n");           BEGIN
|  return 0;                              WRITELN('Hello, world.');
|}                                      END.

The C program returns the zero status to whatever program invoked it.
There is no such guarantee for the Pascal program.  So these trivial
toy programs might NOT be the "same" in their external characteristics,
let alone what goes on internally!  The orginal posting talked
about a calculator program in C and in Modula.  I believe that Modula
follows Pascal-like rules about arithmetic exceptions, whereas there
is no guarantee that any C system (ANSI included) will complain
about, say, overflow or divide by zero.

As to the null program, there might STILL be external differences due to
what each language specifies about what must happen in its environment at
startup and shutdown.
-- 

Steve Clamage, TauMetric Corp, steve@taumet.com

sakkinen@tukki.jyu.fi (Markku Sakkinen) (08/07/90)

Note2: Third trial to post!
Note1: This is the second posting of an article that apparently
disappeared while the news software was out of order here
for three days. Sorry if somebody receives it twice.

In article <380@taumet.com> steve@taumet.com (Stephen Clamage) writes:
>toma@tekgvs.LABS.TEK.COM (Tom Almy) writes:
>
>|It has already been pointed out that Turbo C++ is slower compiling a C program
>|that Turbo C V2.0. But the problem gets worse with C++ programs [...]
>| ...
>You have to be very careful in such comparisons.  Any C++ program
>using streams includes the entire stream i/o package, which is very
>large.  It has much more functionality than does printf(), so it is
>bigger. [...]
> ...

Right! Often in the newsgroups people claim that Ada code is much larger
and slower and C code, when they get in the Ada programmes a lot of
run-time support unheard of in the C world, e.g. array bounds checking.

>|The calc program example from the Stroustrup book (with addition of trig
>|functions) compiles to a 35440 byte file, while the same program rewritten
>|in JPI Modula-2 (just to test it out) is 11510. (The source file in C++ is
>|60% the size, though!).
>
>There can be no validity to a claim that two programs written in two
>different programming languages are the "same".  It is a well-known
>theorem in the theory of computation that one cannot *in general*
>determine whether two programs have the same behavior.

This is a dangerous fallacy. Although the *general* problem is
undecidable, it is in many particular cases possible to prove
that two programmes are (or are not) equivalent.
Of course, the problem is much more difficult when the programming
languages are different; especially if one of the languages has
as irregular and fuzzily defined semantics as C and its derivatives.
(Without seeing the source code, we cannot guess how much it was
"the same program" in the quoted case, of course.)

> ...
>IMHO, comparing executable file size for toy programs is an entirely
>irrelevant exercise. [...]
> ...

Here I agree wholeheartedly!

Markku Sakkinen
Department of Computer Science
University of Jyvaskyla (a's with umlauts)
Seminaarinkatu 15
SF-40100 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)