Question

I'm having difficulty in generating a string of the form "1,2,3,4,5" to pass to a command line program.

Here's what I have tried:

int N=100;
char list[200];
for (i=0; i<2*N; i+=2) {
    char tmp;
    sprintf(tmp,'%d', i);
    strcpy(list[i], tmp);
    strcpy(list[i+1], ',');
}

Edit: I don't feel this question is a duplicate as it is more to do with appending strings into a list and managing that memory and than literally just putting a comma between to integers.

Was it helpful?

Solution

The following code will do what you need.

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

char* CommaSeparatedListOfIntegers(const int N)
{
    if (N < 1)
        return NULL;
    char* result = malloc(1 + N*snprintf(NULL, 0, "%d,", N));
    char* p = result;
    for (int i = 1; i <= N; i++)
        p += sprintf(p, "%d,", i);
    *(p-1) = '\0';
    return result;
}

Note that the function returns a heap allocated block of memory that the caller is responsible for clearing up.

Some points of note:

  • We put a crude upper bound on the length of each number when converted to text. This does mean that we will over allocate the block of memory, but not by a massive amount. If that is a problem for you then you can code a more accurate length. That would involve looping from 1 to N and calling snprintf for each value to determine the required length.
  • Note that we initially write out a comma after the final value, but then replace that with the null-terminator.

OTHER TIPS

Let's forget about writing strings for the moment and write a function that just prints that list to the screen:

int range_print(int begin, int end, const char *sep)
{
    int len = 0;
    int i;

    for (i = begin; i < end; i++) {
        if (i > begin) {
            len += printf("%s", sep);
        }
        len += printf("%d", i);
    }

    return len;    
}

You can call it like this:

range_print(1, 6, ", ");
printf("\n");

The function does not write a new-line character, so we have to do that. It prints all numbers and a custom separator before each number after the first. The separator can be any string, so this function also works if you want to separate your numbers with slashes or tabs.

The function has printf semantics, because it returns the number of characters written. (That value is often ignored, but it can come in handy, as we'll see soon.) We also make the upper bound exclusive, so that in order to print (1, 2, 3, 4, 5) you have tp pass 1 and 6 as bounds.

We'll now adapt this function so that it writes to a string. There are several ways to do that. Let's look at a way that works similar to snprintf: It should tabe a pre-allocated char buffer, a maximum length and it should return the number of characters written or, if the output doesn't fit, the number of characters that would have been written had the buffer been big enough.

int range(char *buf, int n, int begin, int end, const char *sep)
{
    int len = 0;
    int m, i;

    for (i = begin; i < end; i++) {
        m = snprintf(buf, n, "%s%d", 
            (i > begin) ? sep : "", i);
        len += m;
        buf += m;
        n -= m;
        if (n < 0) n = 0;
    }

    return len;    
}

This function is tricky because it has to keep track of the number of characters written and of the free buffer still available. It keeps printing after the buffer is full, which is a bit wasteful in terms of performace, but it is legal to call snprintf with a buffer size of zero, and that way we keep the semantics tidy.

You can call this function like this:

char buf[80];
range(buf, sizeof(buf), 1, 6, ", ");    
printf("%s\n", buf);

That means that we need to define a buffer that is large enough. If the range of numbers is large, the string will be truncated. We might therefore want a function that allocates a string for us that is long enough:

char *range_new(int begin, int end, const char *sep, int *plen)
{
    int len = (end - begin - 1) * strlen(sep) + 1;
    char *str;
    char *p;
    int i;

    for (i = begin; i < end; i++) {
        len += snprintf(NULL, 0, "%d", i);
    }

    str = malloc(len);
    if (str == NULL) return NULL;

    p = str;
    for (i = begin; i < end; i++) {
        if (i > begin) p += sprintf(p, "%s", sep);
        p += sprintf(p, "%d", i);
    }

    if (plen) *plen = len - 1;
    return str;
}

This function needs two passes: in the first pass, we determine how much memory we need to store the list. Next, we allocate and fill the string. The function returns the allocated string, which the user has to free after use. Because the return value is already used, we lose the information on the string length. An additional argument, a pointer to int, may be given. If it is not NULL, the length will be stored.

This function can be called like this.

char *r;
int len;

r = range_new(1, 6, ", ", &len);
printf("%s (%d)\n", r, len);
free(r);

Note that the same can be achieved by calling our old range function twice:

char *r;
int len;

len = range(NULL, 0, 1, 6, ", ");
r = malloc(len + 1);
range(p, len + 1, 1, 6, ", ");
printf("%s (%d)\n", r, len);
free(r);

So, pick one. For short ranges, I recommend the simple range function with a fixed-size buffer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top