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

Design

Extending printf( )


AUG90: EXTENDING PRINTF( )

EXTENDING PRINTF( )

Taking advantage of variable argument lists in C

This article contains the following executables: MISCHEL.LST

Jim Mischel

Jim is a former financial systems programmer and data processing consultant. He can be reached at 13610 N. Scottsdale Rd., Suite 10 - 251, Scottsdale, AZ 85254 (CompuServe: 73717, 1355).


While writing my first serious financial program in C (I'd previously used the language only for utilities), I was distressed by the lack of suitable output formatting -- specifically, comma-separated numbers with trailing signs. After working with Cobol financial applications several years, I expected such niceties from any language.

Faced with this shortcoming, I immediately began working on a function that provided Cobol-like output formatting using Cobol edit picture strings, and so on. The output function worked well, but was awkward to use. I wanted an easy-to-use function such as printf() that would allow me to output formatted numbers as well as all the other standard printf() types. I posed this problem to several C programmers and got a variety of answers. The most common answer was, "buy the runtime library code and modify printf()." I agreed that this was probably the best solution for my particular situation; nevertheless it would have been implementation-specific, expensive (Turbo C library source code costs $250), and no solution at all for compilers for which the runtime library source code is not available.

The second most common reply was, "write your own printf()." I understood that this is a common pastime among bored computer science majors and back room hackers, but I had neither the time nor the desire to reinvent the wheel in order to make a minor engineering change.

Finally, one programmer I spoke with suggested that I write a front end to printf() and use this function to pre-scan the argument list, handling the special formats before passing the arguments to printf().

Variable Arguments

One of the niceties C offers is the ability to write functions that accept a variable number of arguments. The standard functions printf() and scanf() are the primary examples. Normally these functions have a fixed minimum number of parameters and a variable number of arguments, the number of which is usually specified either explicitly or implicitly by the fixed arguments.

There are actually two types of functions that accept a variable number of arguments. The way these work is best described by examining the prototypes for the two types of functions. The functions printf() and vprintf(), defined by the prototypes shown in Example 1, perform almost the same function. The only difference is how they access arguments passed to them: printf() expects an unknown number of arguments to follow the format string, and vprintf() is passed a pointer to a contiguous block of memory that contains the number of arguments specified in the format string.

Example 1: The functions printf() and vprintf() are defined by these prototypes.

  int printf (const char *format, ...);
  int vprintf (const char *format, va_list ap);

The arguments to printf() are read from the stack after being pushed in the function call. vprintf() reads the arguments from the block of memory to which the ap variable points. On the Intel 80x86 processors, these functions work almost identically -- the only difference is that printf() must first determine where the block of memory starts, which just happens to be the address contained in the SS:SP registers. Memory maps for these function calls are shown in Figure 1.

The ANSI standard for the C language calls for a standard header file, stdarg.h, that defines the typedef va_list and the functionality of the macros va_start, va_arg, and va_end. These macros are used in functions that accept a variable number of arguments. In Turbo C, the content of this header file, shown in Example 2, makes me wonder if "..." is a valid variable name.

Example 2: Contents of the Turbo C header file

  typedef void *va_list;
  #define va_start (ap, parmN)  (ap = ...)
  #define va_arg (ap, type)      (*((type *) (ap))++)
  #define va_end (ap)

The va_start macro stores initial context information in the variable designated by the pointer ap. parmN is the name of the last argument that you declare. parmN must not be an array type, function type, type float, or an integer type that is changed when promoted (that is, char). This macro must be invoked before the initial va_arg macro is used.

The va_arg macro returns the value of the argument of type type that is pointed to by the pointer ap. It then increments the pointer ap to the next argument.

The va_end macro is used to perform the cleanup operations required before the function can return. Although this macro does nothing in Turbo C, some implementations may require it, so you might want to include it in the interest of portability.

The %m Format Type

The %m format that I added to printf() and associated functions outputs a comma-separated number with optional leading or trailing sign. Field output width, precision, and justification can also be specified as in the other printf() formats. Default width, precision, and size, and a full explanation of the effects of flag characters, are shown in Table 1.

Table 1: The behavior of the new "%m" output format for printf() and related functions.

  Flag                 How it affects output
  -------------------------------------------------------------------------

  -                  Left-justifies the format
  +                  Generates a plus sign for signed values that are
                     positive.
  blank              Generates a space for signed values that are positive.
  #                  Generates a trailing sign if required by sign or one
                     of the other flags (+ or blank).
  0                  Ignored
  Width              Same as for "f" format
  Precision          Same as for "f" format with the exception that default
                     precision is 2, rather than 6 decimal places.

  Input-size modifier  How arg is interpreted
  -------------------------------------------------------------------------

  none               arg is interpreted as a float
  I                  arg is interpreted as a double
  L                  arg is interpreted as a long double

Turbo C departs from the ANSI standard in how the size modifiers are interpreted. The standard specifies that "f" is a double and "Lf" is a long double. There is no "If" format.

The first major stumbling block was redirecting printf() and associated function calls so that they could take advantage of my new output format. Fortunately the C preprocessor came to the rescue with macros that did the redirection for me.

These macros simply change any printf() reference to AltPrintf(), and vprintf() to AltVprintf(), and so forth. Listing One (page 120), which shows mfmt.h, the header file that contains these macros, must be included AFTER stdio.h in any program that uses this new format.

The Alt ... functions simply set up the argument pointer, call the Formatter() function to handle the %m formats, and then call the appropriate routine to handle the rest of the print formatting using the new format string and argument list.

Listing Two (mfmt.c, page 120) contains the code that performs the number formatting and associated functions. This code can be broken down logically into two sections: The alternate entry functions (AltPrintf, AltFprintf, AltSprintf, AltVprintf, AltVfprintf, AltVsprintf), and Formatter() and associated functions.

The alternate entry functions provide a path between the application program and the Formatter() function. The alternate entry functions intercept the arguments, set up a new argument area if necessary, and produce the final output after the Formatter() function has handled any %m formats. These functions are never directly referenced in the source code. Instead, they are addressed through the redirection macros provided in mfmt.h.

AltVprintf, AltVfprintf, and AltVsprintf call the InitBlock() function to allocate a temporary memory buffer for the new argument list. This new argument area is necessary because the calling program's argument block cannot be modified without side effects. The other three functions do not require this temporary block because their arguments are passed on the stack -- changing them will not affect the calling program.

All of the alternate functions call the FreeBlock() function before exiting. This routine frees the temporary argument area and the memory allocated by the new format string (t).

The heart of the mfmt module is the Formatter() function. After allocating the memory for the new format string, this function simply copies the old format string to the new format string, replacing all %m formats with the formatted number. Any time a % symbol is found in the format string, it calls the ParseFormat() function for future processing. Upon exiting this function, t points to the new format string and the arguments are correctly placed in either NewBlock or on the stack, depending on which alternate entry function was called.

When Formatter() finds a % character in the format string, it calls the ParseFormat() function to perform further processing. This function picks up and saves the flags, width, precision, and size specifiers, and copies the arguments to the modified argument block.

The operation of this function and the ParseFlags, ParseWidth, ParsePrecision, and ParseSize functions are straightforward. Only a couple of items warrant mentioning.

The ParseFlags function uses a bit-encoding technique for reporting which flags are specified. Each of the four possible flags is assigned a power of 2. If that particular bit is set, it indicates the corresponding flag was given in the format string.

The ParseWidth and ParsePrecision functions modify two variables each: a number and a flag. The flags WidthFlag and PrecisionFlag specify which kind of width or precision was given, and the number Width and Precision specify the field width or precision, respectively.

ParseSize modifies the Size variable, which reports if any of the valid input size modifiers were specified.

The ParseType function determines which conversion type is specified and acts accordingly. Unless a %m conversion is specified, all flag, width, precision, and size variables are ignored, the arguments are copied directly, and no modification is done to the format string. Only when the %m conversion is given does any further processing occur.

Throughout the Parse ... functions, I used the AddBlockArg macro to copy arguments from one parameter block to the other, incrementing the NewArgPtr variable in the process. This macro references the va_arg macro to retrieve the next argument from the list. The RemoveBlockArg macro is used to decrement the NewArgPtr variable so that it points to the previous argument, effectively removing that argument from the argument list.

When a %m conversion is identified by ParseType(), the mFormat() function is called to format the number into the modified format string, and to clean up stray arguments from the argument list. mFormat() removes any width or precision variables from the argument list, determines the sign of the number, and calls dtoa() (Double to ASCII) to format the number into the temporary string variable Num. When dtoa() returns, mFormat() places the sign, justifies the number, and adds the formatted result to the modified format string t.

dtoa() is a fairly simple recursive number formatter. It handles precision and comma- separating, but leaves sign placement and justification to the following routines. dtoa() will not correctly handle a negative number; mFormat() must determine the proper sign and pass the absolute value of the number to dtoa().

Some Notes

The mfmt code is not reentrant. I had originally planned on making it reentrant, but soon found out why printf and others like it are not reentrant in most implementations: Too much bookkeeping going on! As a general rule, the use of global variables is not good programming practice. The code could be made reentrant by using double indirection rather than static variables, but this would complicate the code and make it much slower than it already is.

These functions are slower than the standard printf() functions. In effect, both the format string and the argument list are scanned twice, and plenty of time is spent allocating memory and shuffling things around so that the formatting will work correctly.

The functions in the mfmt module are a safe and portable way of modifying the behavior of the printf() family of functions. However, when economic factors and the need for speed warrant it, (and portability is not a consideration) the preferred solution is to obtain the runtime library code and directly modify the printf() functions.

_EXTENDING PRINTF()_ by Jim Mischel

[LISTING ONE]

<a name="01ac_000b">

/* mfmt.h -- macros and function prototypes for mfmt routine. */

#define printf   AltPrintf
#define fprintf  AltFprintf
#define sprintf  AltSprintf
#define vprintf  AltVprintf
#define vfprintf AltVfprintf
#define vsprintf AltVsprintf

int AltPrintf   (char *fmt, ...);
int AltFprintf  (FILE *f, char *fmt, ...);
int AltSprintf  (char *Dest, char *fmt, ...);
int AltVprintf  (char *fmt, va_list Arg);
int AltVfprintf (FILE *f, char *fmt, va_list Arg);
int AltVsprintf (char *Dest, char *fmt, va_list Arg);




<a name="01ac_000c"><a name="01ac_000c">
<a name="01ac_000d">
[LISTING TWO]
<a name="01ac_000d">

/* mfmt.c -- function to output comma-separated numbers with printf().
 * Copyright 1990, Jim Mischel
 * To compile for testing:
 *   tcc -ms -f mfmt
 * To compile for use as a called module:
 *   tcc -ms -c -f mfmt
 */
/* #define TESTING      /* remove comment for testing */
#include "stdio.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "math.h"

#define AddBlockArg(ap, type) (*((type *)(NewArgPtr))++) = va_arg(ap, type)
#define RemoveBlockArg(type) ((type *)(NewArgPtr))--

static va_list OldArgPtr;   /* Pointer to passed arguments */
static va_list NewArgPtr;   /* Pointer to new argument block */
static va_list NewBlock = NULL;   /* New argument block */
static char *Sptr;      /* Pointer to passed format string */
static char *Num;      /* Formatted number */
static char *NumPtr;      /* Pointer into formatted number string */
static char *t = NULL;      /* New format string */
unsigned tSize;         /* Length of new format string */
static char *Tptr;      /* Pointer to new format string */
static char *SavePtr;      /* Saved Tptr used when %m format found */
static int Flags;      /* Flags identified in format string */
static int Width;      /* Width from format string */
static int WidthFlag;      /* Identifies width type */
static int Precision;      /* Width from format string */
static int PrecisionFlag;   /* Identifies precision type */
static int Size;      /* printf() size modifier */
static int Sign;      /* Sign of number for formatting */

/* Increment the flags counter for each occurance of a flag character.
 * Valid flag characters are blank, '-', '#', '+'.  */
/* Definitions for flag characters */
#define Blank 1
#define Dash 2
#define Pound 4
#define Plus 8

static void ParseFlags (void) {
    Flags = 0;
    while (*Sptr == ' ' || *Sptr == '-' || *Sptr == '#' || *Sptr == '+')
   switch (*Tptr++ = *Sptr++) {
       case ' ' : Flags += Blank; break;
       case '-' : Flags += Dash; break;
       case '#' : Flags += Pound; break;
       case '+' : Flags += Plus; break;
   } /* switch */
} /* ParseFlags */

/* Width specification.  Minimum field width is returned in the Width variable.
 * WidthFlag specifies if '0' or '*' was given in the format string.  */
static void ParseWidth (void) {
    Width = WidthFlag = 0;

    if (*Sptr == '0')
   WidthFlag = (*Tptr++ = *Sptr++);
    if (*Sptr == '*') {
   WidthFlag |= 0x80;
   *Tptr++ = *Sptr++;
   Width = (AddBlockArg(OldArgPtr, int));
   if (Width < 0) {
       Width = abs (Width);
       Flags |= Dash;
   }
    }
    while (isdigit (*Sptr)) {
   Width = Width * 10 + *Sptr - '0';
   *Tptr++ = *Sptr++;
    }
} /* ParseWidth */

/* Precision specification. Returns precision in the variables Precision and
 * PrecisionFlag.
 * PrecisionFlag = 0, no precision was specified
 * PrecisionFlag = '0', ".0" specified
 * PrecisionFlag = 'n', ".n" specified
 * PrecisionFlag = '*', ".*" specified
 */
static void ParsePrecision (void) {
    Precision = PrecisionFlag = 0;

    if (*Sptr == '.') {      /* precision specified */
   *Tptr++ = *Sptr++;
   if (*Sptr == '*') {
       PrecisionFlag = (*Tptr++ = *Sptr++);
       Precision = (AddBlockArg(OldArgPtr, int));
   }
   else if (*Sptr == '0')
       PrecisionFlag = (*Tptr++ = *Sptr++);
   else {
       PrecisionFlag = 'n';
       while (isdigit (*Sptr)) {
      Precision = Precision * 10 + *Sptr - '0';
      *Tptr++ = *Sptr++;
       }
   }
    }
} /* ParsePrecision */

/* Input size modifier.  (F|N|h|l|L) */
static void ParseSize (void) {
    if (*Sptr == 'F' || *Sptr == 'l' || *Sptr == 'L' ||
   *Sptr == 'N' || *Sptr == 'h')
   Size = (*Tptr++ = *Sptr++);
    else
   Size = 0;
} /* ParseSize */

/* dtoa -- convert a double to comma-separated ASCII representation.  */
static void dtoa (double Work, int P, int Digits) {
    int c;

    if (P >= 0 || Work != 0) {
   if (P == 0) {
       c = '.';
       Digits = 0;
       P--;
   }
   else if (Digits == 3) {
       c = ',';
       Digits = 0;
   }
   else {
       c = (int)(fmod (Work, 10))+'0';
       modf (Work/10, &Work);
       if (P > 0)
      P--;
       else
      Digits++;
   }
   dtoa (Work, P, Digits);
   *NumPtr++ = c;
    }
} /* dtoa */

/* Right- or left- justifies the formatted number in the string.
 * If the number is too large for the specified width, the number
 * field is filled with the asterisk character (*). */
static void Justify (void) {
    if ((WidthFlag & 0x7f) == '0')   /* check width */
   if (strlen (Num) > Width) {
       memset (Num, '*', Width);
       NumPtr = Num + Width;
       *NumPtr = '\0';
       return;
   }
    if (strlen (NumPtr) < Width) {
   if (Flags & Dash)    /* left justify */
       memset (NumPtr, ' ', Width - strlen (Num));
   else {         /* right justify */
       int n = Width - strlen (Num);
       char *p = Num;
       memmove (p + n, p, strlen (Num));
       memset (p, ' ', n);
   }
   NumPtr = Num + Width;
   *NumPtr = '\0';
    }
} /* Justify */

/* Reports an out of memory error and aborts the program. */
static void MemoryError (char *s) {
    fprintf (stderr, "Out of memory in function %s\n", s);
    exit (1);
} /* MemoryError */

/* Format 'm' type into the new format string t. All parameters (flags, width,
 * precision, size, and type) are stored in the respective variables.  */
static void mFormat (void) {
    double Work;
    /* Move the block pointer back where it belongs if there were width or
     * precision specifications in the argument list. */
    *SavePtr = '\0';
    if (PrecisionFlag == '*')
   RemoveBlockArg (int);
    if (WidthFlag & 0x80)
   RemoveBlockArg (int);

    if ((Num = malloc (128)) == NULL)
   MemoryError ("mFormat");
    NumPtr = Num;
    /* The next argument in the list is the number that is to be formatted.
     * This number will either be a float, a double, or a long double. The
     * input size modifier will tell us what type. Whatever type it is, it will
     * be copied into the double variable Work for us to work with. */
    if (Size == 'l')     Work = va_arg (OldArgPtr, double);
    else if (Size == 'L') Work = (double) va_arg (OldArgPtr, long double);
    else                  Work = (double) va_arg (OldArgPtr, float);
    Sign = (Work < 0) ? -1 : 1;
    if (!PrecisionFlag)
   Precision = 2;
    dtoa (floor (fabs (Work) * pow10 (Precision)),
     (Precision == 0) ? -1 : Precision, 0);
    *NumPtr = '\0';
    /* The number is formatted into Num. Precision was handled by the dtoa()
     * function. Add the sign and perform padding/justifying as necessary.
     * Determine the proper sign */
    if (Sign == -1)         Sign = '-';
    else if (Flags & Plus)  Sign = '+';
    else if (Flags & Blank) Sign = ' ';
    else                    Sign = '\0';

    if (Sign != '\0')      /* Place sign */
   if (Flags & Pound) {   /* trailing sign */
       *NumPtr++ = Sign;
       *NumPtr = '\0';
   }
   else {         /* leading sign */
       memmove (Num+1, Num, (NumPtr - Num));
       *Num = Sign;
       NumPtr++;
   }
    Justify ();
    /* Now re-allocate the string to add more characters and then append the
     * newly-formatted number to the string and release the memory taken by
     * the formatted number string. */
    if ((t = realloc (t, (tSize += strlen (Num)))) == NULL)
   MemoryError ("mFormat");
    strcat (t, Num);
    Tptr = t + strlen (t);
    free (Num);
} /* mFormat */

/* Determine the conversion type. For any type but 'm', simply copy the passed
 * argument to the new argument list and return.  */
static void ParseType (void) {
    switch (*Tptr++ = *Sptr++) {
   case 'd' :
   case 'i' :
   case 'o' :
   case 'u' :
   case 'x' :
   case 'X' :
   case 'c' :      /* in Turbo C, char is treated as int */
       if (Size == 'l') AddBlockArg(OldArgPtr, long);
       else             AddBlockArg(OldArgPtr, int);
       break;
   case 'f' :
   case 'e' :
   case 'g' :
   case 'E' :
   case 'G' :
       if (Size == 'l')      AddBlockArg(OldArgPtr, double);
       else if (Size == 'L') AddBlockArg(OldArgPtr, long double);
       else                  AddBlockArg(OldArgPtr, float);
       break;
   /* In Turbo C, pointers to all types are the same size */
   case 's' :
   case 'n' :
   case 'p' :
       if (Size == 'F')      AddBlockArg(OldArgPtr, char far *);
       else if (Size == 'N') AddBlockArg(OldArgPtr, char near *);
       else                  AddBlockArg(OldArgPtr, char *);
       break;
   case 'm' :
       mFormat ();      /* format 'm' type */
       break;
   default    :      /* anything else isn't defined */
       break;
    } /* switch */
} /* ParseType */

/* Parse a standard printf() format.  */
static void ParseFormat (void) {

    SavePtr = Tptr - 1;

    ParseFlags ();
    ParseWidth ();
    ParsePrecision ();
    ParseSize ();
    ParseType ();
} /* ParseFormat */

/* Copy the input format string to the new format string t, replacing all %m
 * formats with the formatted digit string. Arguments will be placed in the
 * ParamBlock structure, with the %m parameters removed. */
static void Formatter (char *s, va_list Arg) {
    Sptr = s;
    /* Allocate memory for new format string. */
    if ((t = malloc (tSize = strlen (s))) == NULL)
   MemoryError ("Formatter");
    Tptr = t;
    OldArgPtr = Arg;
    while (*Sptr !=  '\0')
   if ((*Tptr++ = *Sptr++) == '%')
       ParseFormat ();
    va_end (OldArgPtr);
    *Tptr = '\0';
} /* Formatter */

/* Allocate a block of memory for the modified argument list.  */
static void InitBlock (char *s) {
    int BlockSize = 0;

    while (*s != '\0')      /* count the '%' characters */
   if (*s == '%')      /* in the format string */
       BlockSize++;
    /* Multiply the number of '%' by the size of a long double, giving some
     * idea of the maximum required size of the new parameter block. It's
     * crude and implementation dependent, but it works. */
    BlockSize *= sizeof (long double);
    if ((NewBlock = malloc (BlockSize)) == NULL)
   MemoryError ("InitBlock");
    NewArgPtr = NewBlock;
} /* InitBlock */

/* Free the memory used by the modified argument list (if any) and the
 * modified format string.  */
static void FreeBlock (void) {
    free (t);
    free (NewBlock);
    NewBlock = t = NULL;
} /* FreeBlock */

/* These 6 routines are the only functions visible to the application program.
 * They are accessed though the printf, fprintf, sprintf, vprintf, vfprintf,
 * and vsprintf macros, respectively.  */
int AltPrintf (char *fmt, ...) {
    va_list Arg;
    int r;
    va_start (Arg, fmt);
    Formatter (fmt, NewArgPtr = Arg);
    r = vprintf (t, Arg);
    FreeBlock ();
    return r;
} /* AltPrintf */

int AltFprintf (FILE *f, char *fmt, ...) {
    va_list Arg;
    int r;
    va_start (Arg, fmt);
    Formatter (fmt, NewArgPtr = Arg);
    r = vfprintf (f, t, Arg);
    FreeBlock ();
    return r;
} /* AltFprintf */

int AltSprintf (char *Dest, char *fmt, ...) {
    va_list Arg;
    int r;
    va_start (Arg, fmt);
    Formatter (fmt, NewArgPtr = Arg);
    r = vsprintf (Dest, t, Arg);
    FreeBlock ();
    return r;
} /* AltSprintf */

int AltVprintf (char *fmt, va_list Arg) {
    int r;
    InitBlock (fmt);
    Formatter (fmt, Arg);
    r = vprintf (t, NewBlock);
    FreeBlock ();
    return r;
} /* AltVprintf */

int AltVfprintf (FILE *f, char *fmt, va_list Arg) {
    int r;
    InitBlock (fmt);
    Formatter (fmt, Arg);
    r = vfprintf (f, t, NewBlock);
    FreeBlock ();
    return r;
} /* AltVfprintf */

int AltVsprintf (char *Dest, char *fmt, va_list Arg) {
    int r;
    InitBlock (fmt);
    Formatter (fmt, Arg);
    r = vsprintf (Dest, t, NewBlock);
    FreeBlock ();
    return r;
} /* AltVsprintf */

#ifdef TESTING
#include "mfmt.h"
void main (void) {
    printf ("The national debt exceeds $%.0lm\n", (double)1000000000000.0);
}
#endif




[Figure 1:  Stack contents for printf() and vprintf() calls that output the
information contained in the Person structure.]

    struct Person {
        char *Name;
        int Age;
    } Jim = {"Jim Mischel", 29};

(a) printf ("%s is %d years old.\n", Jim.Name, Jim.Age);

/-------------------\
|    Jim.Age        |
|-------------------|
|    Jim.Name       |
|-------------------|
| &(format string)  |
|-------------------|
|   return address  |
\-------------------/

(b) vprintf ("%s is %d years old.\n", &Jim);

/-------------------\
|      &Jim         |
|-------------------|
| &(format string)  |
|-------------------|
|   return address  |
\-------------------/










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.