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*
。因此,如果您执行a
的memcpy()
,则您正在执行该指针的浅拷贝。
我会放弃多维方面并使用大小为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(...);
上面的...
是我们想要的i
th 数据的大小。它可以是一个常数,也可以是一个变量,这取决于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 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]
分配内存,那么您将需要一个循环来复制所有数据。
【讨论】:
【参考方案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中创建连续循环,其中循环的每次迭代在其内部循环的每次迭代中发生一次