59 votes

Devrais-je m'inquiéter de l'alignement lors de la conversion du pointeur?

Dans mon projet, nous avons un morceau de code comme ceci:

// raw data consists of 4 ints
unsigned char data[16];
int i1, i2, i3, i4;
i1 = *((int*)data);
i2 = *((int*)(data + 4));
i3 = *((int*)(data + 8));
i4 = *((int*)(data + 12));

J'en ai parlé à mon tech responsable que ce code peut ne pas être portable car il est en train de jeter un unsigned char* d'un int* qui a généralement une plus strict alignement exigence. Mais tech lead dit, c'est tout droit, la plupart des compilateurs reste la même valeur du pointeur après la coulée, et je peux juste écrire le code comme ceci.

Pour être franc, je ne suis pas vraiment convaincu. Après des recherches, j'ai trouver quelques personnes à l'encontre de l'utilisation de pointeur de castings, comme ci-dessus, par exemple, ici et ici.

Voici donc mes questions:

  1. Est-il VRAIMENT sûr de déréférencer le pointeur après la coulée dans un projet immobilier?
  2. Quelle est la différence entre C-style coulée et reinterpret_cast?
  3. Quelle est la différence entre le C et le C++?

43voto

bames53 Points 38303

1. Est-il VRAIMENT sûr de déréférencer le pointeur après la coulée dans un projet immobilier?

Si le pointeur arrive à ne pas être aligné correctement, il peut vraiment causer des problèmes. J'ai personnellement vu et corrigé les erreurs de bus causés par coulée d'un char* plus rigoureusement alignés type. Même si vous n'obtenez pas une erreur évidente, vous pouvez avoir moins de problèmes évidents, comme le ralentissement des performances. En suivant strictement la norme pour éviter UB est une bonne idée, même si vous n'avez pas immédiatement voir les problèmes. (Et la règle du code de rupture est la stricte aliasing règle, § 3.10/10)

Une meilleure alternative est d'utiliser std::memcpy() (ou, mieux encore, bit_cast<>())

unsigned char data[16];
int i1, i2, i3, i4;
std::memcpy(&i1, data     , sizeof(int));
std::memcpy(&i2, data +  4, sizeof(int));
std::memcpy(&i3, data +  8, sizeof(int));
std::memcpy(&i4, data + 12, sizeof(int));

Certains compilateurs de travail plus difficiles que d'autres pour s'assurer des tableaux de char sont alignés de manière plus stricte que nécessaire, car les programmeurs souvent ce mal.

#include <cstdint>
#include <typeinfo>
#include <iostream>

template<typename T> void check_aligned(void *p) {
    std::cout << p << " is " <<
      (0==(reinterpret_cast<std::intptr_t>(p) % alignof(T))?"":"NOT ") <<
      "aligned for the type " << typeid(T).name() << '\n';
}

void foo1() {
    char a;
    char b[sizeof (int)];
    check_aligned<int>(b); // unaligned in clang
}

struct S {
    char a;
    char b[sizeof(int)];
};

void foo2() {
    S s;
    check_aligned<int>(s.b); // unaligned in clang and msvc
}

S s;

void foo3() {
    check_aligned<int>(s.b); // unaligned in clang, msvc, and gcc
}

int main() {
    foo1();
    foo2();
    foo3();
}

http://ideone.com/FFWCjf

2. Quelle est la différence entre C-style coulée et reinterpret_cast?

Il dépend. C-style jette de faire des choses différentes selon les types impliqués. C-style la conversion entre les types de pointeur résultat sera la même chose qu'un reinterpret_cast; Voir § 5.4 une conversion de type Explicite (cast notation) et § 5.2.9-11.

3. Quelle est la différence entre le C et le C++?

Il ne devrait pas être aussi long que vous avez à traiter avec des types qui sont légales C.

29voto

Puppy Points 90818

Ce n'est pas bien, vraiment. L'alignement peut être incorrect et le code peut enfreindre le repliement strict. Vous devriez le décompresser explicitement.

 i1 = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24;
 

etc. Ceci est définitivement un comportement bien défini, et en prime, il est également indépendant de l'endianisme, contrairement à votre distribution de pointeur.

