Optimizing With Microsoft C 6.0

Based pointers and global optimization highlight features of this new incarnation.


August 01, 1990
URL:http://www.drdobbs.com/windows/optimizing-with-microsoft-c-60/184408398

For two years, Microsoft C 5.1 has been the benchmark by which other C compilers are judged. Vendors of competing compilers always compare themselves against Microsoft's product. Some competitors have edged ahead of Microsoft C 5.1 -- Watcom C 7.0 produces faster programs; Borland's Turbo C and others are faster at producing programs. While Microsoft C 5.1 has remained at the top of the heap, it is no longer the leader in many categories.

The upgrade has finally arrived in the form of Microsoft C 6.0. Microsoft's answer to the competition includes a new programming environment, an improved code optimizer, a new version of the venerable CodeView debugger, and some new library enhancements. Pulling out all the stops, Microsoft has made clear its intent to keep its C compiler at the forefront of the industry.

An Overview

Perhaps the biggest surprise for many is that Microsoft C 6.0 (MSC6) does not include any C++ extensions. With most other vendors jumping on the C++ bandwagon, it might seem odd that Microsoft has not followed the same path. Some industry watchers have opined that Microsoft has made a mistake by leaving out C++. I don't agree -- C++ is a language that is still maturing. Most C programmers are sticking with C; only a few have embraced C++ as their primary programming language. For now, C is the dominant programming language for professional developers of PC applications, and Microsoft is addressing this market.

You must use Microsoft's Setup program to install MSC6. The files on the distribution disks are compressed, and they can be expanded only by using the Setup program. Setup is a very good installation program; it gives you the choice of installing different sets of libraries and tools based on your needs. Additional libraries can be built later without having to reinstall the entire package.

MSC6 is disk-hungry; installing the MS-DOS version of the compiler, the programming environment, two memory models (small and large) each for the floating-point emulator and the coprocessor, and other tools used over 7 Mbytes of space on my hard drive. If you install all four memory models for all three floating-point options along with the OS/2 version of the compiler, you can easily use more than 12 Mbytes of disk space. Obviously, it is not possible to use MSC6 on a floppy disk-based computer!

One last change in the basic package may take some people by surprise: It is now distributed on 1.2-Mbyte 5 1/4-inch disks and 720K 3 1/2-inch disks. If you want MSC6 on 360K disks, you'll have to special order them after you've bought the package. Microsoft is offering MSC6 on CD-ROM, preinstalled and ready to run.

Documentation?

No, the question mark is not a typo. Looking at MSC6 you may wonder if Microsoft forgot to pack the documentation. Microsoft has minimized the paper documentation in their language products in recent years. Instead of providing thick books, Microsoft puts the majority of the documentation into on-line databases. MSC6 comes with only 940 pages of documentation, less than half the length of the manuals provided with Microsoft C 5.1 (MSC5.1). Going from the fat three-ring binders of MSC5.1 to the thin paperback manuals of MSC6 gives the impression that Microsoft has left something out.

The documentation isn't missing; it's just not where you'd expect it to be. The on-line help system is where the real documentation for MSC6 lies. There are nearly 2 Mbytes of help files covering every aspect of MSC6 in great detail. These help files are available either through the QuickHelp program, or from within the Programmer's Workbench environment.

In MSC5.1, the command-line compiler had a /HELP switch that displayed a simple list of compiler switches. The same switch in MSC6 is now available for virtually every program in the package. Instead of a simple command list, though, /HELP will invoke the interactive menu-driven QuickHelp system. This provides complete information on the program in question. Entering the command lib /HELP will bring up detailed information on the library manager utility. The scope of the information runs from an explanation of lib to complete coverage of lib switches and commands (with examples).

The lion's share of the help information is available only when you are working within the Programmer's Workbench. If you're working with OS/2, the QuickHelp utility can be used as a keyboard monitor, so that it can be popped up at any time for use. Unfortunately, MS-DOS users do not have the luxury of a TSR version of QuickHelp.

