2010-02-09 14 views
19

Szukam inteligentnego sposobu na skopiowanie wielowymiarowej tablicy znaków do nowego miejsca docelowego. Chcę zduplikować tablicę znaków, ponieważ chcę edytować zawartość bez zmiany tablicy źródłowej.C/C++ Jak skopiować wielowymiarową tablicę znaków bez zagnieżdżonych pętli?

Mogłem zbudować zagnieżdżone pętle, aby skopiować wszystkie znaki ręcznie, ale mam nadzieję, że jest lepszy sposób.

Aktualizacja:

nie mam wielkości wymiaru 2. poziomu. Podana jest tylko długość (rzędy).

Kod wygląda następująco:

char **tmp; 
char **realDest; 

int length = someFunctionThatFillsTmp(&tmp); 

//now I want to copy tmp to realDest 

szukam sposobu, który kopiuje wszystkie pamięci tmp język wolnej pamięci oraz punktu realDest do niego.

Aktualizacja 2:

someFunctionThatFillsTmp() jest funkcją credis_lrange() z Redis C lib credis.c.

Wewnątrz lib tmp jest tworzony z:

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

Update 3:

Próbowałem używać memcpy z tej linii:

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

prints: mystring 

ale jestem uzyskanie: Otrzymany sygnał programu: EXC_BAD_ACCESS

+4

To całkowicie zależy od tego, jak zbudowana jest twoja "tablica wielowymiarowa". Pokaż kod, który go tworzy. – caf

+2

jeśli nie masz wymiarów tablicowych, nie możesz ich skopiować również za pomocą pętli. –

+0

@John Knoeller: Dzięki. Zaktualizowałem opis. – dan

Odpowiedz

30

Można użyć memcpy.

Jeśli wielowymiarowy rozmiar tablicy jest podany w czasie kompilacji, czyli mytype myarray[1][2], to tylko jedno wywołanie memcpy jest potrzebne

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

Jeśli, tak jak wskazuje tablica jest dynamicznie przydzielana, trzeba wiedzieć rozmiar obu wymiarów, jak przy przydzielaniu dynamicznym, pamięć użyta w tablicy nie będzie w sąsiadującej lokalizacji, co oznacza, że ​​memcpy będzie musiał być użyty wiele razy.

Biorąc 2d tablicy, sposób skopiować byłoby następująco:

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

Biorąc pod uwagę, że z pytaniem wygląda jak masz do czynienia z tablicą ciągów, można użyć strlen aby znaleźć długość łańcucha (musi być zakończona zerem).

W takim przypadku pętla staną

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

Ja po drugie takie podejście ... –

+0

Za wszelką cenę używaj 'memcpy', ale pytanie jest raz dla prawdziwej tablicy wielowymiarowej lub wiele razy dla poszarpanej tablicy (co jest zasugerowane przez użycie przez PO podwójnego dwukierunkowości). .)? – dmckee

+0

@dmckee moja oryginalna odpowiedź została napisana dla pierwotnego pytania, a nie dla zaktualizowanego pytania. Moja odpowiedź jest teraz, mam nadzieję, lepiej dopasowana do zaktualizowanego pytania. – Yacoby

6

Wystarczy obliczyć całkowity rozmiar tablicy, a następnie użyć memcpy go skopiować.

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

Edycja: nowe w kwestii wskazuje, że liczba wierszy i cols w tablicy nie jest znana, a tablica może być nierówny, tak memcpy może być rozwiązaniem.

+1

sizeof (char) == 1 byte przez definicja (czy ten 1 bajt to 8 bitów czy nie, to zupełnie inne pytanie ...) – Jon

+1

@Jon: Tak, ale jest nieszkodliwy i pomaga wyjaśnić, że jest to liczba bajtów, a nie liczba elementów - i musiałby zostać zaktualizowany, gdyby tablica była szeroka. –

1

Pozwala odkrywać pewne możliwości, co się dzieje tutaj:

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

Morał z tej historii jest taki: musisz wiedzieć, co znajduje się po drugiej stronie swoich wskaźników. Albo.

sekund Morał z tej historii jest taki: tylko dlatego, że można uzyskać dostęp do podwójnego wskaźnika za pomocą notacji [][] nie sprawiają, że jest taka sama jak tablicy dwuwymiarowej. Naprawdę.


Pozwól mi wyjaśnić drugi moralny trochę.

tablica (może to być jednowymiarowe, dwuwymiarowe, cokolwiek) jest przydzielony kawałek pamięci, a kompilator wie, jak duże jest (ale nigdy nie robi żadnej zakres kontroli dla ciebie), a co go rozwiązać zaczyna się. Deklarujesz tablice z

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

i podobnych sprawach;

A wskaźnik to zmienna, która może pomieścić adres pamięci. Deklarujesz wskaźniki za pomocą

char *sting_ptr1; 
double *matrix_ptr = NULL; 

Są to dwie różne rzeczy.

Ale:

  1. Jeśli używasz składni [] ze wskaźnikiem, kompilator zrobi arytmetyki wskaźników dla Ciebie.
  2. W prawie każdym miejscu, w którym używasz tablicy bez dereferencji, kompilator traktuje ją jako wskaźnik do lokalizacji początkowej tablic.

