Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

C Programming


JUL90: C PROGRAMMING

MIDI, Turbo C++, Token Pasting, and PC Hot Keys

I am writing this month from Redding, California, where the annual Redding Jazz Festival is underway. My other life often finds me at these events seated at a different, wider keyboard. This acoustic form of jazz that I know has not yet given way to technology. We use computers to keep track of bookings, income (what little there is), and such, but we do not play this style of music with machines. Other forms of music, including some newer forms of jazz, are using synthesizers, sequencers, samplers, and an interface called "MIDI" that computer manufacturers could learn from. I have no ear for the sounds and am certainly no expert in the technology, but it is fascinating. Jim Conger has written two books named MIDI Sequencing in C and C Programming for MIDI. Both are published by M&T Books (Redwood City, Calif.) and both address aspects of MIDI programming that a C programmer can use on a PC with a MIDI interface and Microsoft or Turbo C.

Conger explains what MIDI is and how it works, and he provides ample C code to achieve the effects he describes. I do not have any MIDI equipment, so I did not try to run the programs, but the text in the books is understandable and the code is readable, although the small, pale typeface that the publisher uses for code makes it hard to read. I used the diskettes to read the code instead. Maybe it's a way to get us to buy the diskettes, which anyone who gets a book for its code ought to do anyway.

The author says that musicians are being threatened by synthesized music. This is true. Many of my colleagues in music feel that threat; some of them have been touched by it. But Conger goes further and speculates that human composition, conducting, and performance can eventually be replaced by technology. He conjures an image of a digitized wire-frame model of Leonard Bernstein conducting a bank of synthesizers. Add an audience of rows and rows of nothing but digital audio tape recorders, and the picture would be complete.

Turbo C++

The news this month is Turbo C++. Following their success with object-oriented Pascal 5.5, Borland has assaulted the C++ object-oriented market with guns drawn and blazing. Originally intended to be an adjunct to Turbo C 3.0, the C++ face of Borland's compiler is now at its forefront. By emphasizing the object-oriented side of their most popular compiler product, Borland has made a policy commitment to object-oriented programming as the paradigmatic wave of tomorrow.

Included with Turbo C++ are a C++ compiler, an ANSI-conforming standard C compiler, and a completely overhauled Integrated Development Environment. The C++ compiler conforms to the AT&T 2.0 language specification, a standard of sorts but still a moving target. ANSI has launched its X3J16 C++ standards committee and we'll be hearing more about that undertaking in the months and years to come. Until then, purveyors of C++ language products have the AT&T reference manual and the AT&T implementation of C++ to aim at.

Most of the C++ programming systems available to PC users are ports of AT&T's cfront program, the translator that reads C++ source code and emits C language source code. The notable exception until now was Zortech C++, an implementation that is C++ 2.0 in spirit but that has numerous departures from the AT&T implementation. Borland has attempted to adhere closely to the AT&T reference manual.

I am writing this column in April, a month before the scheduled announcement of Turbo C++. You are reading it sometime near the middle of June, a month after the announcement, assuming no delays. There are always impediments to timely reporting when you work within this kind of schedule. My experience with Turbo C++ has been, therefore, gained only in betaland. For that reason I can report no problems with the product. Sure, there have been problems, but this is a beta copy, and that's what a beta test is for.

I just finished writing a book called Teach Yourself C++. By the time you read this, Herb Schildt will no doubt be writing one with the same title. Don't be fooled by imitations. Mine has a yellow cover. (Sorry, Herb.) My book has about 130 exercise programs. Each exercise demonstrates a specific feature of the C++ language. As such, the book is something of a minor C++ torture test, and I used two ports of the AT&T cfront program to validate the exercises. Turbo C++, not a cfront port, passed with flying colors even in its beta configuration.

There are two different compilers in the product called Turbo C++. The other compiler is a full ANSI C compiler. Both compilers execute from within the Integrated Development Environment or from the command line. The program figures out what kind of program it is compiling based on the extension of the source file. A .C file is a C program. A .CPP file is a C++ program. The compiler needs to know the difference to know how to handle certain subtle differences in the languages. One of those differences involves "typesafe linkages."

