C / C ++如何复制没有嵌套循环的多维char数组?

Posted

技术标签:

【中文标题】C / C ++如何复制没有嵌套循环的多维char数组?【英文标题】:C / C++ How to copy a multidimensional char array without nested loops? 【发布时间】:2011-01-14 14:44:03 【问题描述】:

我正在寻找一种将多维字符数组复制到新目的地的智能方法。我想复制 char 数组,因为我想在不更改源数组的情况下编辑内容。

我可以构建嵌套循环来手动复制每个字符,但我希望有更好的方法。

更新:

我没有 2. 级别维度的大小。给定的只是长度(行)。

代码如下:

char **tmp;
char **realDest;

int length = someFunctionThatFillsTmp(&tmp);

//now I want to copy tmp to realDest

我正在寻找一种方法,将 tmp 的所有内存复制到空闲内存中,并将 realDest 指向它。

更新 2:

someFunctionThatFillsTmp() 是来自 Redis C 库 credis.c 的函数 credis_lrange()。

在 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

【问题讨论】:

这完全取决于你的“多维数组”是如何构造的。显示创建它的代码。 如果你没有数组维度,那么你也不能用循环复制它。 @John Knoeller:谢谢。我已经更新了描述。 当 caf 要求提供代码时,他的意思是我们需要知道 someFunctionThatFillsTmp 做了什么,至少在概要上。这是一个参差不齐的数组还是一个整体的单块分配。 (注意,如果是后者,则不需要双重间接。) void * memcpy(void *dst, const void *src, size_t len);你确定你用对了吗? 【参考方案1】:

你可以使用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);

【讨论】:

无论如何,请使用memcpy,但问题是一次用于真正的多维数组或多次用于参差不齐的数组(OP 使用双重间接建议... )? @dmckee 我的原始答案是针对原始问题而非更新问题编写的。希望我的回答现在更适合更新后的问题。 执行 strlen 然后 memcpy 与仅执行 strdup() 没有什么不同。见git.musl-libc.org/cgit/musl/tree/src/string/strdup.c @technosaurus strdup() 不是标准的 C 或 C++【参考方案2】:

您可以只计算数组的整体大小,然后使用memcpy 复制它。

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

编辑:问题中的新信息表明数组的行数和列数未知,并且数组可能参差不齐,因此 memcpy 可能不是解决方案。

【讨论】:

sizeof(char) == 定义的 1 字节(1 字节是否为 8 位是完全不同的问题......) @Jon:是的,但它是无害的,它有助于明确这是一个字节数而不是元素数 - 如果数组是宽字符,则需要更新。 【参考方案3】:

让我们探索一下这里发生的事情的一些可能性:

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;

这个故事的寓意是:你必须知道你的指针的另一面是什么。否则。

第二故事的寓意是:仅仅因为您可以使用[][] 表示法访问双指针并不意味着它与二维数组相同。真的。


让我稍微澄清一下第二个道德。

数组(无论是一维、二维等)是一块分配的内存,编译器知道它有多大(但从不为您进行任何范围检查),并且a 它从什么地址开始。你用

声明数组
char string1[32];
unsigned int histo2[10][20];

和类似的东西;

指针是一个可以保存内存地址的变量。你用

声明指针
char *sting_ptr1;
double *matrix_ptr = NULL;

它们是两个不同的东西。

但是:

    如果您将[] 语法与指针一起使用,编译器将为您进行指针运算。 几乎在任何使用数组而不取消引用的地方,编译器都会将其视为指向数组起始位置的指针。

所以,我可以做到

    strcpy(string1,"dmckee");

因为规则 2 说 string1(一个数组)被视为 char*)。同样,我可以使用:

    char *string_ptr2 = string1;

最后,

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

由于规则 1,将打印“OK”。

【讨论】:

【参考方案4】:

请注意,在以下示例中:

char **a;

a[i]char*。因此,如果您执行amemcpy(),则您正在执行该指针的浅拷贝。

我会放弃多维方面并使用大小为n<em>n</em> 的平面缓冲区。你可以用A[i + jwidth]模拟A[i][j]。然后你可以memcpy(newBuffer, oldBuffer, width * height * sizeof(*NewBuffer))

【讨论】:

【参考方案5】:

当你有一个指向 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 个(再次,连续)@ 中的第一个987654331@s.

如果你有一个指向指针的指针,这同样适用:

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

假设malloc() 成功,现在您有ppa 指向10 个连续int * 值序列中的第一个。

所以,当你这样做时:

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

tmp 指向CR_MULTIBULK_SIZE 此类对象序列中的第一个char * 对象。上面的每个指针都没有初始化,所以tmp[0]tmp[CR_MULTIBULK_SIZE-1]都包含垃圾。初始化它们的一种方法是malloc()它们:

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

上面的... 是我们想要的ith 数据的大小。它可以是一个常数,也可以是一个变量,这取决于i、月相、随机数或其他任何东西。需要注意的主要一点是,您在循环中对malloc() 进行了CR_MULTIBULK_SIZE 调用,并且虽然每个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 chars。但是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】:

正如其他人所建议的,这看起来像是一个指针数组,而不是一个多元素数组。

所以不是

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

【讨论】:

【参考方案7】:

你为什么不使用 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 字符串吗?我怀疑。

【讨论】:

以上是关于C / C ++如何复制没有嵌套循环的多维char数组?的主要内容,如果未能解决你的问题,请参考以下文章

c语言一个死循环中为啥执行完一个功能函数就退出了?

如何避免包含`a(i)= b(i,c(i))`的显式循环?

如何在C中创建连续循环,其中循环的每次迭代在其内部循环的每次迭代中发生一次

c语言中,如何画带有函数嵌套的流程图?

c++ - 如何将多维数组传递给没有内部维度的函数? [复制]

将字符串的char传递给C ++中的函数?