Подтвердить что ты не робот

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

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

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

Update:

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

Код выглядит следующим образом:

char **tmp;
char **realDest;

int length = someFunctionThatFillsTmp(&tmp);

//now I want to copy tmp to realDest

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

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

someFunctionThatFillsTmp() - это функция credis_lrange() из Redis 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

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

4b9b3361

Ответ 1

Вы можете использовать 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);
}

Ответ 2

Когда у вас есть указатель на указатель на 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() их:

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 char *. Первый из указателей tmp[0] указывает на смежный блок из 3 char значений. Аналогично, tmp[1] и tmp[2] указывают на 5 и 2 char соответственно. Но память, на которую указывает 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;

I.e., вы выделили смежное пространство для всех указателей в одном вызове 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], вам понадобится цикл для копирования всех данных.

Ответ 3

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

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

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

Ответ 4

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

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* 
                                  // (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* 
                                  // (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;

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

Но:

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

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

    strcpy(string1,"dmckee");

потому что правило 2 говорит, что string1 (массив) рассматривается как char*). Аналогично, я могу fllow, что с:

    char *string_ptr2 = string1;

Наконец,

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

напечатает "OK" из-за правила 1.

Ответ 5

Почему вы не используете С++?

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

Или что-то подобное? Кроме того, вам нужно использовать C-строки? Я в этом сомневаюсь.

Ответ 6

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

char **a;

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

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

Ответ 7

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

поэтому вместо

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