2010-02-09 39 views
19

Tôi đang tìm một cách thông minh để sao chép mảng char đa chiều đến đích mới. Tôi muốn lặp lại mảng char vì tôi muốn chỉnh sửa nội dung mà không thay đổi mảng nguồn.C/C++ Làm thế nào để sao chép một mảng char đa chiều mà không có vòng lặp lồng nhau?

Tôi có thể xây dựng các vòng lồng nhau để sao chép mọi char bằng tay nhưng tôi hy vọng có một cách tốt hơn.

Cập nhật:

Tôi không có kích thước của kích thước 2. mức. Given chỉ là chiều dài (hàng).

Mã này trông như thế này:

char **tmp; 
char **realDest; 

int length = someFunctionThatFillsTmp(&tmp); 

//now I want to copy tmp to realDest 

Tôi đang tìm một phương pháp mà bản sao tất cả các ký ức về tmp vào bộ nhớ miễn phí và điểm realDest với nó.

Cập nhật 2:

someFunctionThatFillsTmp() là hàm credis_lrange() từ lib Redis C credis.c.

Bên trong tmp lib được tạo ra với:

rhnd->reply.multibulk.bulks = malloc(sizeof(char *)*CR_MULTIBULK_SIZE) 

Cập nhật 3:

Tôi đã cố gắng sử dụng memcpy với dòng này:

int cb = sizeof(char) * size * 8; //string inside 2. level has 8 chars 
memcpy(realDest,tmp,cb); 
cout << realDest[0] << endl; 

prints: mystring 

Nhưng tôi nhận được: Tín hiệu nhận được chương trình: EXC_BAD_ACCESS

+4

Nó hoàn toàn phụ thuộc vào cách "mảng đa chiều" của bạn được xây dựng. Hiển thị mã tạo ra nó. – caf

+2

nếu bạn không có thứ nguyên mảng, thì bạn cũng không thể sao chép kích thước đó bằng vòng lặp. –

+0

@ John Knoeller: Cảm ơn. Tôi đã cập nhật mô tả. – dan

Trả lời

30

Bạn có thể sử dụng memcpy.

Nếu kích thước mảng đa chiều được đưa ra tại thời gian biên dịch, tức là mytype myarray[1][2], sau đó chỉ là một cuộc gọi memcpy đơn là cần thiết

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

Nếu như bạn đã cho mảng được cấp phát động, bạn sẽ cần phải biết kích thước của cả hai kích thước khi được cấp phát động, bộ nhớ được sử dụng trong mảng sẽ không nằm ở vị trí tiếp giáp, có nghĩa là memcpy sẽ phải được sử dụng nhiều lần.

Cho một mảng 2ngày, phương pháp để sao chép nó sẽ là như sau:

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

Cho rằng từ câu hỏi của bạn có vẻ như bạn đang đối phó với một mảng các chuỗi, bạn có thể sử dụng strlen để tìm ra chiều dài của chuỗi (Nó phải là null chấm dứt).

Trong trường hợp này, vòng lặp sẽ trở thành

for (int i = 0; i < length; ++i){ 
    int width = strlen(src[i]) + 1; 
    dest[i] = malloc(width);  
    memcpy(dest[i], src[i], width); 
} 
+0

Tôi thứ hai cách tiếp cận này ... –

+0

Bằng mọi cách, sử dụng 'memcpy', nhưng câu hỏi là một lần cho một mảng đa chiều thực hoặc nhiều lần cho một mảng bị rách rưới (được đề xuất bởi việc sử dụng OP kép của OP .. .)? – dmckee

+0

@dmckee Câu trả lời ban đầu của tôi đã được viết cho câu hỏi gốc, không phải câu hỏi được cập nhật. Câu trả lời của tôi bây giờ hy vọng phù hợp hơn với câu hỏi được cập nhật. – Yacoby

6

Bạn chỉ có thể tính toán kích thước tổng thể của mảng và sau đó sử dụng memcpy để sao chép nó.

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

Edit: thông tin mới trong câu hỏi chỉ ra rằng số lượng hàng và cols của mảng không được biết, và rằng các mảng có thể rách rưới, vì vậy memcpy có thể không phải là một giải pháp.

