web-dev-qa-db-fra.com

memcpy vs for loop - Quelle est la bonne façon de copier un tableau à partir d'un pointeur?

J'ai une fonction foo(int[] nums) qui, je crois, est essentiellement équivalente à foo(int* nums). A l'intérieur foo J'ai besoin de copier le contenu du tableau pointé par numsen un peu int[10] déclaré dans le cadre de foo. Je comprends que ce qui suit n'est pas valide:

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

Quelle est la bonne façon de copier le tableau? Dois-je utiliser memcpy comme ceci:

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

ou devrais-je utiliser une boucle for?

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

Y a-t-il une troisième option qui me manque?

35
mjn12

Memcpy sera probablement plus rapide, mais il est plus probable que vous fassiez une erreur en l'utilisant. Cela peut dépendre de l'intelligence de votre compilateur d'optimisation.

Votre code est cependant incorrect. Ça devrait être:

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

Oui, la troisième option consiste à utiliser une construction C++:

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

Avec n'importe quel compilateur sensé, il:

  • devrait être optimal dans la majorité des cas (compilera dans memcpy() si possible),
  • est de type sûr,
  • copie avec élégance lorsque vous décidez de remplacer le type de données par un non primitif (c'est-à-dire qu'il appelle des constructeurs de copie, etc.),
  • s'adapte avec élégance lorsque vous décidez de passer à une classe de conteneur.
62
Oliver Charlesworth

De manière générale, le pire des cas se situera dans une version de débogage non optimisée où memcpy n'est pas aligné et peut effectuer des vérifications supplémentaires de vérification/assertion équivalant à un petit nombre d'instructions supplémentaires par rapport à une boucle for.

Cependant memcpy est généralement bien implémenté pour exploiter des choses comme intrinsèques, etc., mais cela variera selon l'architecture cible et le compilateur. Il est peu probable que memcpy soit pire qu'une implémentation pour boucle.

Les gens trébuchent souvent sur le fait que la taille de la mémoire est en octets et écrivent des choses comme celles-ci:

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

Vous pouvez vous protéger ici en utilisant des fonctionnalités de langage qui vous permettent de faire un certain degré de réflexion, c'est-à-dire: faire des choses en termes de données elles-mêmes plutôt que ce que vous savez sur les données, car dans une fonction générique, vous ne savez généralement rien sur les données:

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

Notez que vous ne voulez pas le "&" en face de "myGlobalArray" car les tableaux se désintègrent automatiquement aux pointeurs; vous copiez en fait des "nums" à l'adresse en mémoire où se trouvait le pointeur vers myGlobalArray [0].

( Modifier la note: je taperais int[] nums quand je veux dire int nums[] mais j'ai décidé que l'ajout de C array-pointer-equivalence chaos n'a aidé personne, alors maintenant c'est int *nums:) )

L'utilisation de memcpy sur des objets peut être dangereuse, pensez à:

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

C'est le mauvais moyen de copier des objets qui ne sont pas POD (données anciennes simples). F1 et f2 ont maintenant une chaîne std :: qui pense qu'elle possède "hello". L'un d'eux va planter quand ils détruisent, et ils pensent tous les deux qu'ils possèdent le même vecteur d'entiers qui en contient 42.

La meilleure pratique pour les programmeurs C++ est d'utiliser std::copy:

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

Note par Remy Lebeau ou depuis C++ 11

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

Cela peut prendre des décisions de compilation sur ce qu'il faut faire, notamment en utilisant memcpy ou memmove et éventuellement en utilisant des instructions SSE/vector si possible. Un autre avantage est que si vous écrivez ceci:

struct Foo {
    int m_i;
};

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

puis modifiez Foo pour inclure un std::string, votre code va casser. Si vous écrivez à la place:

struct Foo {
    int m_i;
};

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

le compilateur changera votre code pour faire la bonne chose sans aucun travail supplémentaire pour vous, et votre code est un peu plus lisible.

5
kfsone

Pour les performances, utilisez memcpy (ou équivalents). C'est un code spécifique à la plate-forme hautement optimisé pour shunter rapidement de nombreuses données.

Pour la maintenabilité, considérez ce que vous faites - la boucle for peut être plus lisible et plus facile à comprendre. (Se tromper de mémoire est un itinéraire rapide vers un crash ou pire)

1
Jason Williams

Essentiellement, tant que vous traitez avec des types POD (Plain Ol 'Data), tels que int, int non signé, pointeurs, structures de données uniquement, etc ... vous pouvez utiliser mem * en toute sécurité.

Si votre tableau contient des objets, utilisez la boucle for, car l'opérateur = peut être requis pour garantir une affectation correcte.

1
James