Type-Safe Linkages

Type-safe means that a function's declaration and its callers use compatible types for the parameters. There has been a measure of type checking in C for a while. A C program specifies a function's return type and parameter list in the function prototype. If the function declaration or a call to the function does not match the prototype specification, the compiler declares an error. In the old days before prototypes a function was assumed to return an integer unless it was declared to return something else. You could pass it any number and types of parameters regardless of what it was expecting. The compiler did not care. The programmer had to wrestle with the bugs that resulted when the program passed one thing and the function expected something else. C++ needs stronger typing and so its designer created the function prototype, which was adopted by many C compilers and eventually by the ANSI C standards committee. The prototype allows the compiler to insure that calls to a function match the function's declaration with respect to its return type and parameter list.

The C prototype is an effective measure as long as the source file that declares the function and the one that calls it are using the same copy of the prototype. By convention, most programmers will put the prototype in a header file and include the header in the source file that declares the function and all others that call the function. But this practice is only a convention and nothing in the C language enforces it. If you use different prototypes for the same function in different source files, the calls to the function can have different configurations with the same undesirable results that we had before prototypes. The problem is, of course, that you compile the source files independently, and the linker has no way to reconcile different function prototypes between independently compiled object modules. The traditional method for controlling this situation is to use the header file convention mentioned above and a well-defined make file that assures that all affected source files compile when a header file they include changes.

C++ requires stronger type checking across linked modules because much of the integrity of a program depends upon the correct use of overloaded functions. Therefore, C++ 2.0 introduces a feature called "type-safe linkage" to guarantee that function prototypes are consistent across independently compiled source files. To achieve this guarantee, the C++ compiler translates function names into "mangled" names. The translator adds characters to the function name to encode the parameter list. For example, the following function prototype would generate the following mangled function name.

  long foo(int, char*, struct tm);   // mangled name: _foo_FiPc2tm

The mangled name specifies, in cryptic notation, the types of the parameters. An external function does not, therefore, have the name you gave it. If you prototype it differently in a remote source file, the mangled name will be different, and you will get unresolved symbols when you link the program. Thus you have type-safe linkage at the parameter list level. For some reason, this technique does not similarly encode the function's return type.

These mangled names are usually invisible to the programmer unless you read the linkage memory map, but you must know about them because the unresolved symbol errors will report the mangled names rather than the ones you lovingly bestowed and you need to find the original name among the mangled mess. There will be times when you need to tell C++ not to mangle.

Suppose your C++ program calls a function that was compiled by a C compiler. That is not an unusual thing to do. The entire standard C function library is available to C++ programs. So, unless you tell the C++ compiler otherwise, it will mangle the function name in the call, and the linker will not find a matching function. Therefore, the C++ compiler needs a way to know that a named function was compiled by a C compiler. C++ uses this construct to achieve that purpose.

extern "C" 
{ 
// C things . . . 
}

You can put prototypes between the curly braces, and the compiler knows that calls to those functions must be made without mangled names. C++ compilers usually include standard C header files that already have the extern "C" statement built in.

If you surround an entire function with the extern "C" braces, that function will compile with C linkages. You would need to do that in the case where you are using a C library function that requires your program to provide a named function. Of course, such functions do not enjoy the same type-safe linkage that regular C++ functions have.

ANSI C Token Pasting

A few months ago I wondered about the ANSI C preprocessor ## token pasting operation. If you code the ## operator in a macro replacement list, it replaces the two parameters that surround it by "pasting" the two arguments together into one argument and then continuing with preprocessing. For example:

  
#define pasteup(a,b) a ## b   
foo(pasteup(12, 34));

The resulting statement would be:

  
foo(1234);

One is hard pressed to imagine a use for this feature. Necessity being the mother she is, we can only speculate that the invention of ## sprang from the loins of need. The behavior of ## is more peculiar when you consider this: pasteup(hello,dolly)( ); The effective statement is: hellodolly( ); which makes us suspect that ## could be of some use in the preprocessor fabrication of identifiers.

