C / C ++ Как скопировать многомерный массив символов без вложенных циклов? - PullRequest
21 голосов
/ 09 февраля 2010

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

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

Обновление:

У меня нет размера 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

Ответы [ 7 ]

33 голосов
/ 09 февраля 2010

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

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

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

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

Для двухмерного массива метод его копирования будет следующим:

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);
}
8 голосов
/ 09 февраля 2010

Когда у вас есть указатель на указатель в 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;

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

6 голосов
/ 09 февраля 2010

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

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

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

1 голос
/ 01 мая 2011

Почему вы не используете 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);
}

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

1 голос
/ 09 февраля 2010

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

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

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

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


Позвольте мне немного разъяснить второй мораль.

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

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

и подобные вещи;

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

char *sting_ptr1;
double *matrix_ptr = NULL;

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

Но:

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

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

    strcpy(string1,"dmckee");

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

    char *string_ptr2 = string1;

Наконец,

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

выведет «OK» из-за правила 1.

0 голосов
/ 09 февраля 2010

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

так что вместо того, чтобы быть

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 голосов
/ 09 февраля 2010

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

char **a;

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

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

...