Considering the amount of on-line documentation, I was pleased to find that the seemingly small amount of paper documentation was actually very useful. There are two paperback manuals in the MSC6 box: the spiral-bound 380-page Microsoft C Reference and the 480-page Advanced Programming Techniques. The amazingly long title Installing and Using the Professional Development System belongs to an 80-page booklet that explains the installation process and the fundamentals of using the Programmer's Workbench environment.

More Details

The Microsoft C Reference begins with a section describing each program provided by Microsoft. For the compiler and utilities, a list of command-line parameters and switches is given. For nmake -- Microsoft's much-awaited enhanced make utility -- a reference to makefile statements is provided. The section on Code View presents a complete list of all debugger commands and data formats. The documentation for Programmer's WorkBench includes tables explaining the environment's numerous switches, macros, functions, and key assignments.

Following the utilities section, the Microsoft C Reference has a complete list of all library functions, providing information on prototypes, required header files, return values, and compatibility between operating systems. Each function has a one- or two-sentence description, which is adequate for an experienced C programmer. The appendices list printf and scanf format specifiers, key codes, and ASCII values. I think the Microsoft C Reference is the longest quick-reference manual I've seen, but it has quickly become indispensable.

A wide variety of topics are covered in Advanced Programming Techniques. It contains chapters on optimization, floating-point operations, memory management, the new in-line assembler, project development, debugging, graphics, and mixed-language programming. This how-to book provides details on how the MSC6 system can be used most effectively.

My overall impression of the documentation is that it was designed for an experienced professional. I would not recommend this product to someone just beginning to work with C. Beginners need hand-holding and detailed documentation, something that Microsoft is not providing with MSC6.

The Compiler and Tools

QuickC is not included with MSC6. However, the core compiler for QuickC is there; it can be invoked by the compiler control program with the /qc switch. Using /qc doubles the speed of compiles, but prevents you from using advanced optimizations. The compiler itself has actually changed in a number of areas from MSC5.1 to MSC6. The primary additions are in improved ANSI compatibility, a Tiny memory model, an in-line assembler, optimizations, "based" pointers (see sidebar), and a new long double type.

MSC5.1 stuck closely to the ANSI standard; MSC6 is just about on the money. New ANSI features in MSC6 include complete support for volatile, long values in switch statements, and support for "locales." A locale describes the numeric, money, date, and time formats for a given country. As with most MS-DOS C compilers, MSC6 only supports the C locale, which is identical to the locale for the United States, and is the only locale an ANSI-standard compiler must support.

Microsoft has finally recognized that some programmers want to produce programs in the simple .COM format. The new Tiny memory model is a modified version of the Small model; the primary difference is in how a program is linked. Link combines a special library with Tiny model programs to directly create a .COM file without a call to exe2bin.

The in-line assembler was introduced by Microsoft in QuickC 2.01, and it's now available in MSC6. The _asm keyword can preface a block of assembler instructions in a C program. The _emit function places a series of arbitrary byte values into program code. While the in-line assembler and _emit are non-portable, they do provide a convenience many programmers will find difficult to resist (see "DOS + 386 = 4 Gigabytes," DDJ, July 1990).

As predicted, Microsoft has increased the power of its optimizer. Many new optimization options have been added, including global optimizations that look at functions as a whole when analyzing a program. MSC6 is now in the same class as Watcom and Zortech when it comes to optimizations.

MSC5.1 was known for the infamous aliasing problem. An alias occurs in a C program when two different names refer to the same memory location. An optimizer can produce faster code if it can assume that no aliases exist. Under MSC5.1, the maximum optimization compiler switch (/Ox) told the optimizer to ignore the existence of aliases. Many programmers complained when MSC5.1 generated non-functional programs from code that contained aliases. Microsoft has changed /Ox in MSC6 so that it assumes aliases exist. You can still tell the optimizer to ignore the possibility of aliasing, but you must do so explicitly with the /Oa switch.

