2008-11-04 18 views
23

J'ai essayé de comprendre les règles d'alias strictes telles qu'elles s'appliquent au pointeur char.Quand char * est-il sûr pour un alias de pointeur strict?

Here c'est dit:

Il est toujours présumé qu'un char * peut se référer à un alias de tout objet.

Ok dans le contexte du code socket, je peux le faire:

struct SocketMsg 
{ 
    int a; 
    int b; 
}; 

int main(int argc, char** argv) 
{ 
    // Some code... 
    SocketMsg msgToSend; 
    msgToSend.a = 0; 
    msgToSend.b = 1; 
    send(socket, (char*)(&msgToSend), sizeof(msgToSend); 
}; 

Mais il y a cette déclaration

L'inverse est pas vrai. Envoyer un char * à un pointeur de n'importe quel type autre qu'un char * et le déréférencer est généralement en violation de la règle stricte d'aliasing.

Est-ce que cela signifie que lorsque je RECV un tableau de caractères, je ne peux pas réinterprètent casting à une struct quand je connais la structure du message:

struct SocketMsgToRecv 
{ 
    int a; 
    int b; 
}; 

int main() 
{ 
    SocketMsgToRecv* pointerToMsg; 
    char msgBuff[100]; 
    ... 
    recv(socket, msgBuff, 100); 
    // Ommiting make sure we have a complete message from the stream 
    // but lets assume msgBuff[0] has a complete msg, and lets interpret the msg 

    // SAFE!?!?!? 
    pointerToMsg = &msgBuff[0]; 

    printf("Got Msg: a: %i, b: %i", pointerToMsg->a, pointerToMsg->b); 
} 

ce deuxième exemple ne fonctionnera pas car la type de base est un tableau char et je le lance à une structure? Comment gérez-vous cette situation dans un monde strictement aliasé?

+0

Le deuxième morceau de code ne vous oblige pas à casr explicitement? Avez-vous activé tous les avertissements? –

Répondre

6

Correct, le deuxième exemple est en violation des règles strictes d'aliasing, donc si vous compilez avec le drapeau -fstrict-aliasing, vous risquez d'obtenir un code objet incorrect. La solution tout à fait correcte serait d'utiliser une union ici:

union 
{ 
    SocketMsgToRecv msg; 
    char msgBuff[100]; 
}; 

recv(socket, msgBuff, 100); 

printf("Got Msg: a: %i, b: %i", msg.a, msg.b); 
+1

Est-ce conforme à la norme ou juste au compilateur vous permettant d'écrire à un membre et d'en lire un autre? –

+9

L'union est complètement inutile. Passez simplement un pointeur sur la structure (transtypé en char * ') en' recv'. –

+0

Notez que '-fstrict-aliasing' est activé par défaut à' -O2' et plus haut dans gcc –

7

Re @ Adam Rosenfield: Le syndicat atteindra l'alignement tant que fournisseur de char * a commencé à faire quelque chose de similaire.

Il peut être utile de prendre du recul et de comprendre de quoi il s'agit. La base de la règle d'aliasing est le fait que les compilateurs peuvent placer des valeurs de différents types simples sur des limites de mémoire différentes pour améliorer l'accès et que le matériel peut dans certains cas exiger un tel alignement pour pouvoir utiliser le pointeur. Cela peut également apparaître dans les structures où il y a une variété d'éléments de tailles différentes. La structure peut être démarrée sur une bonne limite. De plus, le compilateur peut toujours introduire des mors lents à l'intérieur de la structure pour accomplir l'alignement correct des éléments de structure qui le requièrent. Considérant que les compilateurs ont souvent des options pour contrôler comment tout cela est traité ou non, vous pouvez voir qu'il existe de nombreuses façons que des surprises peuvent se produire. Cela est particulièrement important à prendre en compte lors de la transmission de pointeurs vers des structures (cast en tant que char * ou non) dans des bibliothèques qui ont été compilées pour attendre différentes conventions d'alignement.

Qu'en est-il de char *?

La présomption à propos de char * est que sizeof (char) == 1 (par rapport aux tailles de toutes les autres données de taille) et que les pointeurs char * n'ont aucune exigence d'alignement. Ainsi, un véritable caractère * peut toujours être transmis et utilisé avec succès sans souci d'alignement, et cela vaut pour tout élément d'un tableau char [], effectuant ++ et - sur les pointeurs, et ainsi de suite. (Bizarrement, void * n'est pas tout à fait la même chose.Maintenant, vous devriez pouvoir voir comment, si vous transférez des données de structure dans un tableau char [] qui n'était pas lui-même correctement aligné, essayez de retourner à un pointeur qui nécessite un alignement. Un sérieux problème.

Si vous faites une union d'un tableau char [] et d'une structure, l'alignement le plus exigeant (c'est-à-dire celui de la structure) sera honoré par le compilateur. Cela fonctionnera si le fournisseur et le consommateur utilisent effectivement des unions correspondantes, de sorte que la conversion de struct * en char * et back marche bien.

Dans ce cas, j'espère que les données ont été créées dans une union similaire avant que le pointeur vers lui a été transtypé en char * ou qu'il a été transféré de toute autre manière en tant que tableau de sizeof (char) octets. Il est également important de s'assurer que toutes les options du compilateur sont compatibles entre les bibliothèques et votre propre code.

+0

sont tous 3 de 'char',' signed char', 'unsigned char' OK pour l'aliasing? et avec n'importe quelle combinaison CV-qualification? –

+2

Les règles d'alias n'ont rien à voir avec l'alignement. Selon la logique C89, des déclarations globales données comme 'int i; float * fp; ', le but est de permettre aux compilateurs de garder" i "dans un registre à travers les accès à' * fp'. L'idée était qu'un compilateur ne devrait pas avoir à supposer de manière pessimiste qu'une écriture dans '* fp' pourrait altérer' i' * quand il n'avait aucune raison de s'attendre à ce que * fp' pointe sur quelque chose qui n'était pas '' float' *. Je ne pense pas que la règle ait jamais été conçue pour laisser les compilateurs ignorer les cas où l'alias est évident (prendre l'adresse d'un objet devrait donner un indice fort au compilateur ... – supercat

+0

... que l'objet en question est sur le point d'être accédé via un pointeur, et le lancement d'un 'int *' dans un 'float *' devrait donner au compilateur une indication forte qu'un 'int' est susceptible d'être modifié via write à un' float * ', mais gcc ne ressent plus aucune obligation de remarquez de telles choses – supercat