web-dev-qa-db-ja.com

C / C ++ネストされたループなしで多次元文字配列をコピーする方法は?

多次元のchar配列を新しい宛先にコピーするスマートな方法を探しています。ソース配列を変更せずにコンテンツを編集したいので、char配列を複製します。

入れ子のループを作成してすべての文字を手動でコピーすることもできますが、もっと良い方法があるといいのですが。

更新:

2. levelディメンションのサイズがありません。与えられているのは長さ(行)だけです。

コードは次のようになります。

char **tmp;
char **realDest;

int length = someFunctionThatFillsTmp(&tmp);

//now I want to copy tmp to realDest

Tmpのすべてのメモリを空きメモリにコピーし、realDestにそれを指す方法を探しています。

更新2:

someFunctionThatFillsTmp()は、Redis C lib 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

23
dan

memcpy を使用できます。

多次元配列のサイズがコンパイル時に指定されている場合、つまりmytype myarray[1][2]、その後、1回のmemcpy呼び出しのみが必要です

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

配列が動的に割り当てられていることを示したように、動的に割り当てられたときのように両方の次元のサイズを知る必要がある場合、配列で使用されるメモリは連続した場所にないため、memcpyは複数回使用できます。

2D配列を指定すると、それをコピーする方法は次のようになります。

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 を使って文字列の長さを見つけることができます(それはnullで終了しなければなりません)。

その場合、ループは次のようになります。

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

Cのポインターへのポインターがある場合、データがどのように使用され、メモリーに配置されるかを知る必要があります。さて、最初のポイントは明白であり、一般的にどの変数にも当てはまります。プログラムで変数がどのように使用されるのかわからない場合、なぜそれがあるのでしょうか。 :-)。 2番目の点は、より興味深いものです。

最も基本的なレベルでは、タイプTへのポインターは、タイプTの-​​oneオブジェクトを指します。例えば:

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

現在、piは1つの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()が成功すると仮定すると、10個の連続する_int *_値のシーケンスの最初を指すppaが得られます。

だから、あなたがするとき:

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

tmpは、_char *_のようなオブジェクトのシーケンスの最初の_CR_MULTIBULK_SIZE_オブジェクトを指します。上記の各ポインタは初期化されていないため、_tmp[0]_から_tmp[CR_MULTIBULK_SIZE-1]_にはすべてガベージが含まれています。それらを初期化する1つの方法は、それらをmalloc()することです。

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

上記の_..._は、必要なithデータのサイズです。 i、月の位相、または乱数などに応じて、定数にすることも、変数にすることもできます。注意すべき主な点は、ループ内でmalloc()への_CR_MULTIBULK_SIZE_呼び出しがあり、各malloc()が連続したメモリブロックを返す一方で、 malloc()呼び出し間での連続性は保証されません。言い換えると、2番目の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()は連続したメモリをコピーするため、やりたいことを1つの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;
_

つまり、1つの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;
_

上記から、単純な答えは、_tmp[i]_にメモリを割り当てるmalloc()が複数ある場合、すべてのデータをコピーするためのループが必要になります。

8
Alok Singhal

配列の全体的なサイズを計算し、 memcpy を使用してコピーするだけです。

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

編集:質問の新しい情報は、配列の行数と列数が不明であり、配列が不規則であるため、memcpyが解決策ではない可能性があることを示しています。

6
John Knoeller

ここで何が起こっているかのいくつかの可能性を探りましょう:

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

この話の教訓は次のとおりです。ポインタの向こう側にあるものを知る必要があります。さもなければ

ストーリーのsecond教訓は次のとおりです:[][]表記を使用してダブルポインタにアクセスできるため二次元配列と同じにしないでください。本当に。


第二の道徳を少し明確にしましょう。

配列(1次元、2次元など)は、割り当てられたメモリの一部であり、コンパイラーはそれがどれほど大きいかを認識しています(ただし、範囲のチェックは行いません)。 、およびそれが開始するアドレス。配列は次のように宣言します

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

および同様のもの;

ポインタは、メモリアドレスを保持できる変数です。ポインタを宣言するには

char *sting_ptr1;
double *matrix_ptr = NULL;

彼らは2つの異なるものです。

だが:

  1. []構文をポインターと共に使用すると、コンパイラーがポインター演算を実行します。
  2. 逆参照せずに配列を使用するほとんどすべての場所で、コンパイラは配列を配列の開始位置へのポインタとして扱います。

だから、できる

    strcpy(string1,"dmckee");

ルール2はstring1(配列)がchar*)として扱われることを示しているためです。同様に、私はそれを追いかけることができます:

    char *string_ptr2 = string1;

最後に、

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

ルール1のため、「OK」を出力します。

なぜ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文字列を使用しますか?疑わしい。

1
Mateen Ulhaq

他の人が示唆したように、これは多目的配列ではなくポインタの配列のように見えます。

それの代わりに

char mdArray [10] [10];

それは:

char * pArray [10];

それが事実である場合、あなたができる唯一のことは、あなたが得た1つの長さの値でループすることです。

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...
}
0
matt

次の例で注意してください。

_char **a;
_

_a[i]_は_char*_です。したがって、amemcpy()を実行すると、そのポインタの浅いコピーを実行していることになります。

私は多次元の側面を捨て、サイズ_nn_のフラットバッファーを使います。 _A[i][j]_を_A[i + j__width]_でシミュレートできます。次に、memcpy(newBuffer, oldBuffer, width * height * sizeof(*NewBuffer))を実行できます。

0
asveikau