web-dev-qa-db-ja.com

memcpy対forループ-ポインターから配列をコピーする適切な方法は何ですか?

関数foo(int[] nums)があり、これは基本的にfoo(int* nums)と同等であると理解しています。 fooの中では、numsinが指す配列の内容をいくつかのint[10]は、fooのスコープ内で宣言されています。次の内容が無効であることを理解しています。

void foo (int[] nums) 
{
    myGlobalArray = *nums
}

アレイをコピーする適切な方法は何ですか? memcpyを次のように使用する必要があります。

void foo (int[] nums)
{
    memcpy(&myGlobalArray, nums, 10);
}

またはforループを使用する必要がありますか?

void foo(int[] nums)
{
    for(int i =0; i < 10; i++)
    {
        myGlobalArray[i] = nums[i];
    }
}

欠けている3番目のオプションはありますか?

35
mjn12

Memcpyはおそらくより高速ですが、それを使用すると間違いを犯す可能性が高くなります。それは最適化コンパイラがどれだけ賢いかに依存するかもしれません。

ただし、コードは正しくありません。そのはず:

memcpy(&myGlobalArray, nums, 10 * sizeof(int) );
21
Jay

はい、3番目のオプションはC++コンストラクトを使用することです。

_std::copy(&nums[0], &nums[10], myGlobalArray);
_

正常なコンパイラでは、次のことを行います。

  • ほとんどの場合に最適である必要があります(可能な場合はmemcpy()にコンパイルされます)、
  • タイプセーフであり、
  • データ型を非プリミティブに変更する場合(つまり、コピーコンストラクターを呼び出すなど)に適切に対処します。
  • コンテナクラスへの変更を決定した場合、適切に対処します。
62

一般的に、最悪のシナリオは、memcpyがインライン化されず、forループに対して少数の追加命令に相当する追加の健全性/アサートチェックを実行する、最適化されていないデバッグビルドです。

ただし、memcpyは一般に、組み込み関数などを活用するために適切に実装されますが、これはターゲットのアーキテクチャとコンパイラによって異なります。 memcpyがforループ実装よりも悪いことはまずありません。

人々はしばしばmemcpyのサイズがバイト単位であるという事実につまずき、次のようなことを書きます。

// wrong unless we're copying bytes.
memcpy(myGlobalArray, nums, numNums);
// wrong if an int isn't 4 bytes or the type of nums changed.
memcpy(myGlobalArray, nums, numNums);
// wrong if nums is no-longer an int array.
memcpy(myGlobalArray, nums, numNums * sizeof(int));

ある程度のリフレクションを可能にする言語機能を使用して、ここで身を守ることができます。つまり、一般的な関数では一般に何も知らないため、データについて知っていることではなく、データ自体に関して物事を行います。データについて:

void foo (int* nums, size_t numNums)
{
    memcpy(myGlobalArray, nums, numNums * sizeof(*nums));
}

配列は自動的にポインターに減衰するため、「myGlobalArray」の前に「&」を付けたくないことに注意してください。実際には、myGlobalArray [0]へのポインタが保持されているメモリ内のアドレスに「nums」をコピーしていました。

メモを編集:typo'dしたint[] nums意味しない場合int nums[]しかし、 C array-pointer-equivalence chaosを追加しても誰も役に立たないと決めたので、今ではint *nums:)

オブジェクトでmemcpyを使用するのは危険です。

struct Foo {
    std::string m_string;
    std::vector<int> m_vec;
};

Foo f1;
Foo f2;
f2.m_string = "hello";
f2.m_vec.Push_back(42);
memcpy(&f1, &f2, sizeof(f2));

これは、POD(プレーンな古いデータ)ではないオブジェクトをコピーする間違った方法です。 f1とf2の両方に、「hello」を所有していると考えるstd :: stringがあります。そのうちの1つは、破壊するとクラッシュし、両方とも42を含む同じ整数ベクトルを所有していると考えます。

C++プログラマのベストプラクティスは、std::copy

std::copy(nums, nums + numNums, myGlobalArray);

Remy LebeauごとのメモまたはC++ 11以降

std::copy_n(nums, numNums, myGlobalArray);

これにより、memcpyまたはmemmoveを使用し、可能であればSSE/vector命令を使用するなど、何を行うかについてコンパイル時に決定できます。もう1つの利点は、次のように書くことです。

struct Foo {
    int m_i;
};

Foo f1[10], f2[10];
memcpy(&f1, &f2, sizeof(f1));

後でFooを変更してstd::string、コードが壊れます。代わりに書く場合:

struct Foo {
    int m_i;
};

enum { NumFoos = 10 };
Foo f1[NumFoos], f2[NumFoos];
std::copy(f2, f2 + numFoos, f1);

コンパイラーはコードを切り替えて、追加作業なしで正しいことを行います。コードはもう少し読みやすくなります。

5
kfsone

パフォーマンスのために、memcpy(または同等のもの)を使用します。大量のデータを高速で迂回するための高度に最適化されたプラットフォーム固有のコードです。

保守性のために、何をしているのかを考えてください-forループは読みやすく、理解しやすいかもしれません。 (memcpyを間違って取得することは、クラッシュへの高速ルートまたはさらに悪いことです)

1
Jason Williams

基本的に、int、unsigned int、ポインター、データのみの構造体などのPODタイプ(Plain Ol 'Data)を扱っている限り、mem *を使用しても安全です。

配列にオブジェクトが含まれている場合、forループを使用します。適切な割り当てを保証するために=演算子が必要になる場合があります。

1
James