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++

A Portable Library for Executing Child Processes


MAY93: A PORTABLE LIBRARY FOR EXECUTING CHILD PROCESSES

Matt is currently employed by the Allen-Bradley Company in Highland Heights, Ohio. He is responsible for the design and development of test software on VAX/VMS, UNIX, DOS, and other platforms. This article is an excerpt from his book, Building and Testing Portable Libraries in C, to be published by QED. You can reach him on CompuServe at 71620,2171.


One of the primary advantages of the C programming language is the ease with which code can be ported between various platforms. While the ANSI standard provides the vehicle for this portability, many useful system calls are specific to certain platforms, and consequently reduce portability. One group of system calls not bound to a standard involves the creation and execution of child processes. Despite their nonstandard, nonportable nature, these system calls provide a powerful tool for managing child processes. This article presents a library of functions that make the creation and execution of child processes portable across many different computer platforms.

Portability Issues

No matter how uniform a language design attempts to be, some functions simply can't be performed on certain systems. These limitations can be the result of either hardware or software constraints. For example, both VMS and UNIX are multitasking; thus, a parent process can continue to execute even while one of its children is executing. DOS, on the other hand, is not multitasking, so a parent and child cannot execute concurrently. Because of differences like these, it is unreasonable to expect one portable call to handle all possibilities for all conceivable systems.

However, it is possible to construct libraries that perform limited functionality common to many operating systems. In this context, the objective is to have the parent create the child process, wait for a return code from the child and then, based on the result of the return code, continue execution (capabilities shared by VMS, UNIX, and DOS). The intent is to make the function call look portable to the application program, regardless of the compiler used.

Child-process Concepts

UNIX, DOS, and VMS all require that sufficient memory exist before a child process can be created. If this isn't the case, the system commands will fail. Memory management is handled differently, depending on the system call invoked. For VMS and UNIX, creating a child process is handled by a combination of system calls. The UNIX fork() command copies the parent's memory space exactly into newly acquired memory space. At this point two identical copies of the parent exist. (There are times when it is desirable for two identical processes to execute concurrently.) To initiate a new, totally distinct child, the exec() family of commands are used. An exec() command actually overlays the process space of the duplicated parent with a new process, thus resulting in two distinct processes. There are many flavors of the exec() command and they will all be investigated later.

VMS uses a version of the fork() command called vfork(). The fork() command can be incredibly inefficient as it copies each memory address of the parent. No process is actually created by vfork(), just some setup functions necessary for a subsequent call to an exec().fork() can be used without an exec(), but vfork() must be combined with a call to exec(). Thus, when it is not necessary to have two identical processes running asynchronously, vfork() is more efficient. Some UNIX systems also have a vfork() command. However, one man page stated that the vfork() command will be eliminated at some point in the future because the fork() command is becoming much more efficient. The vfork() will be used for the remainder of this discussion because it is available on both UNIX and VMS and it satisfies the functionality needs of the portable library.

If the parent has no concern about the child's fate, it can proceed as if the child does not exist. However, if the parent needs a return code from the child, it must execute a wait() command. The wait() command will suspend the parent until the child completes and passes back a return value. (If a parent does not execute a wait(), it may still communicate with a child via pipes and mailboxes. These data structures have no bearing on the fork()/exec() and are beyond the scope of this article.)

The basic use of fork()/exec()/wait() is shown in pseudocode in Example 1. The format is a bit confusing at first. The call to fork() actually returns values at two different times. When fork() is initially called, it returns a 0 and enters the else part of the example where the exec() is invoked. If the exec() is successful, control is passed back to the point of the fork(), which now returns 1. The parent can then either ignore the child and continue on its way or execute a wait() command and suspend until the child completes.

Example 1: Pseudocode illustrating fork/exec/wait.

    if ( (status = fork()) ! = 0) {
        /* parent code */
        if (status < 0)
                 error (fork failed);
        if (wait (&child_status) == -1)
                 error (wait failed);
    } else {
        /* exec the child */
        if (exec () == -1)
                 error (exec failed);
    }