8voto

Art Points 6040

Dans l'exemple que vous montrez ici ce que vous ne sera à l'abri sur presque tous les Processeurs modernes iff la première char pointeur est correctement aligné. En général, ce n'est pas en sécurité et ne pas garanti pour fonctionner.

Si le premier char pointeur n'est pas correctement aligné, ce sera le travail sur les architectures x86 et x86_64, mais peut échouer sur d'autres architectures. Si vous avez de la chance, il va juste vous donner un crash et vous pourrez corriger votre code. Si vous êtes malchanceux, les accès non alignés seront fixées par un piège gestionnaire de votre système d'exploitation et vous aurez terrible de performance sans avoir tout évident commentaires sur pourquoi c'est si lent (nous parlons de glaciers lent pour certains code, c'est un énorme problème sur alpha il y a 20 ans).

Même sur x86 & co, non alignés, l'accès sera plus lent.

Si vous voulez être sûr d'aujourd'hui et à l'avenir, memcpy au lieu de faire de la cession de ce genre. Moderne complier auront probablement des optimisations pour l' memcpy et de faire la bonne chose et si non, memcpy lui-même aurez l'alignement de détection et fera de la manière la plus rapide de la chose.

Aussi, votre exemple est mauvais sur un point: sizeof(int) n'est pas toujours 4.

6voto

ecatmur Points 64173

La bonne façon de décompresser char données mises en mémoire tampon est d'utiliser memcpy:

unsigned char data[4 * sizeof(int)];
int i1, i2, i3, i4;
memcpy(&i1, data, sizeof(int));
memcpy(&i2, data + sizeof(int), sizeof(int));
memcpy(&i3, data + 2 * sizeof(int), sizeof(int));
memcpy(&i4, data + 3 * sizeof(int), sizeof(int));

Casting viole aliasing, ce qui signifie que le compilateur et l'optimiseur sont libres de traiter la source de l'objet comme non initialisée.

Concernant vos 3 questions:

  1. Non, la référence à un casting pointeur est en général dangereux, à cause de l'aliasing et de l'alignement.
  2. Non, en C++, C-style casting est définie en termes de reinterpret_cast.
  3. Non, le C et le C++ d'accord sur la fonte de base de l'aliasing. Il y a une différence dans le traitement de l'union aliasing (C) permet dans certains cas; C++ ne prend pas).

2voto

didierc Points 8128

Mise à jour: J'ai négligé le fait que, de fait, plus petits types peuvent être non alignés relativement à une plus grande, comme il peut être dans votre exemple. Vous pouvez aleviate cette question par l'inversion de la façon dont vous avez jeté votre tableau : déclarer votre tableau comme un tableau de int, et jette - char * quand vous avez besoin d'y accéder de cette façon.

// raw data consists of 4 ints
int data[4];

// here's the char * to the original data
char *cdata = (char *)data;
// now we can recast it safely to int *
i1 = *((int*)cdata);
i2 = *((int*)(cdata + sizeof(int)));
i3 = *((int*)(cdata + sizeof(int) * 2));
i4 = *((int*)(cdata + sizeof(int) * 3));

Il n'y aura aucun problème sur le tableau de primitives types. Les questions de l'alignement se produire lorsque vous traitez avec des tableaux de données structurées (struct en C), si l'original primitve type du tableau est plus grand que celui qu'il est coulé à, voir la mise à jour ci-dessus.

Il doit être parfaitement ok pour lancer un tableau de char à un tableau de int, à condition de remplacer le décalage de 4 avec sizeof(int), en fonction de la taille des int sur la plate-forme le code est censé fonctionner sur.

// raw data consists of 4 ints
unsigned char data[4 * sizeof(int)];
int i1, i2, i3, i4;
i1 = *((int*)data);
i2 = *((int*)(data + sizeof(int)));
i3 = *((int*)(data + sizeof(int) * 2));
i4 = *((int*)(data + sizeof(int) * 3));

Notez que vous aurez endianness questions uniquement si vous partagez des données en quelque sorte d'une plateforme à une autre avec un autre octet de commande. Sinon, il doit être parfaitement bien.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X