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() из библиотеки Redis C lib credis. .с.

Внутри 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


person dan    schedule 09.02.2010    source источник
comment
Это полностью зависит от того, как построен ваш многомерный массив. Покажите код, который его создает.   -  person caf    schedule 09.02.2010
comment
если у вас нет размеров массива, вы также не можете скопировать его с помощью цикла.   -  person John Knoeller    schedule 09.02.2010
comment
@Джон Ноеллер: Спасибо. Я обновил описание.   -  person dan    schedule 09.02.2010
comment
Когда caf попросил код, он имел в виду, что нам нужно знать, что делает someFunctionThatFillsTmp, хотя бы в общих чертах. Является ли это рваным массивом или это монолитное выделение одного блока. (Обратите внимание, что если это позднее, вам не нужна двойная косвенность.)   -  person dmckee --- ex-moderator kitten    schedule 09.02.2010
comment
void * memcpy(void *dst, const void *src, size_t len); Вы уверены, что используете его правильно?   -  person dmckee --- ex-moderator kitten    schedule 09.02.2010
comment
@dmckee: Спасибо за это. Я смешал scr и dest. Теперь я могу получить доступ к realDest, но получаю полученный программой сигнал: ошибка EXC_BAD_ACCESS.   -  person dan    schedule 09.02.2010
comment
Я отмечаю, что tmp — это char **, а аргумент memcpy — это void *.   -  person dmckee --- ex-moderator kitten    schedule 09.02.2010
comment
1. Вы сказали, что есть 2 измерения, но функция возвращает только одно. Это квадратный массив? 2. Вы уверены, что для пункта назначения было выделено достаточно памяти?   -  person David Nehme    schedule 09.02.2010
comment
@David Nehme: 1 измерение - это список, второе - количество символов в строке. Я предполагаю, что строки внутри списка состоят из 8 символов. Я также пытался увеличить размер с тем же результатом.   -  person dan    schedule 09.02.2010
comment
@Dan: Насколько вы уверены, что знаете структуру allocated объекта, на который указывает tmp? memcpy, что вы делаете, неправильно почти в любом случае, но мы не можем сказать вам, почему, если вы не уточните, что находится на другом конце. И я не собираюсь копаться в тысяче строк библиотечного кода, чтобы понять это за вас. Я предполагаю, что вещь, возвращаемая библиотекой, должна быть непрозрачной, и вы должны попросить библиотеку передать вам нужные вам строки.   -  person dmckee --- ex-moderator kitten    schedule 09.02.2010


Ответы (7)


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

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

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

Если, как вы указали, массив динамически распределяется, вам нужно будет знать размер обоих измерений, так как при динамическом распределении память, используемая в массиве, не будет находиться в непрерывном месте, что означает, что 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);
}
person Yacoby    schedule 09.02.2010
comment
Во что бы то ни стало, используйте memcpy, но вопрос один раз для реального многомерного массива или много раз для рваного массива (что предлагается использованием ОП двойной косвенности...)? - person dmckee --- ex-moderator kitten; 09.02.2010
comment
@dmckee мой первоначальный ответ был написан для исходного вопроса, а не для обновленного вопроса. Надеюсь, мой ответ теперь лучше подходит для обновленного вопроса. - person Yacoby; 09.02.2010
comment
выполнение strlen, а затем memcpy ничем не отличается от простого выполнения strdup(). см. git.musl-libc.org/cgit/musl /tree/src/string/strdup.c - person technosaurus; 13.12.2014
comment
@technosaurus strdup() не является стандартным C или C++ - person PC Luddite; 11.06.2016

Когда у вас есть указатель на указатель в 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 (опять же, смежных) ints.

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

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(...);

... выше — это размер ith данных, которые нам нужны. Это может быть константа или переменная, зависящая от 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], то вам понадобится цикл для копирования всех данных.

person Alok Singhal    schedule 09.02.2010

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

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

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

person John Knoeller    schedule 09.02.2010
comment
sizeof(char) == 1 байт по определению (соответствует ли 1 байт 8 битам или нет, это совершенно другой вопрос...) - person Jon; 09.02.2010
comment
@Jon: Да, но это безвредно, и это помогает понять, что это количество байтов, а не количество элементов, и его нужно было бы обновить, если массив был широкими символами. - person John Knoeller; 09.02.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;
}

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

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


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

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

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

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

person dmckee --- ex-moderator kitten    schedule 09.02.2010

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

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-строки? Я сомневаюсь в этом.

person Mateen Ulhaq    schedule 30.04.2011

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

char **a;

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

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

person asveikau    schedule 09.02.2010

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

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

char mdArray[10][10];

it is:

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...
}
person matt    schedule 09.02.2010