0

Here Il a été établi qu'il est illégal de traiter des membres structurels consécutifs fortement serrés de type T comme un tableau de T.std :: memcpy structure avec des membres de TriviallyCopyable T fortement serrés à un tableau de T et vice versa

Mais qu'en est-il de la copie de la représentation sous-jacente?

Étant donné:

struct vec { 
    float x, y, z; 
}; 

avec les mêmes contraintes:

static_assert(sizeof(vec) == 3 * sizeof(float)); 

est la suivante:

int main() { 
    vec v = {1.9f, 2.5f, 3.1f}; 

    float a[3]; 
    std::memcpy(&a, &v, 3 * sizeof(float)); 
    assert(a[0] == v.x); 
    assert(a[1] == v.y); 
    assert(a[2] == v.z); 

    vec u; 
    std::memcpy(&u, &a, 3 * sizeof(float)); 
    assert(u.x == a[0]); 
    assert(u.y == a[1]); 
    assert(u.z == a[2]); 
} 

juridique?

+0

Relatif (ou peut-être en double): http://stackoverflow.com/questions/37211298/accessing-an-array-as-a-struct-vs-undefined-behavior – Barmar

+0

@Barmar c'est le même problème discuté dans le question que j'ai liée. –

+0

Ils disent tous les deux que c'est un comportement indéfini. Pourquoi pensez-vous que votre cas est différent? – Barmar

Répondre

1

Tant que votre type de structure n'a pas de rembourrage, il n'y a pas de soutien explicite dans la norme , mais le soutien à quelque chose de très proche peut être déduit.

Étant donné un type trivialement copiable T, ce qui est autorisé explicitement est de copier sa représentation dans un tableau de char (ou unsigned char) et le dos.

Il n'est pas nécessaire que le contenu de la matrice soit conservé dans la matrice elle-même. Le contenu pourrait être stocké dans un fichier, et relu lors d'une exécution ultérieure du programme. Ou stocké dans un objet d'un type différent, aussi longtemps que ce type le permet. Pour que cela fonctionne, les implémentations doivent admettre des représentations memcpy dans des objets lorsque ces représentations ne proviennent pas d'objets de type T dans la même exécution.

En conséquence, à tout le moins,

int main() { 
    vec v = {1.9f, 2.5f, 3.1f}; 

    float a[3]; 

    assert(sizeof v == sizeof a); 

    { char tmp[3 * sizeof(float)]; 
     std::memcpy(tmp, &v, 3 * sizeof(float)); 
     std::memcpy(a, tmp, 3 * sizeof(float)); } 
    assert(a[0] == v.x); 
    assert(a[1] == v.y); 
    assert(a[2] == v.z); 

    vec u; 
    { char tmp[3 * sizeof(float)]; 
     std::memcpy(tmp, a, 3 * sizeof(float)); 
     std::memcpy(&u, tmp, 3 * sizeof(float)); } 
    assert(u.x == a[0]); 
    assert(u.y == a[1]); 
    assert(u.z == a[2]); 
} 

devrait soit échouer sur le premier assert, ou passer. Pour toute représentation où il échouerait, il est trivial de créer une fonction qui arrive à produire cette représentation exacte de manière non ambiguë, donc elle ne doit pas échouer.

Maintenant, en omettant tmp voici un peu incertain.

std::memcpy est juste affectation répétée des octets individuels et aurait pu être explicitées. La sémantique de l'opérateur = implique que pour les types trivialement copiables, a = b; et { auto tmp = b; a = tmp; } sont équivalents. Idem avec a = b; c = d; et { auto tmp1 = b; auto tmp2 = d; a = tmp1; c = tmp2; }, et ainsi de suite. Le premier est ce que fait un memcpy, ce dernier est ce que deux memcpy à travers tmp faire. D'autre part, l'autorisation de copier dans et hors d'un tableau de char peut être interprétée comme nécessitant un tableau réel de char, et pas simplement l'équivalent fonctionnel de celui-ci. Personnellement, je ne m'inquiéterais probablement pas à moins que je ne découvre une implémentation qui utilise cette interprétation, mais si vous voulez jouer en toute sécurité, vous pouvez introduire un tel tableau temporaire, et vérifier que votre compilateur parvient à optimiser loin.

-3

La raison pour laquelle vous ne devriez pas toujours croire qu'une structure de trois membres du même type est équivalente à un tableau du même type est essentiellement due à l'alignement de la mémoire.

https://en.wikipedia.org/wiki/Data_structure_alignment

Votre code pourrait fonctionner bien sur un compilateur C++ et ne parviennent pas à un autre ou même sur le même compilateur avec une configuration différente.

Notez également que vous utilisez le pointeur de tableau incorrectement.

std::memcpy(a, &v, 3 * sizeof(float)); 

et non

std::memcpy(&a, &v, 3 * sizeof(float)); 

un est déjà un pointeur constant flotter

+1

Il n'y a pas de problèmes d'alignement, car 'float's ont toujours les mêmes exigences d'alignement, peu importe où ils sont stockés. Il y a des soucis de bourrage de _are_ cependant, mais je garde contre ceux avec le 'static_assert'. En ce qui concerne le pointeur vers le tableau vs le pointeur vers le premier élément de tableau, cela n'a pas d'importance car ils sont identiques lorsqu'ils sont convertis en pointeur vers void. –

+0

@yurikilochek Les autres types ont des exigences d'alignement différentes selon qu'ils font partie d'une structure ('long long' sur les systèmes x86 GNU/Linux), pourquoi ne pas 'float'? Mais l'alignement ne devrait pas poser de problème ici si votre vérification de remplissage fonctionne. – hvd

+0

@hvd J'étais sous l'impression que c'était vrai pour tous les types, pas seulement pour les flotteurs. Mais vous avez raison, cela n'a pas d'importance si aucun rembourrage n'est introduit. –