Memcpy vs for loop - Каков правильный способ скопировать массив из указателя?
У меня есть функция foo(int[] nums)
, которая, как я понимаю, по существу эквивалентна foo(int* nums)
. Внутри foo
Мне нужно скопировать содержимое массива, на которое указывает nums
, в некоторый 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];
}
}
Есть ли третий вариант, который мне не хватает?
Ответы
Ответ 1
Memcpy, вероятно, будет быстрее, но, скорее всего, вы сделаете ошибку, используя его.
Это может зависеть от того, насколько разумным является ваш оптимизирующий компилятор.
Однако ваш код неверен. Это должно быть:
memcpy(&myGlobalArray, nums, 10 * sizeof(int) );
Ответ 2
Да, третий вариант - использовать конструкцию С++:
std::copy(&nums[0], &nums[10], myGlobalArray);
С любым компилятором sane:
- должен быть оптимальным в большинстве случаев (скомпилируется по возможности
memcpy()
),
- безопасен по типу,
- изящно справляется, когда вы решите изменить тип данных на не-примитивный (т.е. он вызывает конструкторы копирования и т.д.),
- изящно справляется, когда вы решите перейти на класс контейнера.
Ответ 3
В общем случае сценарий наихудшего сценария будет в не оптимизированной сборке отладки, где memcpy
не является встроенным и может выполнять дополнительные проверки на работоспособность/подтверждение, составляющие небольшое количество дополнительных инструкций против цикла for.
Однако memcpy
обычно хорошо применяется для использования таких вещей, как intrinsics и т.д., но это будет зависеть от целевой архитектуры и компилятора. Маловероятно, что memcpy
будет когда-либо хуже, чем реализация for-loop.
Люди часто сталкиваются с тем, что размеры 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));
Вы можете защитить себя здесь, используя языковые функции, которые позволяют вам сделать некоторую степень отражения, то есть: делать что-то с точки зрения самих данных, а не с тем, что вы знаете о данных, потому что в общей функции вы вообще не используете " t знать что-либо о данных:
void foo (int[] nums, size_t numNums)
{
memcpy(myGlobalArray, nums, numNums * sizeof(*nums));
}
Обратите внимание, что вы не хотите "&" infront "myGlobalArray", потому что массивы автоматически распадаются на указатели; вы фактически копировали "nums" в адрес в памяти, где удерживался указатель на myGlobalArray [0].
Использование 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 теперь имеют std::string, который считает, что он владеет "привет". Один из них собирается сбой, когда они разрушают, и оба они считают, что они имеют один и тот же вектор целых чисел, который содержит 42.
Лучшей практикой для программистов на С++ является использование std::copy
:
std::copy(nums, nums + numNums, myGlobalArray);
Это может сделать время принятия решения о том, что делать, включая использование memcpy
или memmove
и потенциально используя SSE/векторные инструкции, если это возможно. Другим преимуществом является то, что если вы пишете это:
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);
компилятор переключит ваш код, чтобы делать правильные вещи без какой-либо дополнительной работы для вас, а ваш код немного читабельнее.
Ответ 4
Для производительности используйте memcpy (или эквиваленты). Он очень оптимизировал платформенный код для быстрого шунтирования данных.
Для удобства обслуживания рассмотрите, что вы делаете - цикл for может быть более читабельным и понятным. (Получение неверного memcpy - быстрый путь к сбою или еще хуже)
Ответ 5
По существу, если вы имеете дело с типами POD (Plain Ol 'Data), такими как int, unsigned int, указатели, структуры данных и т.д., вы можете использовать mem *.
Если ваш массив содержит объекты, используйте цикл for, поскольку для обеспечения правильного назначения может потребоваться оператор =.