web-dev-qa-db-fra.com

C/C++ Comment copier un tableau de caractères multidimensionnel sans boucles imbriquées?

Je recherche un moyen intelligent de copier un tableau de caractères multidimensionnel vers une nouvelle destination. Je veux dupliquer le tableau de caractères parce que je veux éditer le contenu sans changer le tableau source.

Je pourrais créer des boucles imbriquées pour copier chaque caractère à la main, mais j'espère qu'il existe une meilleure solution.

Mise à jour:

Je n'ai pas la taille de la dimension 2. level. Donné est seulement la longueur (lignes).

Le code ressemble à ceci:

char **tmp;
char **realDest;

int length = someFunctionThatFillsTmp(&tmp);

//now I want to copy tmp to realDest

Je cherche une méthode qui copie toute la mémoire de tmp dans la mémoire libre et pointe le realDest vers elle.

Mise à jour 2:

someFunctionThatFillsTmp () est la fonction credis_lrange () de la bibliothèque Redis C credis.c .

À l'intérieur de la lib tmp est créé avec:

rhnd->reply.multibulk.bulks = malloc(sizeof(char *)*CR_MULTIBULK_SIZE)

Mise à jour 3:

J'ai essayé d'utiliser memcpy avec ces lignes:

int cb = sizeof(char) * size * 8; //string inside 2. level has 8 chars
memcpy(realDest,tmp,cb);
cout << realDest[0] << endl;

prints: mystring

Mais je reçois un: signal reçu du programme: EXC_BAD_ACCESS}

21
dan

Vous pouvez utiliser memcpy .

Si la taille du tableau multidimensionnel est donnée au moment de la compilation, c'est-à-dire mytype myarray[1][2], un seul appel memcpy est nécessaire

memcpy(dest, src, sizeof (mytype) * rows * coloumns);

Si, comme vous l'avez indiqué, le tableau est alloué dynamiquement, vous devez connaître la taille des deux dimensions, car la mémoire utilisée dans le tableau ne se trouvera pas dans un emplacement contigu, ce qui signifie que memcpy devra être utilisé plusieurs fois.

Étant donné un tableau 2d, la méthode pour le copier serait la suivante:

char** src;
char** dest;

int length = someFunctionThatFillsTmp(src);
dest = malloc(length*sizeof(char*));

for ( int i = 0; i < length; ++i ){
    //width must be known (see below)
    dest[i] = malloc(width);

    memcpy(dest[i], src[i], width);
}

Étant donné que, d'après votre question, il semble que vous ayez affaire à un tableau de chaînes, vous pouvez utiliser strlen pour trouver la longueur de la chaîne (elle doit être terminée par null).

Dans ce cas, la boucle deviendrait

for ( int i = 0; i < length; ++i ){
    int width = strlen(src[i]) + 1;
    dest[i] = malloc(width);    
    memcpy(dest[i], src[i], width);
}
33
Yacoby

Lorsque vous avez un pointeur sur un pointeur en C, vous devez savoir comment les données vont être utilisées et mises en mémoire. Maintenant, le premier point est évident, et vaut pour toute variable en général: si vous ne savez pas comment une variable va être utilisée dans un programme, pourquoi l’avoir? :-) Le deuxième point est plus intéressant.

Au niveau le plus élémentaire, un pointeur sur le type T pointe sur one object de type T. Par exemple:

int i = 42;
int *pi = &i;

Maintenant, pi pointe sur un int. Si vous le souhaitez, vous pouvez faire pointer un pointeur vers le premier de nombreux objets de ce type:

int arr[10];
int *pa = arr;
int *pb = malloc(10 * sizeof *pb);

pa pointe maintenant vers la première d'une séquence de 10 (contiguës) int valeurs, et en supposant que malloc() réussit, pb pointe vers la première d'une autre série de 10 (à nouveau contiguë) ints.

La même chose s'applique si vous avez un pointeur sur un pointeur:

int **ppa = malloc(10 * sizeof *ppa);

En supposant que malloc() réussisse, vous avez maintenant ppa qui pointe vers la première d'une séquence de 10 valeurs int * contiguës.

Alors, quand tu fais:

char **tmp = malloc(sizeof(char *)*CR_MULTIBULK_SIZE);

tmp pointe vers le premier objet char * dans une séquence de CR_MULTIBULK_SIZE tels objets. Chacun des pointeurs ci-dessus n'est pas initialisé, donc tmp[0] à tmp[CR_MULTIBULK_SIZE-1] contient tous des déchets. Une façon de les initialiser serait de malloc() les:

size_t i;
for (i=0; i < CR_MULTIBULK_SIZE; ++i)
    tmp[i] = malloc(...);

Le ... ci-dessus est la taille de la ith donnée souhaitée. Il peut s'agir d'une constante ou d'une variable, dépendant de i, de la phase de la lune, d'un nombre aléatoire ou de tout autre facteur. Le point principal à noter est que vous avez des appels CR_MULTIBULK_SIZE à malloc() dans la boucle et que bien que chaque malloc() vous renvoie un bloc de mémoire contigu, la contiguïté n’est pas garantie entre les appels malloc(). En d'autres termes, il n'est pas garanti que le deuxième appel malloc() renvoie un pointeur qui commence là où les données de la précédente malloc() se sont terminées.

Pour rendre les choses plus concrètes, supposons que CR_MULTIBULK_SIZE est égal à 3. En images, vos données pourraient ressembler à ceci:

     +------+                                          +---+---+
tmp: |      |--------+                          +----->| a | 0 |
     +------+        |                          |      +---+---+
                     |                          |
                     |                          |
                     |         +------+------+------+
                     +-------->|  0   |  1   |  2   |
                               +------+------+------+
                                   |      |
                                   |      |    +---+---+---+---+---+
                                   |      +--->| t | e | s | t | 0 |
                            +------+           +---+---+---+---+---+
                            |
                            |
                            |    +---+---+---+
                            +--->| h | i | 0 |
                                 +---+---+---+

tmp pointe sur un bloc contigu de 3 valeurs char *. Le premier des pointeurs, tmp[0], pointe vers un bloc contigu de 3 valeurs char. De même, tmp[1] et tmp[2] pointent vers 5 et 2 chars respectivement. Mais la mémoire désignée par tmp[0] à tmp[2] n'est pas contiguë dans son ensemble.

Puisque memcpy() copie la mémoire contiguë, ce que vous voulez faire ne peut pas être fait par un memcpy(). De plus, vous devez savoir comment chaque tmp[i] a été attribué. Donc, en général, ce que vous voulez faire nécessite une boucle:

char **realDest = malloc(CR_MULTIBULK_SIZE * sizeof *realDest);
/* assume malloc succeeded */
size_t i;
for (i=0; i < CR_MULTIBULK_SIZE; ++i) {
    realDest[i] = malloc(size * sizeof *realDest[i]);
    /* again, no error checking */
    memcpy(realDest[i], tmp[i], size);
}