The ## behavior is well documented in the ANSI C standard, but there are no good examples of its use or clues to its purpose. That bothered me because I couldn't come up with a good use for it. So, as an experiment, I went looking for an application for the odd little critter. It would have been unsporting to call someone on the X3J11 committee and ask what it was for but I wanted to find a real use for token pasting, and eventually I did. What I found, however, was not code that used the ANSI token paster at all. In fact, what I found was not in a C program. Instead, I found a contrivance in the generic.h header file that comes with C++ 2.0 compilers. The following construct works with pre-ANSI C and C++ preprocessor programs:

  
#define name2(a,b) a\   
b

The purpose of this macro and others like it are to paste type and class names together to form what has been called a "parameterized" class from a generic one. In Programming in C++, Prentice Hall, 1989, Dewhurst and Stark describe what they call "genericity," still another abominable non-word that someone, out of generosity and in the true spirit of computer literature, has contributed to the English-speaking world. There they observe that ANSI-conforming compilers could achieve the same thing with this:

  
#define name2(a,b) a##b

Eureka! Now a quick look at the generic.h of Turbo C++ reveals that they do indeed use the ## operator in just that fashion and my quest is complete.

My original comments about ## came in a review of Rex Jaeschke's book, Mastering Standard C. Some readers called to say that they could not locate the book or the publisher. Here is the address: Professional Press, 101 Witmer Rd., Horsham, PA 19044, 215-957-1500.

The Future of C

Turbo C++ marks the dawn of an era. C++ proponents would say that the era started a long time ago, but from my view, the era began when a well-established and leading language vendor such as Borland bet the farm and came out full-bore with a complete C++ development environment. You can bet that Microsoft is not far behind.

The C++ dialect is destined to replace C as the language of choice for developers of software systems. The combination of objects, user-defined data types (same thing, only different), and the C syntax makes C++ a natural heir to the throne. But there is at least one category of program that will never switch to C++. Developers of utility and systems programs will stick with C for its freedom and close-to-the-metal feel and for its lean and mean executables. C++ programs can have those qualities, but at the expense of extensibility. Add all those extensible data types and your programs will spend time and memory with layers of constructors and destructors and class hierarchies. Leave them out and you are using a C++ compiler to develop a C program. C lives.

By the way, if you want Turbo C without C++, you can still buy Turbo C 2.0. Borland will continue to market and support that version of the compiler as the C language entry-level compiler to compete with Microsoft QuickC. I do not know prices just now, but you should expect Turbo C 2.0 to be a bit cheaper than when it was this year's snappy model. I would have preferred to see a TC 3.0 that fixed the ANSI incompatibilities and the bugs and used the new, improved IDE, but apparently Borland decided to invest their resources in the C++ package and leave 2.0 the way it is for those who want it.

Hot Keys

We've all used programs that employ hot keys. On the PC, hot keys are key combinations that a program uses to cause things to happen. The most typical use is to make a TSR pop up. By definition, a hot key should be a key combination that applications do not use in their normal command set. By convention, hot keys are combinations that do not produce anything on the screen in normal DOS use and that are not one of the generic function keys. Sidekick uses the Ctrl-Alt combination. Ready uses the 5 on the numeric keypad (with NumLock off).

A programmer has three problems to solve with respect to hot keys. First, how do you read one from the keyboard to let the user change the hot key setting? Second, how do you translate that into a meaningful display to tell the user what the key combination is? Third, how do you know when the hot key has been pressed? The code that accompanies this month's column addresses those problems.

Listing One, page 146, is hotkey.h, a header file that an application program will include to use the hot key functions. It contains the prototypes.

Listing Two, page 146, is testhk.c, a program that demonstrates the use of the hot key functions. Hot keys are described in terms of their keyboard scan code and the BIOS shift key mask. Each key on the keyboard sends a unique scan code to the computer when you press a key. These codes are not ASCII, and you will find them documented in many books about programming the PC. The BIOS shift key mask is a byte in low RAM that represents the current status of the Shift, Ctrl, and Alt keys. The hot key software takes care of the translation of the codes to the key values.

