The OPT benchmark program is shown in Listing One. Each compiler was directed to produce an assembly language listing of its output, based on a compile using the optimized compiler switches just shown. Listing Two shows the output from Watcom C 7.0; Listing Three shows the output from Microsoft C 5.1; Listing Four shows the output from Microsoft C 6.0 without enregistered parameters; Listing Five shows the output from Microsoft C 6.0 with enregistered parameters. These listings should give you a feel for the quality of code generation supported by the test compilers.
Listing One
*********************** *** Microsoft C 6.0 *** *********************** #include "stdio.h" /* prototypes */ void doit(int i); void (* func_ptr)(int i) = doit; void doit(int i) { --> push bp --> mov bp,sp --> push di --> push si --> mov di,WORD PTR [bp+4] int loop; for (; i > 0; --i) --> or di,di --> jle $EX225 { for (loop = 0; loop < 26; ++loop) --> $F227: --> sub si,si --> mov WORD PTR [bp+4],di { printf("loop character = %c\n", 0x41 + loop); --> $F230: --> lea ax,WORD PTR [si+65] --> push ax --> mov ax,OFFSET DGROUP:$SG233 --> push ax --> call _printf --> add sp,4 --> inc si --> cmp si,26 --> jl $F230 } printf("i / 16 = %d\n\n",i / 16); --> mov ax,di --> cwd --> xor ax,dx --> sub ax,dx --> mov cx,4 --> sar ax,cl --> xor ax,dx --> sub ax,dx --> push ax --> mov ax,OFFSET DGROUP:$SG234 --> push ax --> call _printf --> add sp,4 --> dec di --> jne $F227 } --> $EX225: --> pop si --> pop di --> mov sp,bp --> pop bp --> ret --> nop } int main(void) { func_ptr(100); --> mov ax,100 --> push ax --> call WORD PTR _func_ptr --> add sp,2 return 0; --> sub ax,ax --> ret }
Listing Two
***************************************** *** Microsoft C 6.0 (using _fastcall) *** ***************************************** #include "stdio.h" /* prototypes */ void doit(int i); void (* func_ptr)(int i) = doit; void doit(int i) { --> push bp --> mov bp,sp --> sub sp,2 --> push ax --> push si int loop; for (; i > 0; --i) --> or ax,ax --> jle $EX225 { for (loop = 0; loop < 26; ++loop) --> $F227: --> sub si,si { printf("loop character = %c\n", 0x41 + loop); --> $F230: --> lea ax,WORD PTR [si+65] --> push ax --> mov ax,OFFSET DGROUP:$SG233 --> push ax --> call _printf --> add sp,4 --> inc si --> cmp si,26 --> jl $F230 } printf("i / 16 = %d\n\n",i / 16); --> mov ax,WORD PTR [bp-4] --> cwd --> xor ax,dx --> sub ax,dx --> mov cx,4 --> sar ax,cl --> xor ax,dx --> sub ax,dx --> push ax --> mov ax,OFFSET DGROUP:$SG234 --> push ax --> call _printf --> add sp,4 --> dec WORD PTR [bp-4] --> jne $F227 } --> $EX225: --> pop si --> mov sp,bp --> pop bp --> ret } int main(void) { func_ptr(100); --> mov ax,100 --> call WORD PTR _func_ptr return 0; --> sub ax,ax --> ret }
Listing Three
************************ *** Microsoft C 5.10 *** ************************ #include "stdio.h" /* prototypes */ void doit(int i); void (* func_ptr)(int i) = doit; void doit(int i) { --> push bp --> mov bp,sp --> sub sp,2 --> push di --> push si int loop; for (; i > 0; --i) --> cmp WORD PTR [bp+4],0 --> jle $FB202 --> mov di,WORD PTR [bp+4] { for (loop = 0; loop < 26; ++loop) --> $L20002: --> sub si,si { printf("loop character = %c\n", 0x41 + loop); --> $L20000: --> lea ax,WORD PTR [si+65] --> push ax --> mov ax,OFFSET DGROUP:$SG206 --> push ax --> call _printf --> add sp,4 } --> inc si --> cmp si,26 --> jl $L20000 --> mov WORD PTR [bp-2],si ;loop printf("i / 16 = %d\n\n",i / 16); --> mov ax,di --> cwd --> xor ax,dx --> sub ax,dx --> mov cx,4 --> sar ax,cl --> xor ax,dx --> sub ax,dx --> push ax --> mov ax,OFFSET DGROUP:$SG207 --> push ax --> call _printf --> add sp,4 } --> dec di --> jne $L20002 --> mov WORD PTR [bp+4],di --> $FB202: --> pop si --> pop di --> mov sp,bp --> pop bp --> ret --> nop } int main(void) { func_ptr(100); --> mov ax,100 --> push ax --> call WORD PTR _func_ptr --> add sp,2 return 0; --> sub ax,ax --> ret }
Listing Four
********************
*** Watcom C 7.0 ***
********************
#include "stdio.h"
/* prototypes */
void doit(int i);
void (* func_ptr)(int i) = doit;
void doit(int i)
{
int loop;
--> push bx
--> push cx
--> push dx
--> mov cx,ax
--> jmp short L3
for (; i > 0; --i)
{
for (loop = 0; loop < 26; ++loop)
{
--> L1:
--> mov bx,0041H
printf("loop character = %c\n", 0x41 + loop);
--> L2:
--> push bx
--> mov ax,offset DGROUP:L4
--> push ax
--> call near ptr printf_
--> add sp,0004H
--> inc bx
--> cmp bx,005bH
--> jne L2
}
printf("i / 16 = %d\n\n",i / 16);
--> mov bx,0010H
--> mov ax,cx
--> cwd
--> idiv bx
--> push ax
--> mov ax,offset DGROUP:L5
--> push ax
--> call near ptr printf_
--> add sp,0004H
--> dec cx
}
--> L3:
--> test cx,cx
--> jg L1
--> pop dx
--> pop cx
--> pop bx
--> ret
}
int main(void)
{
func_ptr(100);
--> mov ax,0064H
--> call word ptr _func_ptr
return 0;
--> xor ax,ax
--> ret
}
Listing Five
/* Skeleton Program demonstrating the use of based pointers */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#define MAX_TAG 2000
unsigned long get_size(void);
_segment segvar; /* name a segment for use with based pointers */
/* set up structures and tags within segment segvar */
typedef mytag {
char filename[14];
unsigned long size;
mytag _based(segvar) *next;
} _based(segvar) *PTAG, TAG;
main() {
PTAG head, curptr;
/* Allocate a based heap of MAX_TAG structs. Put segment address in segvar. */
if((segvar = _bheapseg(sizeof(TAG) * MAX_TAG))) == NULLSEG){
printf("error allocating based heap \n");
exit(-1);
}
/* Allocate memory within segvar for first structure in linked list */
if((head = _bmalloc(segvar, sizeof(TAG)) == _NULLOFF) {
printf("error allocating TAG \n");
exit(-1);
}
head->size = get_size();
_fstrcpy((char far *) head->filename, get_name()); /* get a
filename and copy it to segvar */
if((head->next = _bmalloc(segvar, sizeof(TAG)) == _NULLOFF) {
printf("error allocating TAG \n");
exit(-1);
}
.
.
.
}
unsigned long get_size(void) {
return 1;
}
char *get_name(void) {
return("foo");
}
At the Workbench
Usually, I don't care much for integrated programming environments -- the ones I've worked with have limited capabilities locked into a simplistic editor. Working from the DOS prompt has always been faster and more powerful. That is, until now.
The Programmer's Workbench is actually an enhanced version of the Microsoft Editor. The Microsoft Editor was easily as powerful as other professional program editors. It allowed separately compiled "add-ons" to be linked with it at run time. The Programmer's Workbench takes the idea of add-ons a step further by using them to add support for compilers, help systems, and utilities to its menus and environments.
The Programmer's Workbench editor is powerful and fast, incorporating all of the features programmers expect from a professional editor -- recordable and compilable macros, powerful block operations, multiple file editing, and mouse support. It also allows you to give editor commands using menus or function keys. I haven't felt restricted by the Programmer's Workbench editor, and that's something I cannot say for other integrated editors.
You can do all of your program development work from within the Programmer's Workbench. The environment integrates the compiler, make utility, linker, help system, and CodeView relatively seamlessly. It's easy to "live" within the Programmer's WorkBench, leaving it only when you're ready to finish up for the day.
Programmer's WorkBench includes menus that allow you to set options for compiles. Options can be set for C, Microsoft Macro Assembler (MASM), and the linker. Every option available from the command line can be selected via the menus. Two sets of program construction options can be set, for debug and production compiles. Combined with a list of files that are part of the same program, the options are used to construct a make file for a project. When you build a project, the project's make file is then passed as a parameter to nmake. Compilation results appear in a window, and errors can be tracked from the compiler's output directly into your source code. It's a slick, powerful, and uninhibited environment for constructing software. Microsoft has told me that the interface to the Programmer's WorkBench will be fully and publicly documented so that third parties can integrate their products into WorkBench. With luck, your favorite editor or make utility will become a part of the environment.
Programmers WorkBench has one drawback that may limit its acceptance by many programmers: It does not run efficiently on anything less than a 386-or fast 286-based PC. The environment is simply too slow to be useful when run on a 10-MHz or slower 286-based computer, according to my correspondents.
Other problems in the Programmer's WorkBench have surfaced. It requires nearly 3 Mbytes of disk space, room many developers need for other software and data. If you don't install WorkBench and its help files, you no longer have access to the primary source of detailed documentation for MSC6. For these reasons many developers may opt to leave Programmer's WorkBench off their system.
Conclusion
Microsoft has made a valiant effort at producing the definitive C compiler for MS-DOS and OS/2. For developers who have high-end PCs with available disk space, Microsoft C 6.0 is a solid professional product that offers some advantages over its competitors. Improvements in code optimization, compiler speed, and ANSI compatibility are major pluses.
Unfortunately, Microsoft missed the mark in some areas. CodeView 3.0 is not what programmers were expecting and the Programmer's WorkBench is a professional environment that is too bulky and too slow for many developers. Finally, the lack of complete paper documentation is simply inexcusable in a product priced at $450. Microsoft still has some work to do before they can accurately claim to have the best C compiler on the market.
Scott is a full-time freelance computer journalist. You can reach him at 705 West Virginia, Gunnison, CO 81230.
Based Pointers for Optimization
Bruce D. Schatzman
Microsoft C 6.0 provides an important new tool in the battle to keep code small and fast -- the based pointer. Virtually all Microsoft C programmers are familiar with near and far pointers, and how they are used within standard or mixed memory models. The 2-byte near pointers provide both size and speed advantages over the larger and slower 4-byte far pointers. As such, programmers tend to use near pointers within small memory models whenever possible.
Even the most carefully designed small model programs, however, sometimes hit the memory wall of DGROUP's 64K allocation. Programmers are then faced with a tough decision: Find a way to keep the program within the confines of a Small memory model to preserve the speed advantage, or admit defeat and move to larger and less efficient models such as compact or large. The inefficiency of larger models is primarily the result of the use of 4-byte (far) pointers. Each time a far data item is referenced, both the segment and offset address must be loaded into one of the 80x86's segment registers. This double load consumes more CPU cycles than near pointers (which load only an offset address), and increases both code and data sizes.
Microsoft C 6.0 provides a solution to this problem through the use of based pointers. This new data type gives you the reach of far pointers while retaining the size and speed advantages of near pointers. You can now increase your program's data space without necessarily moving to a larger memory model or a mixed (near/far) model.
Listing Five presents a small skeleton program that sets up a linked list of file names using a set of structures (TAG), with a maximum number of MAX_TAG structures. Two new keywords are used: _segment and _based. The statement _segment segvar; declares a variable that will hold a segment's memory address. segvar will become a segment in which a set of data is based -- thus the name "based pointers."
This basing is illustrated in the statement _based(segvar) *PTAG, TAG;, which defines a 2-byte structure pointer and a structure that are both based within the segment segvar.
The compiler generates code that automatically looks at the segvar variable each time a reference is made to *PTAG and TAG, necessitating only the use of a 2-byte offset address. Technically, *PTAG and TAG are both in a far heap, yet they act as if they reside in a near heap.
If MAX_TAG were smaller, this program could fit comfortably within a small memory model. However, with MAX_TAG - 2000, the tag list itself could fill up a 64-Kbyte data segment. Basing our structures within segvar means that as the program runs, successive references to these structures involve loading only the 2-byte segment offset. Because the structures are all within one segment, the segment address is preloaded into one of the segment registers and does not need to be reloaded every time a new segvar structure is referenced.
However, referencing another named segment (or far pointer) will requires loading a new value within a segment register. Therefore, based pointers are most effective for use with successive references to data items within the same segment, and offer performance equivalent to that of near pointers.
Note the program's use of the new C 6.0 library routines _bheapseg and _bmalloc, which allocate a based-heap segment and memory within that segment.
Although I've focused here or keeping small programs small based pointers can also be used to reduce the size (and increase the speed) of large programs. This is accomplished by converting some or all of a large program's far pointers to based pointers.
Based pointers are an important tool, but they have their limitations. Perhaps the most significant is the fact that their actual addressing capability is ?6 bits -- the size of a single segment. They do not have the unlimited 32-bit scope of far pointers, and thus cannot be used for very large data structures, such as arrays that exceed 64K, far pointers are the only real choice in these circumstances. Nevertheless, based pointers are a tool that will undoubtedly be used by a large percentage of Microsoft C programmers to optimize code for smaller size and greater speed.
Bruce Schatzman has worked in the computer industry for over 10 years, holding a variety of technical and marketing positions at corporations including General Dynamics, Tektronix, and Xerox. He is currently an independent consultant in Bellevue, Wash., specializing in systems consulting and technical communications.