Comme ci-dessus, vous pouvez appeler memcpy() à l'intérieur de la boucle, de sorte que vous n'avez pas besoin de boucle imbriquée dans votre code. (memcpy() est très probablement implémenté avec une boucle, donc l'effet est comme si vous aviez des boucles imbriquées.) 

Maintenant, si vous aviez un code comme:

char *s = malloc(size * CR_MULTIBULK_SIZE * sizeof *s);
size_t i;
for (i=0; i < CR_MULTIBULK_SIZE; ++i)
    tmp[i] = s + i*CR_MULTIBULK_SIZE;

C'est-à-dire que vous avez alloué de l'espace contigu à tous les pointeurs d'un appel malloc(), vous pouvez alors copier toutes les données sans boucle dans votre code:

size_t i;
char **realDest = malloc(CR_MULTIBULK_SIZE * sizeof *realDest);
*realDest = malloc(size * CR_MULTIBULK_SIZE * sizeof **realDest);
memcpy(*realDest, tmp[0], size*CR_MULTIBULK_SIZE);

/* Now set realDest[1]...realDest[CR_MULTIBULK_SIZE-1] to "proper" values */
for (i=1; i < CR_MULTIBULK_SIZE; ++i)
    realDest[i] = realDest[0] + i * CR_MULTIBULK_SIZE;

La réponse simple à ce qui précède est simple: si vous aviez plusieurs malloc() pour allouer de la mémoire à tmp[i], vous aurez besoin d’une boucle pour copier toutes les données.

8
Alok Singhal

Vous pouvez simplement calculer la taille globale du tableau puis utiliser memcpy pour le copier. 

int cb = sizeof(char) * rows * columns;
memcpy (toArray, fromArray, cb);

Modifier: de nouvelles informations dans la question indiquent que le nombre de lignes et de colonnes du tableau n'est pas connu et que le tableau peut être irrégulier. Par conséquent, memcpy peut ne pas être une solution. 

6
John Knoeller

Permet d’explorer quelques possibilités pour ce qui se passe ici:

int main(int argc; char **argv){
  char **tmp1;         // Could point any where
  char **tmp2 = NULL;
  char **tmp3 = NULL;
  char **tmp4 = NULL;
  char **tmp5 = NULL;
  char **realDest;

  int size = SIZE_MACRO; // Well, you never said
  int cb = sizeof(char) * size * 8; //string inside 2. level has 8 chars

  /* Case 1: did nothing with tmp */
  memcpy(realDest,tmp,cb);  // copies 8*size bytes from WHEREEVER tmp happens to be
                          // pointing. This is undefined behavior and might crash.
  printf("%p\n",tmp[0]);    // Accesses WHEREEVER tmp points+1, undefined behavior, 
                            // might crash.
  printf("%c\n",tmp[0][0]); // Accesses WHEREEVER tmp points, undefined behavior, 
                            // might crash. IF it hasn't crashed yet, derefernces THAT
                            // memory location, ALSO undefined behavior and 
                            // might crash


  /* Case 2: NULL pointer */
  memcpy(realDest,tmp2,cb);  // Dereferences a NULL pointer. Crashes with SIGSEGV
  printf("%p\n",tmp2[0]);    // Dereferences a NULL pointer. Crashes with SIGSEGV
  printf("%c\n",tmp2[0][0]); // Dereferences a NULL pointer. Crashes with SIGSEGV


  /* Case 3: Small allocation at the other end */
  tmp3 = calloc(sizeof(char*),1); // Allocates space for ONE char*'s 
                                  // (4 bytes on most 32 bit machines), and 
                                  // initializes it to 0 (NULL on most machines)
  memcpy(realDest,tmp3,cb);  // Accesses at least 8 bytes of the 4 byte block: 
                             // undefined behavior, might crash
  printf("%p\n",tmp3[0]);    // FINALLY one that works. 
                             // Prints a representation of a 0 pointer   
  printf("%c\n",tmp3[0][0]); // Derefereces a 0 (i.e. NULL) pointer. 
                             // Crashed with SIGSEGV


  /* Case 4: Adequate allocation at the other end */
  tmp4 = calloc(sizeof(char*),32); // Allocates space for 32 char*'s 
                                  // (4*32 bytes on most 32 bit machines), and 
                                  // initializes it to 0 (NULL on most machines)
  memcpy(realDest,tmp4,cb);  // Accesses at least 8 bytes of large block. Works.
  printf("%p\n",tmp3[0]);    // Works again. 
                             // Prints a representation of a 0 pointer   
  printf("%c\n",tmp3[0][0]); // Derefereces a 0 (i.e. NULL) pointer. 
                             // Crashed with SIGSEGV


  /* Case 5: Full ragged array */
  tmp5 = calloc(sizeof(char*),8); // Allocates space for 8 char*'s
  for (int i=0; i<8; ++i){
    tmp5[i] = calloc(sizeof(char),2*i); // Allocates space for 2i characters
    tmp5[i][0] = '0' + i;               // Assigns the first character a digit for ID
  }
  // At this point we have finally allocated 8 strings of sizes ranging 
  // from 2 to 16 characters.
  memcpy(realDest,tmp5,cb);  // Accesses at least 8 bytes of large block. Works.
                             // BUT what works means is that 2*size elements of 
                             // realDist now contain pointer to the character 
                             // arrays allocated in the for block above/
                             //
                             // There are still only 8 strings allocated
  printf("%p\n",tmp5[0]);    // Works again. 
                             // Prints a representation of a non-zero pointer   
  printf("%c\n",tmp5[0][0]); // This is the first time this has worked. Prints "0\n"
  tmp5[0][0] = '*';
  printf("%c\n",realDest[0][0]); // Prints "*\n", because realDest[0] == tmp5[0],
                                 // So the change to tmp5[0][0] affects realDest[0][0]

  return 0;
}

La morale de l'histoire est la suivante: vous devez savoir ce qui se trouve de l'autre côté de vos pointeurs. Ou sinon.

Le second moral de l’histoire est: le fait que vous puissiez accéder à un double pointeur à l’aide de la notation [][] ne signifie pas qu’il est identique à un tableau à deux dimensions. Vraiment.


Permettez-moi de clarifier un peu la seconde morale.

Un tableau (que ce soit un ou deux dimensions, quel qu’il soit) est un morceau de mémoire alloué, et le compilateur sait quelle est sa taille (mais ne vérifie jamais la distance pour vous) et quelle adresse il commence. Vous déclarez des tableaux avec

char string1[32];
unsigned int histo2[10][20];

et des choses similaires;

Un pointeur est une variable pouvant contenir une adresse mémoire. Vous déclarez des pointeurs avec

char *sting_ptr1;
double *matrix_ptr = NULL;

Ce sont deux choses différentes.

Mais:

  1. Si vous utilisez la syntaxe [] avec un pointeur, le compilateur procédera à l'arithmétique des pointeurs à votre place.
  2. Dans presque tous les endroits où vous utilisez un tableau sans le déréférencer, le compilateur le traite comme un pointeur sur l'emplacement de départ du tableau.

Alors je peux faire

    strcpy(string1,"dmckee");

parce que la règle 2 dit que string1 (un tableau) est traité comme un char*). De même, je peux en parler avec:

    char *string_ptr2 = string1;