The testhk.c program begins by displaying these messages to describe what it expects you to do:

Press a hot key
Press Esc to keep the one you have
Press the Space Bar to clear the one you have
Press Enter to use the new one

Then it calls the hotkey function, passing the addresses of an integer for the scan code and one for the shift key mask. The hotkey function lets the user press different hot key combinations, displaying each one until the user presses Enter, Esc, or the Spacebar.

Once the user has selected a hot key, the testhk program displays its value by calling the showkey function, which displays something like Ctrl-X on the screen. Then testhk calls initkey to initialize testing for the hot key. The iskeyhit function returns nonzero if the hot key has been pressed and zero if not. The testhk program loops waiting for a press of the hot key. Then it calls endkey.

It is important that the program calls initkey before testing for the hot key press and endkey afterwards. Don't leave them out.

Listing Three, page 146, is hotkey.c, the functions that manage hot key programming for your application. The initkey function attaches the keyboard interrupt vector to the newkb interrupt service routine. That function simply reads the keyboard input port into a variable named kbval and chains to the old interrupt vector. On some PCs you do not need the interrupt service routine. Anywhere the program examines the value in kbval you could simply read the keyboard input port 0x60. Other computers do not work that way. The input port delivers a meaningful value only after an interrupt 9. Therefore, the program attaches to interrupt vector 9 to read the value of the keyboard port.

The iskeyhit function accepts a scan code and shift key mask and tests them against the current values of kbval and the BIOS shift key mask. The function returns true if the values match and false otherwise.

The hotkey function programs a hot key. When the user presses a valid hot key combination, the function displays an ASCII representation of it on the screen and waits for another key press. When the user presses Enter, the scan and shift mask values associated with the most recent key are returned to the caller. If the user presses the Esc key, the caller's original values are returned. If the user presses the Spacebar, zeros are returned.

Reading a hot key is no cake walk. Neither DOS nor BIOS return anything when you press one of the key combinations that we have said are valid hot keys. What you must do is examine the input from the keyboard port and react according to the scan codes that come in. The first thing to do is wait until the user gets off the keyboard. The most significant bit (0x80) of the scan code will be zero if any key is being held down. Otherwise it will be one. Next, you must clear the BIOS read-ahead buffer and keep it clear. Every time the user presses a valid BIOS key, BIOS adds an entry to a circular read-ahead buffer. By clearing that buffer, you prevent BIOS from beeping when the buffer is full. Then you go into a loop reading the keyboard port waiting for a key to be pressed. If that key is the scan code for the Esc, Enter, or Spacebar key, you are done. Otherwise, it must be the Ctrl, Alt, or a Shift key. After that you wait for another key to be pressed. Then you wait for a third key press or a key release at which time you have the two or three scan codes that constitute the hot key. From them it is a simple matter to validate the combination.

The user might try to program an invalid hot key. You would not want to use the letter A or the Home key as hot keys, for example. A hot key must be a combination that includes one or more of the Shift, Ctrl, and Alt keys and another key. Or a valid hot key can be any two or more of the Shift, Ctrl, and Alt keys. If the user selects anything else, the hotkey program buzzes and rejects the selection.

The showkey function returns a pointer to a display string that it constructs from its scan code and shift key mask parameters. You can use this string to display the name of the hot key.

These functions compile with Turbo C. To use them with another compiler, you must use that compiler's version of the setvect, getvect, sound, delay, and nosound functions. Most compilers have variations on the first two. Microsoft C uses _dos_getvect and _dos_setvect, for example. The other three are Turbo C specific. You can use whatever method your compiler has to make a noise through the speaker or you can stub out the buzz function.

_C PROGRAMMING COLUMN_ by Al Stevens

[LISTING ONE]

<a name="018d_000a">

/* ----------- hotkey.h ---------- */

char *showkey(unsigned keyscan, unsigned keymsk);
int iskeyhit(char keyscan, char keymsk);
void hotkey(unsigned *keyscan, unsigned *keymsk);
void initkey(void);
void endkey(void);



