C/C++, Как скопировать многомерный массив символов без вложенных циклов?

Я ищу умный способ скопировать многомерный массив символов в новое место назначения. Я хочу копировать массив символов, потому что я хочу отредактировать содержание, не изменяя исходный массив.

Я мог создать вложенные циклы для копирования каждого символа вручную, но я надеюсь, что существует лучший путь.

Обновление:

У меня нет размера 2. размер уровня. Данный только длина (строки).

Код похож на это:

char **tmp;
char **realDest;

int length = someFunctionThatFillsTmp(&tmp);

//now I want to copy tmp to realDest

Я ищу метод, который копирует всю память tmp в свободную память и точку realDest к нему.

Обновление 2:

someFunctionThatFillsTmp () является функцией credis_lrange () от Redi C lib credis.c.

В lib tmp создается с:

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

Обновление 3:

Я попытался использовать memcpy с этим, выравнивает:

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

prints: mystring

Но я получаю a: Программа получила сигнал: EXC_BAD_ACCESS

21
задан dan 9 February 2010 в 01:45
поделиться

6 ответов

Вы можете использовать memcpy.

Если размер многомерного массива задан во время компиляции, т.е. mytype myarray[1][2], то потребуется только один вызов memcpy

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

Если, как вы указали, массив выделен динамически, вам нужно знать размер обоих измерений, поскольку при динамическом выделении память, используемая в массиве, не будет находиться в смежном месте, что означает, что memcpy придется использовать несколько раз.

Учитывая массив 2d, метод его копирования будет следующим:

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

Учитывая, что из вашего вопроса следует, что вы имеете дело с массивом строк, вы можете использовать strlen, чтобы найти длину строки (она должна быть с нулевым окончанием).

В этом случае цикл станет

for ( int i = 0; i < length; ++i ){
    int width = strlen(src[i]) + 1;
    dest[i] = malloc(width);    
    memcpy(dest[i], src[i], width);
}
33
ответ дан 29 November 2019 в 20:06
поделиться

Вы можете просто вычислить общий размер массива, а затем использовать memcpy , чтобы скопировать его.

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

Изменить: новая информация в вопросе указывает на то, что количество строк и столбцов массива неизвестно, и что массив может быть рваным, поэтому memcpy не может быть решением.

6
ответ дан 29 November 2019 в 20:06
поделиться

Когда у вас есть указатель на указатель в C, вы должны знать, как данные будут использоваться и располагаться в памяти. Итак, первый пункт очевиден и верен для любой переменной в целом: если вы не знаете, как какая-то переменная будет использоваться в программе, зачем она нужна? :-). Второй момент более интересен.

На самом базовом уровне указатель на тип T указывает на один объект типа T . Например:

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

Теперь pi указывает на один int .При желании вы можете указать указатель на первый из многих таких объектов:

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

pa теперь указывает на первое из 10 (смежных) значений int и при условии, что malloc () завершается успешно, pb указывает на первый из другого набора из 10 (опять же, смежных) int s.

То же самое применимо, если у вас есть указатель на указатель:

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

Предполагая, что malloc () завершается успешно, теперь у вас есть ppa , указывающий на первую из последовательности из 10 смежные значения int * .

Итак, когда вы это сделаете:

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

tmp указывает на первый объект char * в последовательности из CR_MULTIBULK_SIZE таких объектов. Каждый из указанных выше указателей не инициализирован, поэтому от tmp [0] до tmp [CR_MULTIBULK_SIZE-1] все содержат мусор. Один из способов их инициализации - это malloc () them:

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

... выше - это размер i -ых данных, которые мы хотим. Это может быть константа или переменная, в зависимости от i , или фазы луны, или случайного числа, или чего-то еще. Главное отметить, что у вас есть CR_MULTIBULK_SIZE вызовов malloc () в цикле, и что хотя каждый malloc () вернет вам непрерывный блок памяти, смежность не гарантируется между вызовами malloc () .Другими словами, не гарантируется, что второй вызов malloc () вернет указатель, который начинается там, где заканчивались данные предыдущего malloc () .

