Split a C string on whitespace

The C function argc_argv in the following splits the string input into component parts at the whitespace. It returns -1 on any kind of input failure or other error. For example, given an input of

"  a b d def ghij-klm nop  qrst"
the following test code outputs
0: a
1: b
2: d
3: def
4: ghij-klm
5: nop
6: qrst

argc-argv.c

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

#define SKIP(p) while (*p && isspace (*p)) p++
#define WANT(p) *p && !isspace (*p)

/* Count the number of arguments. */

static int count_args (const char * input)
{
    const char * p;
    int argc = 0;
    p = input;
    while (*p) {
        SKIP (p);
        if (WANT (p)) {
            argc++;
            while (WANT (p))
                p++;
        }
    }
    return argc;
}

/* Copy each non-whitespace argument into its own allocated space. */

static int copy_args (const char * input, int argc, char ** argv)
{
    int i = 0;
    const char *p;

    p = input;
    while (*p) {
        SKIP (p);
        if (WANT (p)) {
            const char * end = p;
            char * copy;
            while (WANT (end))
                end++;
            copy = argv[i] = malloc (end - p + 1);
            if (! argv[i])
                return -1;
            while (WANT (p))
                *copy++ = *p++;
            *copy = 0;
            i++;
        }
    }
    if (i != argc) 
        return -1;
    return 0;
}

#undef SKIP
#undef WANT

/* This is the main routine. It splits "input" into pieces at
   whitespace, and returns the number of pieces in "argc_ptr", and the
   pieces themselves in "argv_ptr". */

static int argc_argv (const char * input, int * argc_ptr, char *** argv_ptr)
{
    int argc;
    char ** argv;

    argc = count_args (input);
    if (argc == 0)
        return -1;
    argv = malloc (sizeof (char *) * argc);
    if (! argv)
        return -1;
    if (copy_args (input, argc, argv) == -1)
        return -1;
    *argc_ptr = argc;
    *argv_ptr = argv;
    return 0;
}

/* This "main" is for testing purposes. */

int main ()
{
    int argc;
    char ** argv;
    int i;
    const char * test = "  a b d def ghij-klm nop  qrst";

    if (argc_argv (test, & argc, & argv) == -1) {
        fprintf (stderr, "Something went wrong.\n");
        exit (1);
    }
    for (i = 0; i < argc; i++)
        printf ("%d: %s\n", i, argv[i]);
    return 0;
}

Notes

To perfect this program,
Copyright © Ben Bullock 2009-2010. All rights reserved. For comments, questions, and corrections, please email Ben Bullock/ Privacy/ Disclaimer