memcpy против цикла - Как правильно скопировать массив из указателя? - PullRequest
34 голосов
/ 19 января 2011

У меня есть функция 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];
    }
}

Есть ли третий вариант, который мне не хватает?

Ответы [ 5 ]

61 голосов
/ 19 января 2011

Да, третий вариант - использовать конструкцию C ++:

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

С любым здравомыслящим компилятором, это:

  • должно быть оптимальным в большинстве случаев (будет составлять memcpy(), где это возможно),
  • является типобезопасным,
  • изящно справляется, когда вы решаете изменить тип данных на не примитивный (т.е. он вызывает конструкторы копирования и т. Д.),
  • изящно справляется, когда вы решаете перейти на класс контейнера.
22 голосов
/ 19 января 2011

Memcpy, вероятно, будет быстрее, но, скорее всего, вы допустите ошибку, используя его. Это может зависеть от того, насколько умен ваш оптимизирующий компилятор.

Ваш код неверный. Должно быть:

memcpy(&myGlobalArray, nums, 10 * sizeof(int) );
5 голосов
/ 31 мая 2014

Вообще говоря, наихудший сценарий будет в неоптимизированной отладочной сборке, где 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", потому что массивы автоматически распадаются на указатели; вы на самом деле копировали «nums» по адресу в памяти, где хранился указатель на myGlobalArray [0].

( Редактировать заметку: я бы опечатал int[] nums, когда я имею в виду не int nums[], но я решил, что добавление C-указатель-указателя массива хаоса никому не помогло, так что теперь это 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 теперь имеют std :: string, которая считает, что ей принадлежит «hello». Один из них потерпит крах, когда разрушится, и они оба думают, что имеют один и тот же вектор целых чисел, который содержит 42.

Лучшая практика для программистов на C ++ - использовать std::copy:

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

Примечание по Реми Лебо или начиная с C ++ 11

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

Это может принимать решения во время компиляции о том, что делать, включая использование memcpy или memmove и, возможно, использование инструкций SSE / vector, если это возможно. Еще одним преимуществом является то, что если вы напишите это:

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

компилятор переключит ваш код для правильной работы без какой-либо дополнительной работы для вас, и ваш код станет немного более читабельным.

1 голос
/ 19 января 2011

По сути, если вы имеете дело с типами POD (обычные данные), такими как int, unsigned int, указатели, структуры только для данных и т. Д., Вы можете использовать mem *.

Если ваш массив содержит объекты, используйте цикл for, поскольку для обеспечения правильного назначения может потребоваться оператор =.

1 голос
/ 19 января 2011

Для производительности используйте memcpy (или эквиваленты). Это высокооптимизированный платформо-зависимый код для быстрого шунтирования большого количества данных.

Для удобства обслуживания подумайте о том, что вы делаете - цикл for может быть более читабельным и понятным. (Получение memcpy неправильно - это быстрый путь к краху или хуже)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...