views:

659

answers:

7

What is the most efficient way to prepend to a C string, using as little memory as possible?

I am trying to reconstruct the path to a file in a large directory tree.

Here's an idea of what I was doing before:

char temp[LENGTH], file[LENGTH];
file = some_file_name;

while (some_condition) {
    parent_dir = some_calculation_that_yields_name_of_parent_dir;
    sprintf(temp, "%s/%s", parent_dir, file);
    strcpy(file, temp);
}

This seems a bit clunky though.

Any help would be appreciated. Thanks!

+1  A: 

Copying can hardly be avoided if you want it in the same memory chunk. If the allocated chunk is large enough you could use memmove to shift the original string by the length of what you want to prepend and then copy that one into the beginning, but I doubt this is less "clunky". It would however save you extra memory (again, granted that the original chunk has enough free space for them both).

Something like this:

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


/* Prepends t into s. Assumes s has enough space allocated
** for the combined string.
*/
void prepend(char* s, const char* t)
{
    size_t len = strlen(t);
    size_t i;

    memmove(s + len, s, len + 1);

    for (i = 0; i < len; ++i)
    {
        s[i] = t[i];
    }
}


int main()
{
    char* s = malloc(100);
    strcpy(s, "file");
    prepend(s, "dir/");

    printf("%s\n", s);
    return 0;
}
Eli Bendersky
@Ryan: updated answer
Eli Bendersky
That makes sense. Instead of the for loop, couldn't you use a memcpy?
Ryan
@Ryan - yes, you could
Eli Bendersky
prepend() could be optimized more by using C99 "restrict" qualifiers on s and t.
Zan Lynx
+1  A: 

You could maintain the string starting from the end. Since you seem to know the maxSize already...

So basically if file initially was (foo.txt)

[] [] [] [] [] [f] [o] [o] [.] [t] [x] [t] [\0]
             ^
             |
          lastEmpty           

Now if you add a parent dir a/ it will look like

[] [] [] [a] [/] [f] [o] [o] [.] [t] [x] [t] [\0]
       ^      
       |      
    lastEmpty           

So the code will look something like (there might be bugs, but you get the idea).

char temp[LENGTH], file[LENGTH]; 
int lastEmpty = put_at_end(some_file_name, file);  
// lastEmpty points to right most empty slot

while (some_condition) { 
    parent_dir = some_calculation_that_yields_name_of_parent_dir; 

    int len = strlen(parent_dir);
    char *tmp = parent_dir + len -1;

    while (lastEmpty > 0) {
        file[lastEmpty] = *tmp;
        lastEmpty --;
        tmp--;
    }
} 

Since I suppose we could expect parent_dir to be small, going over it twice should be ok. If you want to pass around the file string, you can just use file+lastEmpty+1.

Moron
A: 

This solution has no more copying than necessary. It does require one strlen, so if the directory name retrieval can return the number of bytes copied or if you can precalculate the parent dir string length, you can optimize that away.

void GetFilename(char *pFile)
{
    strcpy(pFile, "someFile");
}

void GetParentDir(char *pDir)
{
    strcpy(pDir, "/parentdir");
}

int _tmain(int argc, _TCHAR* argv[])
{

    char path[1024];
    GetParentDir(path);
    int dirSize = strlen(path);
    path[dirSize] = '/';
    GetFilename(path + dirSize + 1);
    printf(path);
    return 0;
}
David Gladfelter
+2  A: 

sprintf() is generally not 'fast'. Since you know it's pre-pending memmove() twice would probably be preferable for speed.

If you're allocating the strings with malloc() originally you might consider using realloc() to resize the character arrays so they can contain the new string.

   char* p = malloc( size_of_first_string );
   ...
   p = realloc( p, size_of_first_string + size_of_prepended_string );
   memmove( p + size_of_prepended_string, p, size_of_first_string );
   memmove( p, prepended_string, size_of_prepended_string );
Jay
memcpy() is often much faster if you *know* the source and destination do not overlap. So it might be worth checking the realloc return and use memcpy if it returned a different pointer.
Zan Lynx
+1  A: 

You can climb to the top of the directory tree keeping the names as you go along, then paste the names together all at once. At least then you aren't doing unnecessary copies by pushing onto the front.

int i = 0;
int j;

char temp*[MAX_DIR_DEPTH], file[LENGTH];

while (some_condition) {
    temp[i++] = some_calculation_that_yields_name_of_parent_dir;        
}

char *pCurrent = file;    
for( j = i-1; j > 0; j-- )
{
    strcpy(pCurrent, temp[j]);
    pCurrent += strlen(temp[j]);
    *pCurrent++ = '\';
}
strcpy(pCurrent, filename);
*pCurrent = 0;
Joel
A: 

Perhaps I'm confused, but I believe that a prepend is the same as appending with the strings swapped. So instead of prepending "Hello" to "World", the string "World" can be appended to "Hello":

const char world[] = "World";
const char hello[] = "Hello";

// Prepend hello to world:
const unsigned int RESULT_SIZE = sizeof(world) + sizeof(hello) + 2 * sizeof('\0');
char * result = malloc(RESULT_SIZE);
if (result)
{
  strcpy(result, hello);
  strcat(result, world);
  puts("Result of prepending hello to world: ");
  puts(result);
  puts("\n");
}

Also, the main waste of execution time is finding the end of a string. If the strings were stored with the length, the end could be calculated faster.

Thomas Matthews
Your idea is right, but it doesn't work very well in this case because there are more than two strings. I need to iteratively prepend to a string. If you put your strcpy and strcat lines in a while loop, on the second iteration you'd have to either create a new char array or move what was stored in "result" so that you had room to prepend.
Ryan
+1  A: 

If you don't need the string to be stored in order, but only appear to be in order, then use a thing called a "rope." (It's made of lots of "string", see.)

I believe it's basically a vector (in C terms, an array) of struct { char *begin; char *end };

In C++ it implements all the std::string functions. In C you'd need to write (or get a library of) replacement functions for all the strxxx() functions.

What the "rope" would do to prepend a string to another string is simply insert a new begin,end pair pointing at the new piece of string. It might also have to copy the new piece of string, if it's a temporary pointer. Or it can just take ownership of the string if it's an allocated string.

A rope is very good for large strings. But anything under about 8 KB is faster to handle with memmove and memcpy.

Zan Lynx
+1 I like this suggestion. Even if what is needed eventually is an array of chars, if is easy at the end to compute the final length of the rope and to allocate a right-sized buffer (and to copy **once**).
Pascal Cuoq