A new feature of MSC6 is that optimizations can be turned on and off within a source file through the use of pragmas. This allows you to explicitly protect a piece of code from strong optimizations, for example. MSC6 can enregister parameters, an optimization pioneered by Watcom C. Individual functions or complete source modules can be compiled with enregistered parameters. Normally the parameters to a function are pushed onto the stack before the function call is made. When enregistered parameters are in effect, parameters are stored in registers when the function call is made. This saves a considerable amount of pushing and popping of parameters on the stack, and can increase the speed of a program that makes numerous calls to functions with small numbers of arguments.

nmake is Microsoft's professional make utility. The older make program included with previous versions of Microsoft C was abysmally weak. Unlike its predecessor, nmake is compatible with Unix.

Debugging

CodeView 3.0 has been awaited with great expectations. Earlier versions of Microsoft's pioneering debugger were very good but clumsy to use. Products such as Borland's Turbo Debugger were easier to use and more powerful. While CodeView 3.0 goes a long way towards answering its critics, it's still a few steps short of what programmers want.

Tiled windows are passe -- more information can be easily displayed in layered windows. Unfortunately, CodeView 3.0 uses tiled windows. With Programmer's WorkBench and other full-screen applications using a layered window system, CodeView seems a bit out-of-step. I could not use CodeView 3.0 with more than four windows open on a 25-line screen.

I also found CodeView 3.0 to be frustrating. I want CodeView to always come up in VGA 50-line mode with use of the 80386 debugging registers. While commands for CodeView can be placed in the TOOLS.INI initialization file, none of these commands change the default command-line parameters. There isn't even a CV environment variable to which default switch settings can be assigned. The compiler and linker both support environment variables that contain command-line switches; why CodeView doesn't is a mystery to me.

CodeView 3.0 can be run in extended memory (leaving more room for programs in main memory), but it can only do so when the HIMEM.SYS driver is loaded. HIMEM.SYS is incompatible with any other 386 control program, which prevented me from using Qualitas' 386MAX program, for example, to manage high memory when HIMEM.SYS is loaded.

CodeView 3.0 does not support the Virtual Control Program Interface (VCPI) for 386 programs, a standard developed by Phar Lap that allows multiple 386 virtual memory programs to operate in concert. Microsoft informed me that a VCPI version of CodeView may be developed, but did not name a release date. Make no mistake: CodeView 3.0 is superior to previous versions of the debugger. It has several new windows, including a "locals" view, which shows all variables local to the currently executing function. There is more flexibility in how windows can be tiled, and the command-line interface has largely been replaced by a set of keystrokes and mouse selections. Overall, CodeView 3.0 is a very powerful debugger. With a little work, it could be the best MS-DOS debugger available.

Benchmarks

Benchmarks are infamous for being both controversial and subjective, yet they are one of the few empirical tools we have for comparing compilers.

Optimization has become one of primary ways in which vendors differentiate their products from the competition. An optimizing compiler performs an analysis on a program being compiled, generating a more efficient program. Optimizers can delete unused code and variables, improve register use, combine common subexpressions, precalculate loop invariants, and perform other tasks that improve program performance.

At best, optimizing a well-written program will improve its speed by as much as 25 percent. An optimizer will not replace inefficient algorithms with better ones. As the saying goes, "garbage in, garbage out." Most of the responsibility for a program's performance lies with the programmer. Improving algorithms and manually optimizing a program will often increase program speed by several orders of magnitude. The purpose of an optimizer is to make sure that the compiler is producing the fastest code possible from your source code.

In recent years, Watcom's C 7.0 has become the standard by which optimizing C compilers are judged. Watcom entered the market in 1988 with a compiler that produced very fast executable programs. To see how well Microsoft C 6.0 stands up to the competition, I have run the benchmarks for it, as well as the benchmark for Watcom C 7.0. I've also included benchmark results for Microsoft C 5.10. These results will show current users of Microsoft C how much improvement they can expect from the new version. Table 1 shows the results of these benchmark tests.

