web-dev-qa-db-fra.com

Le programmeur C ++ doit-il éviter le memset?

J'ai entendu dire que les programmeurs c ++ devraient éviter les memsets,

class ArrInit {
    //! int a[1024] = { 0 };
    int a[1024];
public:
    ArrInit() {  memset(a, 0, 1024 * sizeof(int)); }
};

donc en considérant le code ci-dessus, si vous n'utilisez pas memset, comment pourriez-vous faire un [1..1024] rempli de zéro? Quel est le problème avec memset en C++?

merci.

36
Jichao

Le problème n'est pas tellement d'utiliser memset () sur les types intégrés, c'est de les utiliser sur les types de classe (aka non-POD). Cela fera presque toujours la mauvaise chose et souvent la chose fatale - cela peut, par exemple, piétiner un pointeur de table de fonction virtuelle.

44
anon

En C++ std::fill ou std::fill_n peut être un meilleur choix, car il est générique et peut donc fonctionner aussi bien sur des objets que sur des POD. Cependant, memset fonctionne sur une séquence brute d'octets, et ne doit donc jamais être utilisé pour initialiser des non-POD. Quoi qu'il en soit, des implémentations optimisées de std::fill peut utiliser en interne la spécialisation pour appeler memset si le type est un POD.

49
Charles Salvia

L'initialisation à zéro devrait ressembler à ceci:

class ArrInit {
    int a[1024];
public:
    ArrInit(): a() { }
};

En ce qui concerne l'utilisation de memset, il existe deux façons de rendre l'utilisation plus robuste (comme avec toutes ces fonctions): évitez de coder en dur la taille et le type du tableau:

memset(a, 0, sizeof(a));

Pour des vérifications supplémentaires au moment de la compilation, il est également possible de s'assurer que a est bien un tableau (donc sizeof(a) aurait du sens):

template <class T, size_t N>
size_t array_bytes(const T (&)[N])  //accepts only real arrays
{
    return sizeof(T) * N;
}

ArrInit() { memset(a, 0, array_bytes(a)); }

Mais pour les types sans caractère, j'imagine que la seule valeur que vous utiliseriez pour remplir est 0, et l'initialisation à zéro devrait déjà être disponible d'une manière ou d'une autre.

23
UncleBens

Ce qui ne va pas avec memset en C++ est principalement la même chose qui ne va pas avec memset en C. memset remplit la région de la mémoire avec un modèle physique de zéro bit, alors qu'en réalité dans pratiquement 100 % de cas dont vous avez besoin pour remplir un tableau avec des valeurs zéro logiques du type correspondant. En langage C, memset est uniquement garanti pour initialiser correctement la mémoire pour les types entiers (et sa validité pour tous les types entiers, par opposition aux types char uniquement, est une garantie relativement récente ajoutée à Spécification du langage C). Il n'est pas garanti de mettre correctement à zéro les valeurs à virgule flottante, il n'est pas garanti de produire des pointeurs NULL appropriés.

Bien sûr, ce qui précède peut être considéré comme excessivement pédant, car les normes et conventions supplémentaires actives sur la plate-forme donnée pourraient (et très certainement) étendre l'applicabilité de memset, mais je suggérerais toujours de suivre le rasoir d'Occam principe ici: ne vous fiez pas à d'autres normes et conventions, sauf si vous y êtes vraiment obligé. Le langage C++ (ainsi qu'un C) offre plusieurs fonctionnalités au niveau du langage qui vous permettent d'initialiser en toute sécurité vos objets agrégés avec des valeurs nulles correctes de type approprié. D'autres réponses ont déjà mentionné ces fonctionnalités.

10
AnT

C'est "mauvais" parce que vous ne mettez pas en œuvre votre intention.

Votre intention est de mettre à zéro chaque valeur du tableau et ce que vous avez programmé, c'est de mettre à zéro une zone de mémoire brute. Oui, les deux choses ont le même effet mais il est plus clair d'écrire simplement du code pour mettre à zéro chaque élément.

De plus, ce n'est probablement pas plus efficace.

class ArrInit
{
public:
    ArrInit();
private:
    int a[1024];
};

ArrInit::ArrInit()
{
    for(int i = 0; i < 1024; ++i) {
        a[i] = 0;
    }
}


int main()
{
    ArrInit a;
}