<a name="018d_000b"><a name="018d_000b">
<a name="018d_000c">
[LISTING TWO]
<a name="018d_000c">

/* -------------- testhk.c --------------- */

/*
 * A program to demonstrate the use of the hotkey programmer
 */

#include <stdio.h>
#include "hotkey.h"

void main()
{
    unsigned scan = 0;
    unsigned mask = 0;

    printf("\nPress a hot key");
    printf("\nPress Esc to keep the one you have");
    printf("\nPress the Space Bar to clear the one you have");
    printf("\nPress Enter to use the new one\n");

    /* ------- program the hot key ---------- */
    hotkey(&scan, &mask);

    if (scan || mask)    {
        /* ------- display the programmed hot key --------- */
        printf("\nThe new hot key is %s (%02x, %02x)\n",
            showkey(scan, mask), scan, mask);

        initkey();
        while (!iskeyhit(scan, mask))
            printf("\rWaiting for you to press the hot key..");
        endkey();

        printf("\n%s was pressed", showkey(scan, mask));
    }
    else
        printf("\nNo hot key programmed");
}




<a name="018d_000d"><a name="018d_000d">
<a name="018d_000e">
[LISTING THREE]
<a name="018d_000e">

/* ---------- hotkey.c ------------ */

#include <stdio.h>
#include <conio.h>
#include <bios.h>
#include <dos.h>
#include <string.h>
#include "hotkey.h"

#define KYBRD 9

/*
 * Program a hot key
 */

static char hotky[50];

static int multibits(int k);
static void clearkb(void);
static void buzz(void);

char *scodes[] = {
    "Esc",
    "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
    "[-]",
    "[=]",
    "Bksp",
    "Tab",
    "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]",
    "Enter",
    "Ctrl",
    "A", "S", "D", "F", "G", "H", "J", "K", "L",
    "[;]",
    "[\"]",
    "[`]",
    "LShift",
    "\\",
    "Z", "X", "C", "V", "B", "N", "M",
    "[,]",
    "[.]",
    "[/]",
    "RShift",
    "PrtSc",
    "Alt",
    "",
    "",
    "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10",
    "",
    "",
    "Home",
    "Up",
    "PgUp",
    "[-]",
    "[<-]",
    "[5]",
    "[->]",
    "[+]",
    "End",
    "Dn",
    "PgDn",
    "Ins",
    "Del"
};

static void (interrupt *oldkb)(void);
static void interrupt newkb(void);
static int kbval = 0;

/* ----- keyboard ISR ------ */
static void interrupt newkb(void)
{
    kbval = inportb(0x60);
    (*oldkb)();
}

/* ------------ initialize for hot key test ----------- */
void initkey(void)
{
    /* -------- attach to keyboard interrupt -------- */
    oldkb = getvect(KYBRD);
    setvect(KYBRD, newkb);
}

void endkey(void)
{
    setvect(KYBRD, oldkb);
}

/* --------- test for a specified keystroke ----------- */
int iskeyhit(char keyscan, char keymsk)
{
    char far *bp = MK_FP(0, 0x417);
    int rtn;

    /* -------- if no key is depressed, bail out ---------- */
    if ((kbval & 0x80) != 0)
        rtn = 0;
    else if (keyscan && kbval != keyscan)
        rtn = 0;
    else
        rtn = (keymsk == ((*bp) & 0xf));

    return rtn;
}