A UNIX exec() command can determine if a child-program image exists. Thus, if a child executable cannot be found, the exec() will fail at the point of the call. VMS cannot determine if the image exists. Therefore, the exec() will appear to succeed even though the image does not exist. The fact that the creation of the child process failed will not be reported until the status from the wait() command is inspected.

The DOS exec() command behaves differently than its counterparts on VMS and UNIX. Because DOS does not support multitasking, it is impossible for both the parent and the child to be running at the same time. The DOS exec() command terminates the parent when the child is created. When the exec() command is called, DOS actually overlays the parent process with that of the child. The only way the parent can regain control is if the exec() on the child fails. Once the child is running, the parent process cannot be recovered.

DOS also provides a facility for the parent to suspend itself while the child initiates, and then re-awaken when the child process completes. The commands to accomplish this are called spawn() commands. DOS must find enough space for the child to occupy, or the spawn() will fail. A sample spawn() format is shown in Example 2.

Example 2: Using spawn().

if ((status = spawn ()) == -1 )
     error (spawn failed);

The DOS spawnv() command requires less code to perform a function similar to the fork()/exec()/wait(). However, the fork()/exec()/wait() affords much more flexibility. The remainder of this discussion will focus on the VMS/UNIX exec() and the DOS spawn() family of commands.

Command Line and Environment Variables

Two types of parameters can be passed via the exec()/spawn() commands: mandatory and optional. The mandatory parameter is the argument list by which the child is called. The optional parameter is the environment-variable list. The child is called in the same way any C program is called from the command line. Recall that when accepting arguments from a command line, a program's main procedure looks like Example 3.

Example 3: Main procedure when accepting arguments from a command line.

    main (argc, argv, envp);
    int argc;
    char *argv [];
    char *envp[];

The environment variables (envp) are optional. Under normal circumstances, a child inherits most of the parent's environment attributes. Using the environment variables in the exec()/spawn() command allows the child to be initiated within a different environment. The variables that can be changed are HOME, TERM, PATH, and USER.

The Different Flavors of exec()/spawn()

In the previous section I mentioned that the use of the environment variable is optional. The manner in which the environment variable is utilized depends upon which exec()/spawn() command is used. The two primary categories are differentiated by the way the argument list is passed. The execv()/spawnv() command format is shown in Example 4(a) .

Example 4: (a) execv()/spawnv() command format; (b) execl() format; (c) execl() function call; (d) hard coding the command line; (e) calling getenv using execv(); (f) argument lists when executables are kept in another directory; (g) command to utilize path information; (h) command that allows parent to change environment variables of the child.

  (a)

  execv (argv[0], argv);
  spawnv(P_WAIT, argv[0], argv);

  (b)

  execl (command, command, arg 1, .... arg N, NULL);

  (c)

  execl ("command", "command", "-x", "-c", "-v", NULL);
  spawnl (P_WAIT, "command", "command", "-x", "-c", "-v", NULL);

  (d)

  char *argv[] = {"command", "-x", "-c", "-v", NULL};

  execv (argv[0], argv);
  spawnv (P_WAIT, argv[0], argv);

  (e)
  char *argv[] = {"getenv", NULL);

  execv(argv[0], argv);
  spawnv(P_WAIT, argv[0], argv);

  (f)

  char *argv[] = {"{test.exe}getenv", NULL};       /* for VMS */
  char *argv[] = {"/test/bin/getenv", NULL};       /* for UNIX */
  char *argv[] = {"C:\\bin\\getenv", NULL};        /* for DOS */

  (g)

  execvp(argv[0], argv);
  spawnvp(P_WAIT, argv[0], argv);

  (h)

  char *argv[] = {"getenv", NULL};

  char *envp[] = { "HOME = /test/home",
                   "TERM = vt100",
                   "PATH = /test/bin",
                   "USER = test"
                    NULL                       };

  execve(argv[0], argv, envp);
  spawnve(P_WAIT, argv[0], argv, envp);