La compilation de cela avec Visual C++ 2008 32 bits avec les optimisations activées compile la boucle en -

; Line 12
    xor eax, eax
    mov ecx, 1024               ; 00000400H
    mov edi, edx
    rep stosd

Ce qui est à peu près exactement ce que le memset compilerait probablement de toute façon. Mais si vous utilisez memset, il n'y a aucune possibilité pour le compilateur d'effectuer d'autres optimisations, alors qu'en écrivant votre intention, il est possible que le compilateur puisse effectuer d'autres optimisations, par exemple en remarquant que chaque élément est plus tard défini sur quelque chose d'autre avant d'être utilisé de sorte que le l'initialisation peut être optimisée, ce qui ne serait probablement pas aussi facile si vous aviez utilisé un memset.

8
jcoder

Il s'agit d'un VIEUX fil, mais voici une tournure intéressante:

class myclass
{
  virtual void somefunc();
};

myclass onemyclass;

memset(&onemyclass,0,sizeof(myclass));

fonctionne parfaitement bien!

Cependant,

myclass *myptr;

myptr=&onemyclass;

memset(myptr,0,sizeof(myclass));

définit en effet les virtuels (c'est-à-dire somefunc () ci-dessus) sur NULL.

Étant donné que le memset est considérablement plus rapide que la mise à 0 de chaque membre d'une grande classe, je fais le premier memset ci-dessus depuis des siècles et je n'ai jamais eu de problème.

La question vraiment intéressante est donc comment ça marche? Je suppose que le compilateur commence en fait à mettre le zéro AU-DELÀ de la table virtuelle ... une idée?

1
user1920453

En plus d'être mauvais lorsqu'il est appliqué aux classes, memset est également sujet aux erreurs. Il est très facile de mettre les arguments en désordre ou d'oublier la partie sizeof. Le code compilera généralement avec ces erreurs et fera tranquillement la mauvaise chose. Le symptôme du bogue peut ne se manifester que beaucoup plus tard, ce qui rend la recherche difficile.

memset est également problématique avec de nombreux types simples, comme les pointeurs et les virgules flottantes. Certains programmeurs définissent tous les octets à 0, en supposant que les pointeurs seront alors NULL et les flottants seront 0,0. Ce n'est pas une hypothèse portable.

0
Adrian McCarthy

Il n'y a aucune raison réelle de ne pas l'utiliser, sauf dans les rares cas où les gens ont souligné que personne n'utiliserait de toute façon, mais il n'y a aucun avantage réel à l'utiliser, sauf si vous remplissez des memguards ou quelque chose du genre.

0
Charles Eli Cheese

La réponse courte serait d'utiliser un vecteur std :: avec une taille initiale de 1024.

std::vector< int > a( 1024 ); // Uses the types default constructor, "T()".

La valeur initiale de tous les éléments de "a" serait 0, car le constructeur std :: vector (size) (ainsi que vector :: resize) copie la valeur du constructeur par défaut pour tous les éléments. Pour les types intégrés (a.k.a. types intrinsèques ou POD), la valeur initiale est garantie à 0:

int x = int(); // x == 0

Cela permettrait au type que "a" utilise de changer avec un minimum de bruit, même à celui d'une classe.

La plupart des fonctions qui prennent un pointeur void (void *) comme paramètre, comme memset, ne sont pas de type sécurisé. Ignorer le type d'un objet, de cette manière, supprime tous les objets sémantiques de style C++ sur lesquels s'appuyer, tels que la construction, la destruction et la copie. memset fait des hypothèses sur une classe, ce qui viole l'abstraction (ne pas savoir ou se soucier de ce qui est à l'intérieur d'une classe). Bien que cette violation ne soit pas toujours immédiatement évidente, en particulier avec les types intrinsèques, elle peut potentiellement entraîner des bogues difficiles à localiser, d'autant plus que la base de code grandit et change de mains. Si le type qui est memset est une classe avec une table virtuelle (fonctions virtuelles), elle écrasera également ces données.

0
Kit10

Votre code est très bien. Je pensais que le seul moment en C++ où memset est dangereux, c'est quand vous faites quelque chose comme:
YourClass instance; memset(&instance, 0, sizeof(YourClass);.

Je pense que cela pourrait mettre à zéro les données internes de votre instance que le compilateur a créées.

0
rui