/* ---------- program the user hot key ----------- */
void hotkey(unsigned *keyscan, unsigned *keymsk)
{
    int key1, key2 = 0, key3, k = 0;
    char svbios;
    char far *bp = MK_FP(0, 0x417);

    /* ---------- attach to keyboard interrupt ------------- */
    initkey();

    /* ------- first, we need to be off the keyboard ------- */
    kbval = 0x80;
    while ((kbval & 0x80) == 0)
        ;

    /* --------- clear the BIOS readahead buffer ---------- */
    clearkb();
    svbios = *bp;
    while (1)    {
        key1 = 0x80;
        key3 = 0;
        /* ------- wait for a key press --------- */
        while (key1 & 0x80)
            key1 = kbval;
        /* ------ if Spacebar, Enter or Esc, drop out ------ */
        if (key1 == 57 || key1 == 1 || key1 == 28)
            break;
        /* ------ must be Alt, Ctrl, or Shift -------- */
        if (key1 != 56 && key1 != 29 &&
               key1 != 42 && key1 != 54)    {
            buzz();
            continue;
        }
        /* ------- wait for a second key press -------- */
        while ((key2 = kbval) == key1)
            if (key2 & 0x80)
                break;
        key2 &= 0x7f;
        k = 0;
        if (key1 == key2)
            /* ----- released the first key ----*/
            continue;
        /* ---- wait for key release or key change ----- */
        while (((key3 = kbval) & 0x80) == 0)
            if (key3 != key2 && key3 != key1)
                break;
        key3 &= 0x7f;
        if (key3 == key2 || key3 == key1)
            key3 = 0;
        /* -------- clear the bios readahead buffer -------- */
        clearkb();
        *bp = svbios;
        /* ------ look at the keystrokes -------- */
        switch (key1)    {
            case 56:    k |= 8; break;
            case 29:    k |= 4; break;
            case 42:    k |= 2; break;
            case 54:    k |= 1; break;
            default:    break;
        }
        switch (key2)    {
            case 56:    k |= 8; key2 = 0; break;
            case 29:    k |= 4; key2 = 0; break;
            case 42:    k |= 2; key2 = 0; break;
            case 54:    k |= 1; key2 = 0; break;
            default:    break;
        }
        switch (key3)    {
            case 56:    k |= 8; key3 = 0; break;
            case 29:    k |= 4; key3 = 0; break;
            case 42:    k |= 2; key3 = 0; break;
            case 54:    k |= 1; key3 = 0; break;
            default:    break;
        }
        if (key2 != 1 && key2 != 28)    {
            if (key2 == 70 || key3 == 70)    {
                /* ---- can't use the Break key ---- */
                buzz();
            }
            else    {
                if (key2 == 0)
                    key2 = key3;
                /* ---- shift Function keys allowed ---- */
                if ((k<4 && key2) && (key2 < 59 || key2 > 68))
                    buzz();
                else if (!(k && (key2 || multibits(k))))
                    buzz();
                else
                    printf("\r%s                              ",
                        showkey(key2, k));
            }
        }
        /* -------- wait for the key release -------- */
        while ((kbval & 0x80) == 0)
            ;
    }
    if (key1 == 57 && k == 0)
        *keymsk = *keyscan = 0;
    if (key1 == 28 && k != 0)    {
         *keymsk = k;
        *keyscan = key2;
    }
    clearkb();
    *bp = svbios;

    endkey();
    kbval = 0;
}

/* ----- test a nybl for multiple bits ---- */
static int multibits(int k)
{
    int ct = 0, i;

    for (i = 1; i < 16; i *= 2)
        if (k & i)
            ct++;
    return (ct > 1);
}

/* ----- show the hot key ------ */
char *showkey(unsigned keyscan, unsigned keymsk)
{
    *hotky = '\0';
    if (keymsk & 8)
        strcat(hotky, "Alt-");
    if (keymsk & 4)
        strcat(hotky, "Ctrl-");
    if (keymsk & 2)
        strcat(hotky, "LShift-");
    if (keymsk & 1)
        strcat(hotky, "RShift-");
    if (keyscan)
        strcat(hotky, scodes[keyscan-1]);
    else
        *(hotky + strlen(hotky) - 1) = '\0';
    return hotky;
}

/* ----- clear the BIOS keyboard read-ahead buffer ----- */
static void clearkb(void)
{
    int far *nextoff = MK_FP(0x40,0x1a);
    int far *nexton  = MK_FP(0x40,0x1c);
    *nextoff = *nexton;
}

static void buzz(void)
{
    sound(100);
    delay(250);
    nosound();
}










Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.