The DOS command spawnv() has one more parameter than execv(). The Turbo C Reference Guide states that the first parameter passed with spawnv() specifies whether or not the parent should wait for the child to return, either P_WAIT or P_NOWAIT. A later note explains that P_NOWAIT is not supported (understandable because DOS cannot support two concurrent processes). This parameter is provided for future use; however, it still needs to be included.

In all flavors of execv()/spawnv() the argument list argv must be built and its pointer passed. (From this point on, any reference to an exec() command will cover the equivalent spawn() command.) The command argv[0] is the first parameter. Notice that the command is actually passed twice, once as a standalone parameter and then as the first member of the argument list.

The argument list can also be passed explicitly, that is, as hard-coded strings. This command is called execl() and its format is shown in Example 4(b). Again, notice that the command is passed twice, and the list is terminated by a null. For instance, Example 4(c) invokes the execl() function using command -x-c -v as the command line.

There are uses for the execl() family of commands; however, the hard-coding aspects of the command make it less flexible than the execv() commands. In fact, if you're intent on hard coding the command line, execv() can still be used; see Example 4(d) . Because of this, only the execv() functions will be discussed further.

When the execv() command is called, the current directory is searched for the executable specified by argv[0]. If it is not found, an error will result. If the executable resides in another directory, the entire path must be explicitly provided. To illustrate, a program called "getenv," which simply prints out the current value of the environment variables, is called by execv(); see Example 4(e).

The getenv executable must reside in the current directory or the command will fail. However, assume that all program executables are kept in a directory called [test.exe] (for VMS), /test/bin (for UNIX), or C:\bin (for DOS). The argument lists would look like Example 4(f).

Explicitly providing the path information is not always the most elegant solution. There is a way to take advantage of the execution paths UNIX and DOS provide. VMS does not provide a path, but a path variable called VAXC$PATH can be set. The command to utilize the path information is called execvp(), which looks similar to the regular execv(); see Example 4(g).

The only difference is that if the executable is not found in the current directory, the PATH environment variable is searched. In VMS, the VAXC$PATH variable can be set either at the command line or in the login.com file as define VAXC$PATH [test.exe].

The last version of execv() is called execve(). This command allows the parent to change the environment variables of the child; see Example 4(h). This command will create an environment for the child without changing the environment of the parent. All of these execv() commands have an execl() counterpart.

DOS also provides a command called spawnvpe(). This command combines the functionality of execve() and execvp(). VMS and UNIX do not provide this function, but code can be added to the library to include this functionality.

Building a Portable execv()

Because the execv() command is the basis for all of the other extensions, this command will be used as an illustration. The code for the complete library is in Listing One (page 90). The intent is to create a library command, called xexecv, that will handle all of the overhead involved in creating and executing a child process. In essence, the only code required in an application program should be: status = xexecv (argv[0], argv);.

This one line of code will fork() and exec() (in UNIX and VMS) or spawn() (in DOS), execute the command, and return the proper status.

The first order of business is to determine what platform the program is running on. This is defined in the header file, exec.h, presented in Listing Two (page 90). Portability issues can be addressed here, such as whether or not the platform is ANSI standard. Once this information is known, either defined on the compilation line or in the exec.h file itself, the ifdefs in the code can handle the portability issues.

After xexecv is called, the first ifdef determines whether or not the platform is DOS. If it is, only the spawnv() command is executed. If it is not DOS, the fork(), execv(), and wait() calls are made. In any event, the status--whether the actual child return status or an error code--is passed back to the calling program.

Special Portability Issues

Besides the fact that execv() and spawnv() are different commands, there are other portability issues to consider. One, the way VMS uses VAXC$PATH, has already been mentioned. Further portability concerns pertain to compiler/ operating-system bugs, return codes, and word alignment. As of this writing, there is a bug registered against the VMS command execvp(). The problem is that the proper value of VAXC$PATH is not returned. The way to get around this is to use the getenv() command to obtain the information. Then the path can be prepended to the command. The code workaround appears in the xexecvp function in the library. It is somewhat curious that the VMS command xexeclp does work properly.