Finalement, 

    if (string_ptr[3] == 'k') {
      prinf("OK\n");
    }

affichera "OK" à cause de la règle 1.

1
dmckee

Pourquoi n'utilisez-vous pas le C++?

class C
{
    std::vector<std::string> data;
public:
    char** cpy();
};

char** C::cpy()
{
    std::string *psz = new std::string [data.size()];
    copy(data.begin(), data.end(), psz);
    char **ppsz = new char* [data.size()];
    for(size_t i = 0; i < data.size(); ++i)
    {
        ppsz[i] = new char [psz[i].length() + 1];
        ppsz[i] = psz[i].c_str();
    }
    delete [] psz;
    return(ppsz);
}

Ou quelque chose de similaire? Aussi, avez-vous besoin d’utiliser des C-strings? J'en doute.

1
Mateen Ulhaq

Comme d'autres l'ont suggéré, il semble qu'il s'agisse d'un tableau de pointeurs plutôt que d'un tableau multi-démetional.

donc au lieu d'être

char mdArray [10] [10];

il est:

char * pArray [10];

si tel est le cas, la seule chose que vous pouvez faire est de parcourir en boucle avec la valeur de longueur que vous obtenez. S'il y a des chaînes (ce qui semble être le cas), utilisez strlen, auquel cas:

char **tmp;

int length = getlengthfromwhereever;

char** copy = new char*[length];

for(int i=0; i<length; i++)
{
    int slen = strlen(tmp[i]);
    copy[i] = new char[slen+1]; //+1 for null terminator
    memcpy(copy[i],tmp[i],slen);
    copy[i][slen] = 0; // you could just copy slen+1 to copy the null terminator, but there might not be one...
}
0
matt

Notez que dans l'exemple suivant:

char **a;

a[i] est char*. Donc, si vous faites une memcpy() of a, vous faites une copie superficielle de ce pointeur.

Je laisserais tomber l'aspect multi-dimensionnel et choisirais un tampon plat de taille nn. Vous pouvez simuler A[i][j] avec A[i + jwidth]. Ensuite, vous pouvez memcpy(newBuffer, oldBuffer, width * height * sizeof(*NewBuffer)).

0
asveikau