Four programs make up the benchmark suite. Dhrystone 2 is a standard industry benchmark designed to represent the "average" program. For this test, it was set to run 200,000 iterations.

DMath is a test of floating-point code generation of my own invention. DMath calculates the sines of the angles between 0 and 360 degrees using a simple series. A test of floating-point code generation, DMath contains only double data items, and it makes no library function calls.

XRef is a test of dynamic memory management and I/O speed. XRef is a filter program that creates a cross-reference of input from standard input. The cross-reference is displayed to standard output.

The last benchmark program is GrafTest, a program that exercises my low-level graphics library. GrafTest performs millions of function calls, and interfaces directly with video hardware.

All tests were run on a 20-MHz i386-based computer running MS-DOS 3.30. The computer was equipped with a 25ms hard drive, a 20-MHz 80387 coprocessor, and a 16-bit VGA video system.

Two compiles were done for the benchmark chart. One compile used the compiler and linker options I would use when generating a program with debug information in it. The other compile used full optimization, inline math coprocessor instructions, and 80286 code generation. Most compiles done during development are with full debugging information on. For MSC6, I ran two sets of benchmarks -- one with enregistered parameters, and the other without. Compile times are actually a combination of compile and link times. Timings are an average for five compiles/runs. The compiler command lines used are shown in Figure 1.

  Watcom C 7.0 debug       :-2 -7 -Os -d2 -ms
  Watcom C 7.0 opt.        :-2 -7 -Oail -s -ms
  Microsoft C 5.1 debug    :/c /G2 /FPi87 /qc/Zi /Od /AS
  Microsoft C 5.1 opt.     :/c /G2 /FPi87 /Ox /AS
  Microsoft C 6.0 debug    :/c /G2 /FPi87 /qc /Zi /Od /AS
  Microsoft C 6.0 opt.     :/c /G2 /FPi87 /Oxaz /AS

Figure 1: The compiler command lines used in the benchmark tests. The /Gr switch was added to the Microsoft C 6.0 command lines when enregistered parameters were used.

Table 1 shows some clear trends. Enregistered parameters in MSC6 did not provide a significant increase in program speed in the DMath or XRef tests. The biggest gain from enregistered parameters is seen in the GrafTest benchmark, which makes hundreds of thousands of calls to functions that take two or three int parameters. Because Dhrystone 2 lacks function prototypes, MSC6 was unable to employ enregistered parameters. While Microsoft C 6.0 produced the fastest debug compiles and the best run times in three out of four tests, it fell far behind in run-time speed on the DMath test. Watcom maintains its position as the best compiler for mathematically demanding applications.

                          Watcom    Microsoft    Microsoft    Microsoft
  Program & Test            7.0       C 5.1        C 6.0        C 6.0
                                                   no/Gr         /Gr
-----------------------------------------------------------------------
  Dhrystone 2
   time: debug compile     24.24      13.13        11.65           ---
   time: opt. compile      25.15      20.48        32.26           ---
   time: execution         31.88      29.42        27.25           ---

   .EXE file size         14,082     19,078       20,334           ---

  DMath
   time: debug compile     11.04       6.50         5.87          6.22
   time: opt. compile      10.29       8.46        12.21         12.03
   time: execution         30.39      40.64        40.48         39.41

   .EXE file size          6,534      9,572       12,868        12,820

  XRef
   time: debug compile     13.74       7.98         7.20          7.31
   time: opt. compile      13.70      11.60        16.83         16.98
   time: execution         33.48      32.42        32.41         32.37

   .EXE file size          7,125      9,067        7,639         7,607

  GrafTest
   time: debug compile     19.99      11.03        13.35         12.91
   time: opt. compile      26.05      22.09        49.05         50.47
   time: execution         27.71      28.78        27.09         26.85

   .EXE file size          4,230      5,603        5,845         5,781

Table 1: Benchmark results for Microsoft C 5.1, 6.0, and Watcom C 7.0

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.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.