Integers to English words in C As a beginner in C programming, I decided to write a function words that, given an unsigned integer n, puts the English representation in words of n into the character array dest.

This code is quite difficult to follow due to its complexity. If I hadn't written this code, it would take me a good while to understand it fully. What can I do to improve readability and reduce the unnecessary complexity?

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

const char *digits[] = { "one ", "two ", "three ", "four ", "five ", "six ", "seven ", "eight ", "nine " };
const char *tens[] = { "ten ", "twenty ", "thirty ", "forty ", "fifty ", "sixty ", "seventy ", "eighty ", "ninety " };
const char *teens[] = { "eleven ", "twelve ", "thirteen ", "fourteen ", "fifteen ", "sixteen ", "seventeen ", "eighteen ", "nineteen " };
const char *hundreds[] = { "thousand ", "million ", "billion " };

char *strprep(const char *prefix, char *dest)
{
    char *temp = strdup(dest);
    sprintf(dest, "%s%s", prefix, temp);
    free(temp);
    return dest;
}

void words(unsigned int n, char *dest)
{
    unsigned int l, r, m, d = 0, t = 0;
    char d_cpy[16];

    while(n)
    {
        r = n % 10;
        n = (n - r) / 10;
        m = d % 3;

        if(d &amp;&amp; !m)
        {
            if(r || n % 100)
            {
                strprep(hundreds[t], dest);
            }

            t++;
        }

        if(r)
        {
            switch(m)
            {
                case 0:
                    if(n % 10 != 1)
                    {
                        strprep(digits[r - 1], dest);
                    }
                    break;
                case 1:
                    if(l &amp;&amp; r == 1)
                    {
                        strprep(teens[l - 1], dest);
                    }
                    else
                    {
                        strprep(tens[r - 1], dest);
                    }
                    break;
                case 2:
                    strcpy(d_cpy, digits[r - 1]);
                    strprep(strcat(d_cpy, "hundred "), dest);
            }
        }

        l = r;
        d++;
    }

    if(!strlen(dest))
    {
        strcpy(dest, "zero");
    }
}


To provide some context, words() is called in main() (defined in the same file):

#include &lt;limits.h&gt;

int main(int argc, char *argv[])
{
    char *last;
    unsigned long int n;

    if(argc != 2)
    {
        puts("incorrect number of arguments!");
        printf("usage: %s [integer]\n", argv[0]);
        return -1;
    }

    n = strtoul(argv[1], &amp;last, 10);

    if(*last || !(*argv[1]) || n &gt; UINT_MAX)
    {
        puts("first argument must be a valid integer");
        return -1;
    }

    char buf[256];
    puts(words((unsigned int)n, buf));
}
Keep your line-length under control. Horizontal scrolling is death on readability.
strprep() is extremely inefficient, and even assuming the destination is big enough, you don't check whether allocation of your needless temporary buffer succeeds. Better to fix that:

char *strprep(const char *prefix, char *dest) {
    size_t prefix_len = strlen(prefix);
    memmove(dest + prefix_len, dest, strlen(dest) + 1);
    memcpy(dest, prefix, prefix_len);
    return dest;
}

Assuming a user-supplied Buffer contains an empty string for no reason is certainly an interesting decision. It violates the principle of least surprise though, and leads to Undefined Behavior if the assumption proves unfounded.
I won't try to decipher what your cryptic single-character variables in words() are for. Do everyone (especially yourself) a favor and invest some more into finding good names.
Using strlen() to decide whether a string is empty? That's an \$O(n)\$ call where a primitive direct check is sufficient:

if (!*dest) // dest is empty


Most optimizing compilers in hosted mode will succeed in lowering it to the above, but why write that much more and rely on it?
Actually, check for zero beforehand instead. No need to do so after failing to put it into words some other way.
Either your example code violates the contract of words(), or words is buggy. I suggest fixing words() to not assume the buffer is pre-filled with an empty string.
Consider merging multiple outputs into one request. Don't worry, due to adjacent string-literals being concatenated by the compiler, that need not result in one humungous string-literal.