views:

168

answers:

4

I need to store the following strings in 3 char * variables in C:

[foo]
[foo]:[bar]
[foo]:[bar]:[tat]

Each individual string such as "foo" is received at one point of time via a char* pointer, the corresponding output string is produced immediately before receiving the second string, and the total number of individual strings is known at all times.

There can be an arbitrary number of strings.

I wrote the following code to print the strings:

for(i=0;i<num;i++)
    {  
        for(j=0;j<=i;j++)
            {
                printf("[%s]",str[j]);
                if(j!=i)
                {
                    printf(":");
                }
                else
                {
                    printf("\n");
                }
            }       
    }   

But I am unable to figure out a way of storing the desired output strings in variables, other than writing a messy case-by-case function using strcat() & some additional self-defined functions.

Is there a way to do this, that looks as clean as simply printing the strings?

+1  A: 

You could use sprintf(). Using your code as an example:

for(i=0;i<num;i++)
{  
    char s[N];
    s[0] = '\0';
    for(j=0;j<=i;j++)
        {
            sprintf(s+strlen(s),"[%s]",str[j]);
            if(i!=j)
            {
                sprintf(s+strlen(s),":");
            }
            else
            {
                sprintf(s+strlen(s),"\n");
            }
        }      
    /* store 's' somewhere */
}   

Edit: I wouldn't recommend this solution for anything serious. But for a simple application where you just want to get things done it should do the job. Just have N large enough, or allocate it dynamically to be able to hold the largest string.

Oskar N.
Rather than having to constantly recalculate `strlen(s)`, you can keep a running pointer to the end of the string by doing `p += sprintf(p, "[%s]", str[j]);` (`sprintf()` returns the number of characters copied, not including the null terminator).
caf
+3  A: 

This is a memory-management-and-string-handling-in-C-are-tedious problem. If I'm right in thinking that for n input strings, you want n output strings, each longer than the last, I'd do this:

char **results = malloc(num * sizeof(char*));
if (results == 0) return ENOMEM;
int lastlen = 0;
for (int i = 0; i < num; ++i) {
    char *newresult = malloc(lastlen + strlen(str[i]) + 4);
    if (newresult == 0) { /* even more tedious freeing code goes here */ }
    results[i] = newresult;
    char *ptr = newresult;
    if (i != 0) {
        ptr += sprintf(ptr, "%s:", results[i-1])
    }
    sprintf(ptr, "[%s]", str[i])
    lastlen = strlen(newresult);
}

Or something like that. Obviously there's a good opportunity to write more than one function here, to separate the generation of these strings from the storing of them in arrays. There's also the usual argument how we can do better in the risk-of-buffer-overflow stakes than sprintf + mental arithmetic.

Edit: stole the use of sprintf from Oskar.

Another edit: example with asprintf, which is available on GNU and BSD. This has the tedious freeing code included, the success path is actually quite good. Returns a newly-allocated array on success, 0 on error. The caller is assumed to know num and therefore know how long the array will be: if that's no use then the array could be null-terminated.

char **results = malloc(num * sizeof(char*));
if (results != 0) {
    const char *last = "";
    const char *sep = "";
    for (int i = 0; i < num; ++i) {
        if (asprintf(results + i, "%s%s[%s]", last, sep, str[i]) == -1) break;
        last = str[i];
        sep = ":";
    }
    if (i == num) return results; // success!
    // asprintf failed, so the value of results[i] is undefined
    while (i != 0) free(results[--i]);
    free(results); 
}
return 0;
Steve Jessop
+1, I misread the question. My last point still stands, pass whatever you do through valgrind or your platform's equivalent, to check for those pesky buffer overflows.
Ninefingers
@Ninefingers: true. And on that score it's probably better to write a function which takes two strings as input, and returns a newly-allocated "%s:[%s]" as output, than mix it all up in the loop like I have. Easier to see that the buffer size is right that way, and then use your favourite checked-length string functions to implement. I prefer `strlcpy` and a check for truncation that panics loudly, over `strncpy`, but that's a separate argument...
Steve Jessop
You could use snprintf().
Nyan
@Nyan: I could, but silent truncation on error sucks, so it has to be `snprintf` and a check for truncation that panics loudly. `asprintf` would be good here if available.
Steve Jessop
+1  A: 

You have two distinct issues: ensuring you allocate enough space and also creating the string. There are lots of ways to balance the two, but using realloc() makes it pretty straightforward. Here's a solution that checks and, if needed, enlarges the buffer.

The trade-offs are basically code complexity vs performance, such as:

  • Calculating memory requirements and allocating once vs. doing it more simply (IMHO) in the loop.
  • Wasting memory by over-allocating vs wasting time by (re-)allocation frequently.
  • Using the simpler strcpy() vs. simpler usage with sprintf().

My version

char *joinCmds( char* str[], /* The strings to concatenate */
                int num )    /* the number of strings */
{
    int  len = 0, i, off = 0;
    char *out = NULL, sep;

    for(i=0; i<num; i++)
    {
        while (len < (off+4+strlen(str[i]))) {
            len += 16384;      /* Larger numbers wastes memory in for better performance */
            out = realloc( out, len );  /* Warning: some 'realloc()' take 3 parameters */
            /** Handle NULL (Out of Memory errors) */
        }
        sep  = (i < (num-1)) ? ':' : '\n';
        off += sprintf(&out[off], "[%s]%c", str[i], sep);
    }
    return out;
}
Carter Galle
why are you using 16384? I don't get what you mean by "Larger numbers wastes memory in for better performance".
Kedar Soparkar
A: 

a solution with strcpy; with sprintf its also easy.

char out[1000],*p=out; /* out with enough size */
int i=0;
/* num > 0 */
for(; i<num && (p+=strlen(strcpy(p,"]:["))) ; p+=strlen(strcpy(p,str[i++])) ) ;
strcpy(p,"]");
puts(out+2);