+1

sizeof (char) == 1 byte bởi định nghĩa (cho dù 1 byte là 8 bit hay không là một câu hỏi hoàn toàn khác ...) – Jon

+1

@Jon: Có, nhưng nó vô hại, và nó giúp làm cho nó rõ ràng rằng đây là một số byte và không phải là một phần tử đếm - và sẽ cần được cập nhật nếu mảng là ký tự rộng. –

1

Hãy khám phá một số khả năng cho những gì đang xảy ra ở đây:

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

Các đạo đức của câu chuyện là: bạn phải biết những gì là ở phía bên kia của con trỏ của bạn. Hoặc cái gì đó khác.

Các thứ hai đạo đức của câu chuyện là: chỉ vì bạn có thể truy cập vào một con trỏ kép sử dụng [][] ký hiệu không làm cho nó cũng giống như mảng hai chiều. Có thật không.


Hãy để tôi làm rõ đạo đức thứ hai một chút.

An mảng (là một chiều, hai chiều, bất kỳ) là một phần bộ nhớ được phân bổ và trình biên dịch biết mức độ lớn (nhưng không bao giờ kiểm tra phạm vi cho bạn) và địa chỉ gì bắt đầu. Bạn khai báo các mảng với

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

và những thứ tương tự;

A con trỏ là một biến có thể chứa địa chỉ bộ nhớ. Bạn khai báo con trỏ với

char *sting_ptr1; 
double *matrix_ptr = NULL; 

Chúng là hai thứ khác nhau.

Nhưng:

  1. Nếu bạn sử dụng cú pháp [] với một con trỏ, trình biên dịch sẽ làm con trỏ số học cho bạn.
  2. Ở hầu hết mọi nơi bạn sử dụng một mảng mà không có dereferencing nó, trình biên dịch xử lý nó như là một con trỏ đến vị trí bắt đầu mảng.

Vì vậy, tôi có thể làm

strcpy(string1,"dmckee"); 

vì quy tắc 2 nói rằng chuỗi1 (một mảng) được coi là một char*). Tương tự như vậy, tôi có thể fllow rằng với:

char *string_ptr2 = string1; 

Cuối cùng,

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

sẽ in "OK" vì quy tắc 1.

0

Lưu ý rằng trong ví dụ sau:

char **a; 

a[i]char*. Vì vậy, nếu bạn thực hiện một số memcpy() của a, bạn đang tạo bản sao nông của con trỏ đó.

Tôi sẽ bỏ qua khía cạnh đa chiều và đi với bộ đệm phẳng có kích thước nn. Bạn có thể mô phỏng A[i][j] với A[i + jwidth]. Sau đó, bạn có thể memcpy(newBuffer, oldBuffer, width * height * sizeof(*NewBuffer)).

7

Khi bạn có con trỏ đến con trỏ trong C, bạn phải biết cách dữ liệu sẽ được sử dụng và được trình bày trong bộ nhớ. Bây giờ, điểm đầu tiên là hiển nhiên, và đúng cho bất kỳ biến nói chung: nếu bạn không biết làm thế nào một số biến sẽ được sử dụng trong một chương trình, tại sao có nó? :-). Điểm thứ hai thú vị hơn.

Ở cấp độ cơ bản nhất, con trỏ nhập T trỏ tới một đối tượng loại T. Ví dụ:

int i = 42; 
int *pi = &i; 

Bây giờ, pi trỏ đến một int. Nếu bạn muốn, bạn có thể làm cho một điểm con trỏ đến đầu tiên của nhiều đối tượng như:

int arr[10]; 
int *pa = arr; 
int *pb = malloc(10 * sizeof *pb); 

pa tại chỉ để là người đầu tiên của một chuỗi các 10 (liền kề) int giá trị, và giả định rằng malloc() thành công, pb điểm đến tập đầu tiên của bộ 10 (một lần nữa, tiếp giáp) int s.

cũng áp dụng nếu bạn có một con trỏ đến một con trỏ:

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

Giả sử rằng malloc() thành công, bây giờ bạn có ppa trỏ đến đầu tiên trong một chuỗi 10 int * giá trị tiếp giáp.

Vì vậy, khi bạn làm:

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

