2017-02-19 2 views
4

L'appel memcpy sur deux structures différentes conserve-t-il les données d'origine si la taille de la mémoire tampon est suffisante? Et est-il défini pour récupérer les valeurs d'un autre type de données avec les données du type de données précédent si leurs types de données respectifs se chevauchent?Memcpy conserve-t-il les données entre différents types?

Cela devrait être similaire pour les deux c/langues cpp mais je fournir un exemple dans cpp -

#include <iostream> 
#include <cstring> 

using namespace std; 

struct A{ 
    int a; 
    char b[10]; 
}; 

struct B{ 
    int ba; 
    int bb; 
}; 

int main(){ 
    B tmp; 
    tmp.ba = 50; 
    tmp.bb = 24; 
    cout << tmp.ba << tmp.bb << "\n"; 

    // everything is fine yet 

    A obj; 
    memcpy(&obj, &tmp, sizeof(tmp)); 

    // 1. is this valid? 
    cout << obj.a << "\n"; 

    B newB; 
    memcpy(&newB, &obj, sizeof(newB)); 

    // 2. Are these valid? 
    cout << newB.ba << newB.bb << "\n"; 
} 

Dans l'exemple ci-dessus, je l'ai commenté 1er et 2ème commentaire, sont-ils valides et les données conservées si une zone tampon suffisante est fournie? Pouvons-nous le faire de façon portative?

La structure et les autres fonctions qui s'y rapportent sont en bibliothèque C mais nous allons l'utiliser et le compiler avec C++.

+4

'memcpy' ne connaît ni ne se soucie du type de données. Il copie le nombre d'octets que vous spécifiez, que le tampon de destination soit suffisamment grand ou non. Lorsque les plages se chevauchent, le comportement est * non défini * et "memmove" doit être utilisé. –

+1

'memcpy()' ne garantit rien concernant les types de données sous-jacents. –

+3

C ou C++? Ce sont deux langues différentes avec un modèle d'objet complètement différent. –

Répondre

3

La norme C++ ne spécifie pas le comportement memcpy, hormis le renvoi à la norme C. (Peut-être pour éviter de s'attaquer à des problèmes comme celui-ci!). Dans la norme C, elle est définie comme étant équivalente à une séquence de copies de type de caractères .

Il semble donc raisonnable de traiter memcpy(&obj, &tmp, sizeof(tmp)); comme:

unsigned char *dst = (char *)&obj; 
unsigned char *src = (char *)&tmp; 
for (size_t i = 0; i != sizeof tmp; ++i) 
    dst[i] = src[i]; 

puis utilisez la norme C++ pour couvrir ce code.

Les questions sont maintenant:

  1. Est-ce que &tmp, &obj donnent effectivement l'adresse du début de l'objet?
  2. Qu'en est-il des octets de remplissage dans obj?
  3. Qu'en est-il des octets de remplissage non initialisés dans tmp?
  4. Qu'advient-il des valeurs des sous-objets de obj?

Numéro 1: Oui, cela est couvert par [class.mem]/19, car il n'y a pas de sous-objets de classe de base (et il ne surcharge pas operator&).

Problème 2: Je ne trouve aucun texte traitant spécifiquement de cela; mais l'exemple dans la norme de copie d'un objet de type classe dans un tampon char et de retour dans l'objet ne fonctionnerait pas s'il n'était pas autorisé à écrire des octets de remplissage.

Problème 3: Dans [dcl.init]/12 est un texte qui autorise explicitement l'utilisation du code ci-dessus pour les données non initialisées; et la destination contiendra des valeurs indéterminées. Par conséquent, si les octets de remplissage non initialisés dans la source sont uniquement mappés sur des octets de remplissage non initialisés dans la destination, tout va bien. Mais s'ils sont mappés à des sous-objets dans la destination, alors ces objets auront une valeur indéterminée.

Problème 4: Il n'y a pas de problème ici, la règle d'alias strict permet aux objets d'avoir une partie (ou la totalité) de leurs octets écrasés par une expression de type caractère. L'accès à l'objet plus tard donnera la valeur correspondant à la représentation, avec UB s'il ne représente pas une valeur. Donc, dans l'ensemble, je pense que votre exemple est OK, en supposant sizeof(A) >= sizeof(B).


En C, memcpy préserve aussi le de type effective de l'objet. C++ a un modèle d'objet différent et il n'y a pas d'équivalent de ceci. Donc, si vous avez utilisé un code similaire avec un compilateur C, vous devrez également observer la règle d'alias stricte entre les types dans les deux objets.

+1

à propos de 3e point, cela devrait également soutenir votre argument - http://en.cppreference.com/w/cpp/language/data_members#Standard_layout, non? –

+0

@AbhinavGauniyal oui, je vais modifier la réponse –

+0

@ M.M si je 'memset (.., 0, ..)' toutes les structures avant d'appeler 'memcpy()' ceci devrait résoudre le problème des bits non initialisés? – buggy3