Architecture also plays a role in the lack of portability. The program return codes for VMS and DOS differ from those of some of the UNIX platforms, at least the ones already ported to SUN/OS and HP/UX. These UNIX systems place the return code in the high-order byte, so, to conform with the other platforms, the contents need to be shifted as: child_status = child_status >> 8;.

The intent of the return codes poses a problem. VMS, UNIX, and DOS have different ways of interpreting returned information. This portability issue is dealt with in the error-handling section.

The three operating systems/compilers referenced by this article are not the only ones that can be incorporated into the library. Any operating system that supports the creation of child processes and conforms to the functionality discussed can be added.

Error Handling

Error handling is the responsibility of the calling routine, not the execv() library (which returns only status information). This status information can be either an error code or the status of a successfully invoked child process. The error codes are defined in the include file exec.h. It is up to the calling program to process the return status and report any anomalies. The error codes can be tailored to a specific application.

Sample Application

To illustrate how the execv() library works, a short example is presented. The example program is called child.c; see Listing Three (page 90). This short program tests each flavor of the execv() library: xexecv, xexecve, xexecvp. The data structure envp holds information that will be used to alter the environment variables. The routine convert(), which is presented in Listing Four (page 92), takes a (char*) command line as input and converts it into an (char**) argv structure. A malloc, used within the convert() routine, acquires the appropriate amount of memory for the argv structure. In this example, a command called getenv is converted into an argv list, and then is passed to the appropriate execv(). A status is passed back and checked by the routine check_status(). When the process is complete, the memory used by the argv structure is released.

The program getenv is presented in Listing Five (page 92) and will compile on all three of the target systems. The routine invokes the system command getenv, which displays the system's environment information.

To utilize the library in an application, simply compile the modules execv.c, child.c, and convert.c, and then link all three into an executable (in this case called "child"). Remember, either a compiler switch or an entry in the exec.h file must define the target platform.

Sample output from the program child is presented in Listing Six (page 92). Notice the output from the call to execve(). The environment variables have been altered and reflect the data defined in the data structure envp.

Conclusion

Despite the fact that C is a very portable language, portability is still a problem in some areas. System calls, such as the execv() and spawn() family of commands, can be specific to an individual platform. When used, these commands may obstruct portability. These problems can be resolved with code libraries that, when linked into application programs, free the programmer from dealing with these system concerns. With the vast number of platforms and compilers now available, it is helpful to have these libraries of reusable code ready at hand.



_A PORTABLE LIBRARY FOR EXECUTING CHILD PROCESSES_
by Matt Weisfeld


[LISTING ONE]
<a name="012b_0012">

#include <stdio.h>
#ifdef DOS
#include <process.h>
#include <stdlib.h>
#endif
#include "exec.h"