tmp điểm đến đối tượng đầu tiên char * trong một chuỗi các CR_MULTIBULK_SIZE đối tượng như vậy. Mỗi con trỏ ở trên không được khởi tạo, vì vậy, tmp[0] đến tmp[CR_MULTIBULK_SIZE-1] tất cả đều chứa rác. Một cách để khởi tạo chúng sẽ được malloc() họ:

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

Các ... trên là kích thước của dữ liệu thứ i chúng ta muốn. Nó có thể là một hằng số, hoặc nó có thể là một biến, tùy thuộc vào i, hoặc giai đoạn của mặt trăng, hoặc một số ngẫu nhiên, hoặc bất cứ điều gì khác. Điểm chính cần lưu ý là bạn có các cuộc gọi CR_MULTIBULK_SIZE tới malloc() trong vòng lặp và rằng trong khi mỗi malloc() sẽ trả về cho bạn một khối bộ nhớ liền kề, tiếp giáp không được đảm bảo qua các cuộc gọi malloc(). Nói cách khác, cuộc gọi thứ hai malloc() không được đảm bảo để trả lại con trỏ bắt đầu ngay khi dữ liệu của malloc() trước đó kết thúc.

Để làm cho mọi việc cụ thể hơn, chúng ta hãy giả CR_MULTIBULK_SIZE là 3. Trong hình ảnh, dữ liệu của bạn có thể trông như thế này:

 +------+           +---+---+ 
tmp: |  |--------+       +----->| a | 0 | 
    +------+  |       |  +---+---+ 
        |       | 
        |       | 
        |   +------+------+------+ 
        +-------->| 0 | 1 | 2 | 
           +------+------+------+ 
            |  | 
            |  | +---+---+---+---+---+ 
            |  +--->| t | e | s | t | 0 | 
          +------+   +---+---+---+---+---+ 
          | 
          | 
          | +---+---+---+ 
          +--->| h | i | 0 | 
           +---+---+---+ 

tmp điểm đến một khối tiếp giáp của 3 char * giá trị. Đầu tiên của con trỏ, tmp[0], trỏ đến khối liền kề 3 char giá trị.Tương tự, tmp[1]tmp[2] trỏ đến 5 và 2 char s tương ứng. Nhưng bộ nhớ được trỏ đến bởi tmp[0] đến tmp[2] không tiếp giáp với tổng thể.

memcpy() bản sao bộ nhớ liền kề, những gì bạn muốn làm không thể được thực hiện bởi một memcpy(). Hơn nữa, bạn cần phải biết cách phân bổ mỗi tmp[i]. Vì vậy, nói chung, những gì bạn muốn làm cần có một vòng lặp:

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

Như trên, bạn có thể gọi memcpy() bên trong vòng lặp, do đó bạn không cần vòng lặp lồng nhau trong mã của bạn. (Nhiều khả năng memcpy() được thực hiện với một vòng lặp, do đó hiệu quả là như nếu bạn đã lồng vòng.)

Bây giờ, nếu bạn có mã như:

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; 

Ie, bạn phân bổ không gian tiếp giáp cho tất cả các con trỏ trong một malloc() cuộc gọi, sau đó bạn có thể sao chép tất cả dữ liệu mà không có một vòng lặp trong mã của bạn:

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; 

Từ trên, câu trả lời đơn giản là, nếu bạn có nhiều hơn một malloc() phân bổ bộ nhớ cho tmp[i], sau đó bạn sẽ cần một vòng lặp để sao chép tất cả dữ liệu.

0

Như những người khác được đề xuất, có vẻ như đây là một mảng con trỏ thay vì mảng đa mảng.

vì vậy thay vì là

char mdArray [10] [10];

là:

char * pArray [10];

nếu trường hợp duy nhất bạn có thể thực hiện là lặp lại với giá trị độ dài mà bạn nhận được, nếu có nghĩa là chuỗi (có vẻ như là) thì sử dụng strlen trong trường hợp đó :

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

Tại sao bạn không sử dụng 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); 
} 

Hoặc điều gì đó tương tự? Ngoài ra, bạn có cần cần để sử dụng C-strings không? Tôi nghi ngờ điều đó.

Các vấn đề liên quan