web-dev-qa-db-fra.com

En attente d'une chaîne

Quel est le moyen le plus efficace d’ajouter une chaîne C en utilisant le moins de mémoire possible?

J'essaie de reconstruire le chemin d'accès à un fichier dans une grande arborescence de répertoires.

Voici une idée de ce que je faisais auparavant:

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);
}

Cela semble un peu maladroit cependant.

Toute aide serait appréciée. Merci!

29
Ryan

La copie peut difficilement être évitée si vous la souhaitez dans le même bloc de mémoire. Si le bloc alloué est suffisamment volumineux, vous pourriez utiliser memmove pour décaler la chaîne d'origine de la longueur de ce que vous voulez ajouter, puis copier celle-ci au début, mais je doute que ce soit moins "maladroit". Cela vous permettrait toutefois d'économiser de la mémoire supplémentaire (encore une fois, étant donné que le bloc d'origine dispose de suffisamment d'espace libre pour les deux).

Quelque chose comme ça:

#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, strlen(s) + 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;
}
13
Eli Bendersky

Si vous n'avez pas besoin que la chaîne soit stockée dans l'ordre, mais que semble seulement soit dans l'ordre, utilisez un objet appelé "corde". (Il est composé de beaucoup de "chaîne", voir.)

Je crois que c'est fondamentalement un vecteur (en termes C, un tableau) de struct { char *begin; char *end };

En C++, il implémente toutes les fonctions std :: string. En C, vous devez écrire (ou obtenir une bibliothèque de) fonctions de remplacement pour toutes les fonctions strxxx ().

Ce que la "corde" ferait pour ajouter une chaîne à une autre chaîne est simplement d'insérer un nouveau couple début/fin pointant vers le nouveau morceau de chaîne. Il peut également être nécessaire de copier le nouveau morceau de chaîne, s'il s'agit d'un pointeur temporaire. Ou il peut simplement devenir propriétaire de la chaîne s'il s'agit d'une chaîne allouée.

Une corde est très bonne pour les grosses cordes. Mais tout ce qui dépasse 8 Ko est plus rapide à gérer avec memmove et memcpy.

7
Zan Lynx

sprintf () n'est généralement pas "rapide". Puisque vous savez que memmove () est en attente deux fois, ce serait probablement préférable pour la vitesse.

Si vous allouez les chaînes avec malloc () à l'origine, vous pouvez envisager d'utiliser realloc () pour redimensionner les tableaux de caractères afin qu'ils puissent contenir la nouvelle chaîne.

   char* p = malloc( size_of_first_string );
   ...
   p = realloc( p, size_of_first_string + size_of_prepended_string + 1 );
   memmove( p + size_of_prepended_string, p, size_of_first_string );
   memmove( p, prepended_string, size_of_prepended_string );
3
Jay

Vous pouvez grimper en haut de l’arborescence en conservant les noms au fur et à mesure, puis les coller tous en même temps. Au moins, vous ne faites pas de copies inutiles en appuyant sur l'avant.

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;
1
Joel

Je suis peut-être confus, mais je pense qu'un préfixe est identique à un ajout de chaînes échangé. Ainsi, au lieu de prepending "Hello" à "World", la chaîne "World" peut être ajoutée à "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");
}

En outre, le principal gaspillage de temps d'exécution consiste à trouver la fin d'une chaîne. Si les chaînes étaient stockées avec la longueur, la fin pourrait être calculée plus rapidement.

1
Thomas Matthews

Vous pouvez maintenir la chaîne à partir de la fin. Puisque vous semblez déjà connaître le maxSize ...

Donc, fondamentalement, si le fichier était initialement (foo.txt)

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

Maintenant, si vous ajoutez un répertoire parent a/il ressemblera à

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

Donc, le code ressemblera à quelque chose (il peut y avoir des bugs, mais vous avez l’idée).

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--;
    }
} 

Puisque je suppose que nous pouvons nous attendre à ce que parent_dir soit petit, il devrait être correct de l'examiner deux fois. Si vous voulez contourner la chaîne de fichier, vous pouvez simplement utiliser file+lastEmpty+1.

1
Aryabhatta

Je laisse un tampon à gauche et à droite du tableau. Vous devez tenir deux index mais si vous devez le faire beaucoup de fois (autrement, il n'y aurait aucun problème d'efficacité), cela le gênerait. Les deux index que je propose d’être] s; e], l’un inclus et l’autre non:

 #define BUFSIZE 256
 #define LEFTBUF 20
 struct mstring
 {
   char * string;
   unsigned s;
   unsigned e;
  }
  void checkbuf(struct mstring *value, int newstringlen, char   leftorright)
  {
  //have fun here
  }
  char * concat (struct mstring * value, char * str)
  {
       checkbuf(value, strlen(value,str), 'r');
       int i=0;
       while (str[i])
            value->string[value->e++]=str[i++];
   }
   char * set(struct mstring * value, char * str)
   {
        value->e=LEFTBUF;
        value->s=LEFTBUF;
        concat( value,str);

   }

  char * prepend (struct mstring * value, char * str)
  {
       checkbuf(value, strlen(value,str), 'l');
       int i=strlen(value,str)-1;
       while (i>=0)
            value->string[--value->s]=str[i--];
   }
  int main()
  {
      struct mstring * mystring= (struct mstring *) malloc(sizeof(struct mstring) );
      mystring->string=(char*)malloc(sizeof(char)*BUFSIZE);
      set( mystring,"World");
      prepend(mystring,"Hallo")

  }

alors vous devez préparer une fonction pour remplir les sous-chaînes ... 

0
jurhas

Cette solution n'a pas plus de copie que nécessaire. Cela nécessite un strlen. Par conséquent, si l'extraction du nom de répertoire peut renvoyer le nombre d'octets copiés ou si vous pouvez précalculer la longueur de la chaîne de répertoire parent, vous pouvez l'optimiser.

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;
}
0
David Gladfelter