Więc mogę zrobić

strcpy(string1,"dmckee"); 

ponieważ zasada 2 mówi, że łańcuch1 (tablica) jest traktowany jako char*). Podobnie mogę fllow że z:

char *string_ptr2 = string1; 

Wreszcie,

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

wypisze "OK", ponieważ z reguły 1.

0

pamiętać, że w poniższym przykładzie:

char **a; 

a[i] to char*. Więc jeśli robisz memcpy() z a, robisz płytką kopię tego wskaźnika.

Chciałbym porzucić aspekt wielowymiarowy i przejść z płaskim buforem o rozmiarze nn. Możesz symulować A[i][j] z A[i + jwidth]. Następnie możesz memcpy(newBuffer, oldBuffer, width * height * sizeof(*NewBuffer)).

7

Kiedy masz wskaźnik do wskaźnika w C, musisz wiedzieć, w jaki sposób dane będą używane i rozmieszczone w pamięci. Teraz pierwsza kwestia jest oczywista i prawdziwa dla każdej zmiennej w ogóle: jeśli nie wiesz, w jaki sposób niektóre zmienne będą używane w programie, dlaczego tak jest? :-). Drugi punkt jest bardziej interesujący.

Na najbardziej podstawowym poziomie, wskaźnik do typu T punkty jeden obiektu typu T. Na przykład:

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

Teraz pi punkty do jednego int. Jeśli chcesz, możesz zrobić punkt wskaźnik do pierwszego z wielu takich obiektów:

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

pa wskazuje teraz na pierwszą z sekwencji 10 (ciągłych) int wartości, przy założeniu, że malloc() powiedzie, pb punkty do pierwszego z innego zestawu 10 (ponownie, sąsiadującego) int s.

To samo dotyczy jeśli masz wskaźnik do wskaźnika:

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

Zakładając, że malloc() powiedzie, teraz masz ppa wskazując na pierwszą z sekwencji 10 ciągłych int * wartości.

Więc, kiedy zrobić:

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

tmp punkty do pierwszego char * obiektu w sekwencji CR_MULTIBULK_SIZE takich obiektów. Każdy z powyższych wskaźników nie został zainicjowany, więc wszystkie zawierają śmieci. Jednym ze sposobów, aby je zainicjować byłoby malloc() nich:

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

... powyżej jest rozmiar danych i th chcemy. Może to być stała lub zmienna, w zależności od i, fazy księżyca, liczby losowej lub czegokolwiek innego. Najważniejsze, aby zwrócić uwagę, że w pętli masz CR_MULTIBULK_SIZE połączenia z malloc() i że podczas gdy każdy malloc() zwróci ci sąsiedni blok pamięci, nie jest gwarantowane sąsiadowanie w połączeniach malloc(). Innymi słowy, drugie wywołanie malloc() nie zwróci wskaźnika, który rozpoczyna się w miejscu, w którym zakończyły się poprzednie dane.

Aby bardziej konkretne rzeczy, załóżmy CR_MULTIBULK_SIZE to 3. Na zdjęciach, Twoje dane mogą wyglądać następująco:

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

tmp punktów do ciągłego bloku 3 char * wartości. Pierwszy ze wskaźników, tmp[0], wskazuje na sąsiedni blok wartości 3 char.Podobnie, tmp[1] i tmp[2] wskazują odpowiednio 5 i 2 char s. Ale pamięć wskazywana przez tmp[0] do tmp[2] nie jest ciągła jako całość.

Od memcpy() kopiuje sąsiednią pamięć, co chcesz zrobić, nie można zrobić przez jeden memcpy(). Ponadto musisz wiedzieć, w jaki sposób przydzielono poszczególne numery tmp[i]. Tak w ogóle, co chcesz zrobić, potrzebuje pętli:

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

Jak wyżej, można zadzwonić memcpy() wewnątrz pętli, więc nie trzeba zagnieżdżonych pętli w kodzie. (Najprawdopodobniej memcpy() jest realizowana za pomocą pętli, więc efekt jest jak gdyby zagnieżdżone pętle.)

Teraz, jeśli miał kod jak:

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; 

czyli ty przydzielone ciągłą przestrzeń dla wszystkich wskaźniki w jednym malloc() rozmowy, można skopiować wszystkie dane bez pętli w kodzie:

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; 

z powyższego, odpowiedź jest prosta, jeśli miał więcej niż jeden malloc() przydzielić pamięci dla tmp[i], będziesz potrzebował pętli do skopiowania wszystkich danych.

0

Jak sugerowali inni, wygląda na to, że jest to tablica wskaźników, a nie wielowańcowa tablica.

więc zamiast tego jest

znak mdArray [10] [10];

to:

char * pArray [10];

Jeśli tak jest, jedyną rzeczą, którą możesz zrobić, to wykonać pętlę z wartością o jednej długości, jeśli istnieją ciągi (jak to wygląda), użyj strlen, w którym to przypadku :

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

Dlaczego nie używasz 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); 
} 

Czy coś podobnego? Ponadto, czy potrzebujesz potrzebujesz do używania ciągów C? Wątpię.

Powiązane problemy