Чтобы сделать вещи более конкретными, предположим, что CR_MULTIBULK_SIZE равно 3. На рисунках ваши данные могут выглядеть следующим образом:

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

tmp указывает на непрерывный блок из 3 символов * значений. Первый из указателей, tmp [0] , указывает на непрерывный блок из 3 char значений. Аналогично, tmp [1] и tmp [2] указывают на 5 и 2 char s соответственно. Но память, на которую указывает tmp [0] - tmp [2] , не является непрерывной в целом.

Поскольку memcpy () копирует непрерывную память, то, что вы хотите сделать, не может быть выполнено одной memcpy () . Кроме того, вам необходимо знать, как был выделен каждый tmp [i] . Итак, в общем, то, что вы хотите сделать, требует цикла:

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

Как и выше, вы можете вызвать memcpy () внутри цикла, поэтому вам не нужен вложенный цикл в вашем коде. (Скорее всего, memcpy () реализован с помощью цикла, поэтому эффект будет таким, как если бы у вас были вложенные циклы.)

Теперь, если у вас был код вроде:

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;

Т., вы выделили непрерывное пространство для всех указателей за один вызов malloc () , тогда вы можете скопировать все данные без цикла в коде:

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;

Из вышесказанного простой ответ: если вы было несколько malloc () для выделения памяти для tmp [i] , тогда вам понадобится цикл для копирования всех данных.

8
ответ дан 29 November 2019 в 20:06
поделиться

Давайте исследуем некоторые возможности того, что здесь происходит:

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

Мораль истории такова: вы должны знать, что находится по ту сторону ваших указателей. Или иначе.

Вторая мораль этой истории такова: только то, что вы можете получить доступ к двойному указателю, используя нотацию [][] , не делает его таким же, как двумерный массив. Действительно.


Позвольте мне немного пояснить вторую мораль.

Массив (будь то одномерный, двухмерный и т. Д.) - это выделенная часть памяти, и компилятор знает ее размер (но никогда не проверяет диапазон за вас), и с какого адреса начинается. Вы объявляете массивы с помощью

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

и тому подобного;

Указатель - это переменная, которая может содержать адрес памяти. Вы объявляете указатели с помощью

char *sting_ptr1;
double *matrix_ptr = NULL;

. Это разные вещи.

Но:

  1. Если вы используете синтаксис [] с указателем, компилятор выполнит арифметику указателя за вас.
  2. Практически везде, где вы используете массив без разыменования, компилятор рассматривает его как указатель на начальную позицию массива.

Итак, я могу сделать

    strcpy(string1,"dmckee");

, потому что правило 2 говорит, что строка1 (массив) обрабатывается как char * ). Точно так же я могу изменить это с помощью:

    char *string_ptr2 = string1;

Наконец,

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

напечатает «ОК» из-за правила 1.

1
ответ дан 29 November 2019 в 20:06
поделиться

Как предлагали другие, похоже, что это массив указателей, а не массив, состоящий из нескольких разрядов.

поэтому вместо того, чтобы быть

char mdArray[10][10];

это:

char* pArray[10];

если это так, то единственное, что можно сделать - это проциклировать полученное значение одной длины, если предполагается, что это строки (а это так и выглядит), то используйте strlen, и в этом случае это будет так и есть:

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
ответ дан 29 November 2019 в 20:06
поделиться

Обратите внимание, что в следующем примере:

char **a;

a[i] это char*. Таким образом, если вы делаете memcpy() из a, вы делаете неглубокую копию этого указателя.

я бы избавился от многомерного аспекта и взял бы плоский буфер размером nn. Вы можете имитировать A[i][j] с помощью A[i + jwidth]. Затем можно memcpy(newBuffer, oldBuffer, width * height * sizeof(*NewBuffer)).

0
ответ дан 29 November 2019 в 20:06
поделиться