21


4

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

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

ネストされたループを作成してすべての文字を手でコピーすることもできますが、もっと良い方法があればいいのですが。

更新:

2のサイズがありません。 レベル次元。 長さ(行)のみが与えられます。

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

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

7 Answer


33


http://en.cppreference.com/w/cpp/string/byte/memcpy [memcpy]を使用できます。

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

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

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

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

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

あなたの質問から、文字列の配列を扱っているように見えるので、http://en.cppreference.com/w/cpp/string/byte/strlen [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);
}


8


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個のセットの最初(再び、連続した) int `s。

ポインターへのポインターがある場合も同じことが当てはまります。

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

`malloc()`が成功すると仮定すると、10個の連続した `int *`値のシーケンスの最初を指す `ppa`があります。

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

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

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

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

上記の「…​」は、必要な「i」番目のデータのサイズです。 「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個の `char`を指します。 しかし、 `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;

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


6


配列全体のサイズを計算し、http://msdn.microsoft.com/en-us/library/dswaw1wk(VS.71).aspx [memcpy]を使用してコピーするだけです。

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

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


1


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

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_教訓は次のとおりです。* `[] []`表記を使用してダブルポインターにアクセスできるからといって、2次元配列と同じになりません。 本当に。*

'' '' '

第二の教訓を少し明確にしましょう。

  • array *(1次元、2次元など)は割り当てられたメモリの一部であり、コンパイラはそれがどれだけ大きいかを知っています(ただし、範囲チェックを行うことはありません)。 配列を宣言するには

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

および同様のもの;

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

char *sting_ptr1;
double *matrix_ptr = NULL;

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

But:

  1. ポインタで `[]`構文を使用すると、コンパイラは あなたのためのポインタ演算。

  2. ほとんどすべての場所で、配列を間接参照せずに使用します。 コンパイラは、配列の開始位置へのポインタとしてそれを扱います。

だから、私はできる

    strcpy(string1,"dmckee");

ルール2では、string1(配列)は「char *」として扱われるためです)。 同様に、私はそれを次のように投げることができます:

    char *string_ptr2 = string1;

最後に、

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

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


1


なぜC ++を使用しないのですか?

class C
{
    std::vector 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文字列を使用する必要がありますか? 疑わしい。


0


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

char **a;

「a [i]」は「char *」です。 したがって、 a`の memcpy() `を実行すると、そのポインターの浅いコピーが実行されます。

多次元の側面を捨てて、サイズがnnのフラットバッファーを使用します。 `A [i] [j]`を `A [i + jwidth]`でシミュレートできます。 次に、 `memcpy(newBuffer、oldBuffer、width * height * sizeof(* NewBuffer))`を実行できます。


0


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

そうではなく

char mdArray [10] [10];

それは:

char * pArray [10];

それがあなたができる唯一のことは、あなたが得る1つの長さの値でループすることであり、文字列(それがそうであるように見える)がある場合は、strlenを使用します。

char **tmp;

int length = getlengthfromwhereever;

char** copy = new char*[length];

for(int i=0; i