web-dev-qa-db-fra.com

Correction du déréférencement du pointeur punencé par type pour rompre l'aliasing strict

J'essaie de corriger deux avertissements lors de la compilation d'un programme spécifique à l'aide de GCC. Les avertissements sont les suivants:

avertissement: le déréférencement d'un pointeur avec punition de type violera les règles strictes d'alias [-Wstrict-aliasing]

et les deux coupables sont:

unsigned int received_size = ntohl (*((unsigned int*)dcc->incoming_buf));

et

*((unsigned int*)dcc->outgoing_buf) = htonl (dcc->file_confirm_offset);

entrant_buf et outgoing_buf sont définis comme suit:

char                    incoming_buf[LIBIRC_DCC_BUFFER_SIZE];

char                    outgoing_buf[LIBIRC_DCC_BUFFER_SIZE];

Cela semble subtilement différent des autres exemples de cet avertissement que j'ai examinés. Je préfère résoudre le problème plutôt que de désactiver les vérifications d'alias strict.

Il y a eu de nombreuses suggestions pour utiliser un syndicat - quel pourrait être un syndicat approprié dans ce cas?

24
BlankFrank

Tout d'abord, examinons pourquoi vous obtenez les avertissements de violation d'alias.

Règles d'alias dites simplement que vous ne pouvez accéder à un objet que par son propre type, son type variant signé/non signé, ou via un type de caractère (char, signed char, unsigned char).

C dit que la violation des règles d'alias appelle un comportement indéfini ( alors ne le faites pas! ).

Dans cette ligne de votre programme:

unsigned int received_size = ntohl (*((unsigned int*)dcc->incoming_buf));

bien que les éléments du tableau incoming_buf soient de type char, vous y accédez en tant que unsigned int. En effet le résultat de l'opérateur de déréférencement dans l'expression *((unsigned int*)dcc->incoming_buf) est de type unsigned int.

Il s'agit d'une violation des règles d'alias, car vous avez uniquement le droit d'accéder aux éléments du tableau incoming_buf Via (voir le résumé des règles ci-dessus!) char, signed char Ou unsigned char.

Notez que vous avez exactement le même problème d'alias dans votre deuxième coupable:

*((unsigned int*)dcc->outgoing_buf) = htonl (dcc->file_confirm_offset);

Vous accédez aux éléments char de outgoing_buf À unsigned int, Il s'agit donc d'une violation d'alias.

Solution proposée

Pour résoudre votre problème, vous pouvez essayer de définir les éléments de vos tableaux directement dans le type auquel vous souhaitez accéder:

unsigned int incoming_buf[LIBIRC_DCC_BUFFER_SIZE / sizeof (unsigned int)];
unsigned int outgoing_buf[LIBIRC_DCC_BUFFER_SIZE / sizeof (unsigned int)];

(Au fait, la largeur de unsigned int Est définie par l'implémentation, vous devriez donc envisager d'utiliser uint32_t Si votre programme suppose que unsigned int Est de 32 bits).

De cette façon, vous pouvez stocker des objets unsigned int Dans votre tableau sans violer les règles d'alias en accédant à l'élément via le type char, comme ceci:

*((char *) outgoing_buf) =  expr_of_type_char;

ou

char_lvalue = *((char *) incoming_buf);

MODIFIER:

J'ai entièrement retravaillé ma réponse, en particulier j'explique pourquoi le programme obtient les avertissements d'aliasing du compilateur.

40
ouah

Pour résoudre le problème, ne jeu de mots et alias! La seule façon "correcte" de lire un type T est d'allouer un type T et de remplir sa représentation si nécessaire:

uint32_t n;
memcpy(&n, dcc->incoming_buf, 4);

En bref: si vous voulez un entier, vous devez en faire un entier. Il n'y a aucun moyen de tromper cela d'une manière tolérée par la langue.

La seule conversion de pointeur qui vous est autorisée (aux fins des E/S, généralement) est de traiter l'adresse de ne variable existante de type T comme un char* , ou plutôt, comme le pointeur sur le premier élément d'un tableau de caractères de taille sizeof(T).

21
Kerrek SB
union
{
    const unsigned int * int_val_p;
    const char* buf;
} xyz;

xyz.buf = dcc->incoming_buf;
unsigned int received_size = ntohl(*(xyz.int_val_p));

Explication simplifiée 1. La norme c ++ stipule que vous devez essayer d'aligner les données vous-même, g ++ fait un effort supplémentaire pour générer des avertissements sur le sujet. 2. vous ne devriez l'essayer que si vous comprenez parfaitement l'alignement des données sur votre architecture/système et à l'intérieur de votre code (par exemple, le code ci-dessus est une chose sûre sur Intel 32/64; alignement 1; Win/Linux/Bsd/Mac) 3. la seule raison pratique d'utiliser le code ci-dessus est d'éviter les avertissements du compilateur, QUAND et SI vous savez ce que vous faites

4
Real Name

Si vous avez des raisons qui ne vous permettent pas de changer le type d'objet source (comme c'était le cas dans mon cas) et que vous êtes absolument sûr que le code est correct et qu'il fait ce que vous vouliez faire avec ce tableau de caractères, pour éviter les avertissements peut effectuer les opérations suivantes:

unsigned int* buf = (unsigned int*)dcc->incoming_buf;
unsigned int received_size = ntohl (*buf);
0
Oleg Oleg

Si vous me le permettez, à mon humble avis, dans ce cas, le problème est la conception des API ntohl et htonl et des fonctions connexes. Ils n'auraient pas dû être écrits comme argument numérique avec retour numérique. (et oui, je comprends le point d'optimisation des macros) Ils auraient dû être conçus comme le côté 'n' étant un pointeur vers un tampon. Lorsque cela est fait, tout le problème disparaît et la routine est précise quel que soit l'endian de l'hôte. Par exemple (sans tentative d'optimisation):

inline void safe_htonl(unsigned char *netside, unsigned long value) {
    netside[3] = value & 0xFF;
    netside[2] = (value >> 8) & 0xFF;
    netside[1] = (value >> 16) & 0xFF;
    netside[0] = (value >> 24) & 0xFF;
};
0
Henri Socha