int _execv(char **argv)
{
    int status, child_status;

    /* fork off the child process */

    /* vfork returns values on two different occasions :
        1) It returns a 0 the first time it is called, before
            the exec functions is called.
        2) After the exec call is made, control is passed
           back to the parent at the point of the vfork.
    */
#ifdef DOS
    if ((child_status = spawnv(P_WAIT,argv[0],argv)) == -1) {
            return (BADEXEC);
    }
#else
    if ((status = vfork()) != 0) {
        /* after the exec, control is returned here */
        if (status < 0) {
            printf ("Parent: child failed\n");
            return (BADFORK);
        } else {
            printf ("Parent - Waiting for child\n");
            if ((status=wait(&child_status)) == -1) {
                printf ("Parent: wait failed\n");
                return (BADWAIT);
            }
        }
    } else {    /* if vfork returns a 0 */
        /* execute command after the initial vfork */
        printf ("Parent - Starting Child\n");
        if ((status=execv(argv[0], argv)) == -1) {
            printf ("Parent: execv on child failed\n");
            return (BADEXEC);
        }
    }
#endif

#ifdef UNIX
    child_status = child_status >> 8;
#endif
    return (child_status);
}
int _execvp(char **argv)
{
    /* fork off the child process */
    /* vfork returns values on two different occasions :
        1) It returns a 0 the first time it is called, before
            the exec functions is called.
        2) After the exec call is made, control is passed
           back to the parent at the point of the vfork.
    */
    int status, child_status;
    int pathlen,comlen;

    char *path;
    char *command;
    /* This code has to be here, if it is within the vfork domain, then any
       error return code will be interpreted as an error from the child. */

#ifdef VMS
    if ( (path = getenv("CHILD$PATH")) == NULL) {;
        printf ("Error: CHILD$PATH not defined\n");
        return(BADPATH);
    }
    pathlen = strlen (path);
    comlen  = strlen (argv[0]);

    if ( (command = malloc(pathlen+comlen+1)) == NULL) {
        printf ("Error: malloc failed\n");
        return(BADMALLOC);
    }
    strcpy (command, path);
    strcat (command, argv[0]);
    argv[0] = command;

    printf ("command = %s\n", command);
#endif

#ifdef DOS
    if ((child_status = spawnvp(P_WAIT,argv[0],argv)) == -1) {
            return (BADEXEC);
    }
#else
    if ((status = vfork()) != 0) {
        /* after the exec, control is returned here */
        if (status < 0) {
            printf ("Parent: child failed\n");
            return (BADFORK);
        } else {
            printf ("Parent - Waiting for child\n");

            if ((status=wait(&child_status)) == -1) {
                printf ("Parent: wait failed\n");
                return (BADWAIT);
            }
        }
    } else {    /* if vfork returns a 0 */
        /* execute command after the initial vfork */
        printf ("Parent - Starting Child\n");

#ifdef VMS
        if ((status=execv(argv[0], argv)) == -1) {
#else
        if ((status=execvp(argv[0], argv)) == -1) {
#endif
            printf ("Parent: execv on child failed\n");
            return (BADEXEC);
        }
    }
#endif

#ifdef UNIX
    child_status = child_status >> 8;
#endif
    return (child_status);
}
int _execve(char **argv, char **envp)
{
    int status, child_status;
    /* fork off the child process */

    /* vfork returns values on two different occasions :
        1) It returns a 0 the first time it is called, before
            the exec functions is called.
        2) After the exec call is made, control is passed
           back to the parent at the point of the vfork.
    */

#ifdef DOS
    if ((child_status = spawnve(P_WAIT,argv[0],argv,envp)) == -1) {
            return (BADEXEC);
    }
#else
    if ((status = vfork()) != 0) {
        /* after the exec, control is returned here */
        if (status < 0) {
            printf ("Parent: child failed\n");
            return (BADFORK);
        } else {
            printf ("Parent - Waiting for child\n");
            if ((status=wait(&child_status)) == -1) {
                printf ("Parent: wait failed\n");
                return (BADWAIT);
            }
        }
    } else {    /* if vfork returns a 0 */
        /* execute command after the initial vfork */
        printf ("Parent - Starting Child\n");

        if ((status=execve(argv[0], argv, envp)) == -1) {
            printf ("Parent: execve on child failed\n");
            return (BADEXEC);
        }
    }
#endif

#ifdef UNIX
    child_status = child_status >> 8;
#endif
    return (child_status);
}





<a name="012b_0013">
<a name="012b_0014">
[LISTING TWO]
<a name="012b_0014">

/* One of the following must be defined here or on the compile line
#define DOS 1
#define UNIX 1
*/

#define VMS 1

#define BADFORK   -1
#define BADEXEC   -2
#define BADWAIT   -3
#define BADPATH   -4
#define BADMALLOC -5





<a name="012b_0015">
<a name="012b_0016">
[LISTING THREE]
<a name="012b_0016">

#include <stdio.h>
#include <stdarg.h>
#include "exec.h"

char envp1[50] = "HOME=sys$sysdevice:[weisfeld]";
char envp2[50] = "TERM=vt100";
char envp3[50] = "PATH=sys$sysdevice:[weisfeld.exe]";
char envp4[50] = "USER=test";

int main()
{
    int status, child_status;

    char **argv;
    char *envp[6];

    envp[0] = envp1;
    envp[1] = envp2;
    envp[2] = envp3;
    envp[3] = envp4;
    envp[4] = 0;


    printf ("CALL EXECV\n");
    argv = (char *)convert("getenv");
    status = _execv(argv);
    check_status(status);
    free (argv);

    printf ("CALL EXECVE\n");
    argv = (char *)convert("getenv");
    status = _execve(argv, envp);
    check_status(status);
    free (argv);

    printf ("CALL EXECVP\n");
    argv = (char *)convert("getenv");
    status = _execvp(argv);
    check_status(status);
    free (argv);

    return;
}

check_status(status)
int status;
{
    switch(status) {
        case BADFORK:
            printf ("Error: bad fork\n");
        break;
        case BADEXEC:
            printf ("Error: bad exec\n");
        break;
        case BADWAIT:
            printf ("Error: bad wait\n");
        break;
        case BADPATH:
            printf ("Error: bad path\n");
        break;
        case BADMALLOC:
            printf ("Error: bad malloc\n");
        break;
        default:
            printf ("Child status = %d\n", status);
        break;
    }
}





<a name="012b_0017">
<a name="012b_0018">
[LISTING FOUR]
<a name="012b_0018">

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#define EXIT_NORMAL  1
#define EXIT_ERROR   5

/* convert line to 'c' like argv */
char **convert(char *ptr)
{
    static char buffer[100];
    char *bufptr;
    int offset = 0;
    int counter = 0;
    char **argv;

    strcpy (buffer, ptr);

    bufptr = &buffer;

    for (;;) {
        /* bypass white space */
        while (isspace(*bufptr)) {
            bufptr++;
        }
        /* if we have a null string break out of loop */
        if (*bufptr == '\0')
            break;
        counter++;
        /* continue until white space of string terminator is found */
        while ((!isspace(*bufptr)) && (*bufptr != '\0')) {
            bufptr++;
        }
    }
    /* get space for argument */

      if ((argv = (char **)calloc(counter+1, sizeof(char *))) == (char **) 0) {
        printf ("Error: no space available\n");
        return (EXIT_ERROR);
    }
    bufptr = &buffer;
    /* build argument */
    for (;;) {
        while (isspace(*bufptr)) {
            *bufptr = '\0';
            bufptr++;
        }
        if (*bufptr == '\0')
            break;
        *(argv + offset++) = bufptr;
        while ((!isspace(*++bufptr)) && (*bufptr != '\0'));
    }
    /* return all the arguments */
    return(argv);
}





<a name="012b_0019">
<a name="012b_001a">
[LISTING FIVE]
<a name="012b_001a">

#include <stdio.h>

#define EXIT_NORMAL 1

main()
{
    char  *logical;

    logical = getenv("HOME");
    printf ("HOME = %s\n", logical);
    logical = getenv("TERM");
    printf ("TERM = %s\n", logical);
    logical = getenv("PATH");
    printf ("PATH = %s\n", logical);
    logical = getenv("USER");
    printf ("USER = %s\n", logical);

    return(EXIT_NORMAL);
}




<a name="012b_001b">
<a name="012b_001c">
[LISTING SIX]
<a name="012b_001c">

CALL EXECV
Parent - Starting Child
HOME = sys$sysdevice:[weisfeld]
TERM = vt300-80
PATH = sys$sysdevice:[weisfeld.test]
USER = WEISFELD
Child status = 1
CALL EXECVE
Parent - Starting Child
HOME = sys$sysdevice:[weisfeld]
TERM = vt100
PATH = sys$sysdevice:[weisfeld.exe]
USER = test
Child status = 1
CALL EXECVP
command = [WEISFELD.EXE]getenv
Parent - Starting Child
HOME = sys$sysdevice:[weisfeld]
TERM = vt300-80
PATH = sys$sysdevice:[weisfeld.test]
USER = WEISFELD
Child status = 1






Copyright © 1993, Dr. Dobb's Journal


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.