Lorsqu'on pose des questions sur un comportement non défini commun en C , les gens se réfèrent parfois à la règle stricte d'alias.
De quoi parlent-ils?
805
Lorsqu'on pose des questions sur un comportement non défini commun en C , les gens se réfèrent parfois à la règle stricte d'alias.
De quoi parlent-ils?
c
etc++faq
.Réponses:
Une situation typique où vous rencontrez des problèmes d'alias stricts est lors de la superposition d'une structure (comme un périphérique / réseau msg) sur un tampon de la taille de mot de votre système (comme un pointeur vers
uint32_t
s ouuint16_t
s). Lorsque vous superposez une structure sur un tel tampon, ou un tampon sur une telle structure via la conversion de pointeur, vous pouvez facilement violer des règles d'aliasing strictes.Donc, dans ce type de configuration, si je veux envoyer un message à quelque chose, je devrais avoir deux pointeurs incompatibles pointant vers le même bloc de mémoire. Je pourrais alors naïvement coder quelque chose comme ça (sur un système avec
sizeof(int) == 2
):La règle d'alias stricte rend cette configuration illégale: déréférencer un pointeur qui alias un objet qui n'est pas d'un type compatible ou l'un des autres types autorisés par C 2011 6.5 paragraphe 7 1 est un comportement non défini. Malheureusement, vous pouvez toujours coder de cette façon, peut - être obtenir des avertissements, le faire compiler correctement, seulement pour avoir un comportement inattendu étrange lorsque vous exécutez le code.
(GCC semble quelque peu incohérent dans sa capacité à donner des avertissements de pseudonyme, nous donnant parfois un avertissement amical et parfois non.)
Pour voir pourquoi ce comportement n'est pas défini, nous devons réfléchir à ce que la règle d'aliasing stricte achète le compilateur. Fondamentalement, avec cette règle, il n'a pas à penser à insérer des instructions pour actualiser le contenu de
buff
chaque exécution de la boucle. Au lieu de cela, lors de l'optimisation, avec certaines hypothèses ennuyeusement non appliquées sur l'alias, il peut omettre ces instructions, chargerbuff[0]
etbuff[1
] dans les registres du processeur une fois avant l'exécution de la boucle et accélérer le corps de la boucle. Avant l'introduction d'un aliasing strict, le compilateur devait vivre dans un état de paranoïa dont le contenubuff
pouvait changer à tout moment et de n'importe où par n'importe qui. Donc, pour obtenir un avantage supplémentaire en termes de performances et en supposant que la plupart des gens ne tapent pas de pointeurs de pun, la règle d'aliasing stricte a été introduite.Gardez à l'esprit, si vous pensez que l'exemple est artificiel, cela peut même arriver si vous passez un tampon à une autre fonction effectuant l'envoi pour vous, si vous l'avez à la place.
Et réécrit notre boucle précédente pour profiter de cette fonction pratique
Le compilateur peut ou non être en mesure ou suffisamment intelligent pour essayer d'inclure SendMessage et il peut ou non décider de charger ou de ne pas charger à nouveau buff. Si
SendMessage
fait partie d'une autre API compilée séparément, elle contient probablement des instructions pour charger le contenu de buff. Là encore, vous êtes peut-être en C ++ et il s'agit d'une implémentation basée uniquement sur un modèle que le compilateur pense pouvoir incorporer. Ou peut-être que c'est juste quelque chose que vous avez écrit dans votre fichier .c pour votre propre convenance. Quoi qu'il en soit, un comportement indéfini pourrait encore se produire. Même lorsque nous savons ce qui se passe sous le capot, c'est toujours une violation de la règle, donc aucun comportement bien défini n'est garanti. Donc, simplement en encapsulant une fonction qui prend notre tampon délimité par des mots n'aide pas nécessairement.Alors, comment puis-je contourner cela?
Utilisez un syndicat. La plupart des compilateurs prennent en charge cela sans se plaindre d'un alias strict. Ceci est autorisé en C99 et explicitement autorisé en C11.
Vous pouvez désactiver l'alias strict dans votre compilateur ( f [no-] strict-aliasing dans gcc))
Vous pouvez utiliser l'
char*
alias au lieu du mot de votre système. Les règles autorisent une exception pourchar*
(y comprissigned char
etunsigned char
). Il est toujours supposé que leschar*
alias d'autres types. Cependant, cela ne fonctionnera pas dans l'autre sens: il n'y a pas d'hypothèse que votre structure alias un tampon de caractères.Attention aux débutants
Ce n'est qu'un champ de mines potentiel lors de la superposition de deux types l'un sur l'autre. Vous devriez également vous renseigner sur l' endianité , l' alignement des mots et la façon de traiter les problèmes d'alignement via correctement les des structures d'emballage .
note de bas de page
1 Les types auxquels C 2011 6.5 7 permet à une valeur d'accéder sont:
la source
unsigned char*
être être utilisé à lachar*
place? J'ai tendance à utiliserunsigned char
plutôt quechar
comme type sous-jacentbyte
car mes octets ne sont pas signés et je ne veux pas l'étrangeté du comportement signé (notamment wrt to overflow)unsigned char *
est acceptable.uint32_t* buff = malloc(sizeof(Msg));
et lesunsigned int asBuffer[sizeof(Msg)];
déclarations de tampon d' union suivantes auront des tailles différentes et aucune n'est correcte. L'malloc
appel repose sur l'alignement de 4 octets sous le capot (ne le faites pas) et l'union sera 4 fois plus grande qu'elle ne devrait l'être ... Je comprends que c'est pour plus de clarté mais cela me dérange quand même - moins ...La meilleure explication que j'ai trouvée est de Mike Acton, Understanding Strict Aliasing . Il se concentre un peu sur le développement de la PS3, mais c'est essentiellement GCC.
De l'article:
Donc, fondamentalement, si vous avez un
int*
pointage vers une mémoire contenant unint
, puis que vous pointez unfloat*
vers cette mémoire et quefloat
vous l' utilisez comme vous enfreignez la règle. Si votre code ne respecte pas cela, alors l'optimiseur du compilateur cassera très probablement votre code.L'exception à la règle est un
char*
, qui est autorisé à pointer vers n'importe quel type.la source
Il s'agit de la règle d'alias stricte, trouvée dans la section 3.10 de la norme C ++ 03 (d'autres réponses fournissent une bonne explication, mais aucune n'a fourni la règle elle-même):
Formulation C ++ 11 et C ++ 14 (changements mis en évidence):
Deux changements étaient mineurs : glvalue au lieu de lvalue et clarification du cas d'agrégation / d'union.
Le troisième changement apporte une garantie plus forte (assouplit la règle de l'alias fort): le nouveau concept de types similaires qui sont désormais sûrs pour l'alias.
Aussi le libellé C (C99; ISO / IEC 9899: 1999 6.5 / 7; le même libellé exact est utilisé dans ISO / IEC 9899: 2011 §6.5 §7):
la source
wow(&u->s1,&u->s2)
devrait être légal même lorsqu'un pointeur est utilisé pour modifieru
, et cela annulerait la plupart des optimisations que le règle d'aliasing a été conçue pour faciliter.Remarque
Ceci est extrait de mon "Qu'est-ce que la règle de repliement strict et pourquoi nous en soucions-nous?"rédiger.
Qu'est-ce qu'un aliasing strict?
En C et C ++, l'aliasing a à voir avec quels types d'expression nous sommes autorisés à accéder aux valeurs stockées. En C et C ++, la norme spécifie quels types d'expression sont autorisés à alias quels types. Le compilateur et l'optimiseur sont autorisés à supposer que nous suivons strictement les règles d'alias, d'où le terme de règle d'alias stricte . Si nous tentons d'accéder à une valeur en utilisant un type non autorisé, elle est classée comme comportement non défini ( UB ). Une fois que nous avons un comportement indéfini, tous les paris sont désactivés, les résultats de notre programme ne sont plus fiables.
Malheureusement, avec des violations d'alias strictes, nous obtiendrons souvent les résultats escomptés, laissant la possibilité qu'une future version d'un compilateur avec une nouvelle optimisation casse le code que nous pensions être valide. Ce n'est pas souhaitable et c'est un objectif valable de comprendre les règles strictes d'alias et comment éviter de les violer.
Pour mieux comprendre pourquoi nous nous soucions, nous discuterons des problèmes qui surviennent lors de la violation de règles d'aliasing strictes, de la punition de type car les techniques courantes utilisées dans la punition de type violent souvent les règles d'aliasing strictes et comment taper correctement pun.
Exemples préliminaires
Regardons quelques exemples, puis nous pourrons parler exactement de ce que disent les normes, examiner d'autres exemples et voir comment éviter l'aliasing strict et les violations de capture que nous avons manquées. Voici un exemple qui ne devrait pas surprendre ( exemple en direct ):
Nous avons un int * pointant vers la mémoire occupée par un int et ceci est un aliasing valide. L'optimiseur doit supposer que les affectations via ip pourraient mettre à jour la valeur occupée par x .
L'exemple suivant montre un alias qui conduit à un comportement indéfini ( exemple en direct ):
Dans la fonction foo, nous prenons un int * et un float * , dans cet exemple, nous appelons foo et définissons les deux paramètres pour pointer vers le même emplacement mémoire qui dans cet exemple contient un int . Remarque, le reinterpret_cast indique au compilateur de traiter l'expression comme si elle avait le type spécifié par son paramètre de modèle. Dans ce cas, nous lui disons de traiter l'expression & x comme si elle avait le type float * . Nous pouvons naïvement nous attendre à ce que le résultat du deuxième cout soit 0, mais avec l'optimisation activée en utilisant -O2, gcc et clang produisent le résultat suivant:
Ce qui n'est peut-être pas prévu, mais est parfaitement valide car nous avons invoqué un comportement indéfini. Un flottant ne peut pas alias valablement un objet int . Par conséquent, l'optimiseur peut supposer que la constante 1 stockée lorsque le déréférencement i sera la valeur de retour puisqu'un magasin via f n'a pas pu affecter valablement un objet int . Brancher le code dans l'Explorateur de compilateur montre que c'est exactement ce qui se passe ( exemple en direct ):
L'optimiseur utilisant l' analyse d'alias basée sur le type (TBAA) suppose que 1 sera renvoyé et déplace directement la valeur constante dans le registre eax qui contient la valeur de retour. TBAA utilise les règles de langues sur les types autorisés à alias pour optimiser les chargements et les magasins. Dans ce cas, TBAA sait qu'un float ne peut pas alias et int et optimise la charge de i .
Maintenant, au livre de règles
Que dit exactement la norme que nous sommes autorisés et non autorisés à le faire? Le langage standard n'est pas simple, donc pour chaque élément, je vais essayer de fournir des exemples de code qui en démontrent le sens.
Que dit la norme C11?
La norme C11 dit ce qui suit dans la section 6.5 Expressions, paragraphe 7 :
gcc / clang a une extension et qui permet également d' assigner int * non signé à int * même s'il ne s'agit pas de types compatibles.
Ce que dit le projet de norme C ++ 17
Le projet de norme C ++ 17 dans la section [basic.lval] paragraphe 11 dit:
Il convient de noter que le caractère signé n'est pas inclus dans la liste ci-dessus, c'est une différence notable par rapport à C qui dit un type de caractère .
Qu'est-ce que le type Punning
Nous sommes arrivés à ce point et nous nous demandons peut-être pourquoi voudrions-nous nous alias? La réponse est généralement de taper calembour , souvent les méthodes utilisées violent les règles strictes d'alias.
Parfois, nous voulons contourner le système de types et interpréter un objet comme un type différent. C'est ce qu'on appelle le type punning , pour réinterpréter un segment de mémoire comme un autre type. Le repérage de type est utile pour les tâches qui souhaitent accéder à la représentation sous-jacente d'un objet à visualiser, transporter ou manipuler. Les domaines typiques que nous trouvons le type punning utilisé sont les compilateurs, la sérialisation, le code réseau, etc.
Traditionnellement, cela a été accompli en prenant l'adresse de l'objet, en le convertissant en un pointeur du type auquel nous voulons le réinterpréter, puis en accédant à la valeur, ou en d'autres termes par un aliasing. Par exemple:
Comme nous l'avons vu précédemment, ce n'est pas un aliasing valide, nous invoquons donc un comportement non défini. Mais traditionnellement, les compilateurs ne profitaient pas de règles d'alias strictes et ce type de code fonctionnait généralement bien, les développeurs se sont malheureusement habitués à faire les choses de cette façon. Une méthode alternative courante pour la punition de type consiste à utiliser les unions, ce qui est valide en C mais un comportement non défini en C ++ ( voir l'exemple en direct ):
Cela n'est pas valide en C ++ et certains considèrent que le but des unions est uniquement d'implémenter des types de variantes et estiment que l'utilisation des unions pour le punning de type est un abus.
Comment pouvons-nous taper Pun correctement?
La méthode standard pour la punition de type en C et C ++ est memcpy . Cela peut sembler un peu lourd, mais l'optimiseur doit reconnaître l'utilisation de memcpy pour le type punning et l'optimiser et générer un registre pour enregistrer le mouvement. Par exemple, si nous savons que int64_t a la même taille que double :
nous pouvons utiliser memcpy :
À un niveau d'optimisation suffisant, tout compilateur moderne décent génère un code identique à la méthode reinterpret_cast ou la méthode d' union mentionnée précédemment pour le découpage de type . En examinant le code généré, nous voyons qu'il utilise simplement register mov ( exemple de l'explorateur de compilation en direct ).
C ++ 20 et bit_cast
En C ++ 20, nous pouvons gagner bit_cast ( implémentation disponible dans le lien de la proposition ) qui donne un moyen simple et sûr de taper-pun ainsi que d'être utilisable dans un contexte constexpr.
Ce qui suit est un exemple d'utilisation de bit_cast pour taper pun un entier non signé à flotter ( voir en direct ):
Dans le cas où les types To et From n'ont pas la même taille, il nous faut utiliser une structure intermédiaire15. Nous utiliserons une structure contenant un tableau de caractères sizeof (int non signé) ( suppose un entier non signé de 4 octets ) comme étant le type From et un entier non signé comme type To :
Il est regrettable que nous ayons besoin de ce type intermédiaire mais c'est la contrainte actuelle de bit_cast .
Détecter les violations d'alias strictes
Nous n'avons pas beaucoup de bons outils pour intercepter l'aliasing strict en C ++, les outils dont nous disposons vont détecter certains cas de violations d'aliasing strictes et certains cas de chargements et de magasins mal alignés.
gcc utilisant l'indicateur -fstrict-aliasing et -Wstrict-aliasing peut intercepter certains cas, mais pas sans faux positifs / négatifs. Par exemple, les cas suivants généreront un avertissement dans gcc ( voir en direct ):
bien qu'il n'attrapera pas ce cas supplémentaire ( voir en direct ):
Bien que clang autorise ces indicateurs, il n'applique apparemment pas les avertissements.
Un autre outil dont nous disposons est ASan qui peut capturer des charges et des magasins mal alignés. Bien qu'il ne s'agisse pas directement de violations d'alias strictes, elles sont le résultat commun de violations d'alias strictes. Par exemple, les cas suivants génèrent des erreurs d'exécution lorsqu'ils sont créés avec clang en utilisant -fsanitize = adresse
Le dernier outil que je recommanderai est spécifique au C ++ et pas strictement un outil mais une pratique de codage, n'autorisez pas les conversions de style C. Gcc et clang produiront un diagnostic pour les modèles de style C à l'aide de -Wold-style-cast . Cela forcera tous les jeux de mots de type non défini à utiliser reinterpret_cast, en général, reinterpret_cast devrait être un indicateur pour une révision plus approfondie du code. Il est également plus facile de rechercher dans votre base de code reinterpret_cast pour effectuer un audit.
Pour C, nous avons tous les outils déjà couverts et nous avons également tis-interpreter, un analyseur statique qui analyse de manière exhaustive un programme pour un grand sous-ensemble du langage C. Étant donné un C verions de l'exemple précédent où l'utilisation de -fstrict-aliasing manque un cas ( voir en direct )
tis-interpeter est capable d'attraper les trois, l'exemple suivant appelle tis-kernal comme tis-interpreter (la sortie est éditée pour plus de concision):
Enfin, il y a TySan qui est actuellement en développement. Cet assainisseur ajoute des informations de vérification de type dans un segment de mémoire fantôme et vérifie les accès pour voir s'ils violent les règles d'alias. L'outil devrait potentiellement être en mesure de détecter toutes les violations d'alias, mais il peut avoir un gros temps d'exécution.
la source
reinterpret_cast
pourrait faire ou de ce quicout
pourrait signifier. (Il est bon de mentionner C ++ mais la question initiale portait sur C et IIUC, ces exemples pourraient tout aussi bien être écrits en C.)L'aliasing strict ne fait pas uniquement référence aux pointeurs, il affecte également les références, j'ai écrit un article à ce sujet pour le wiki du développeur boost et il a été si bien reçu que je l'ai transformé en une page sur mon site Web de consultation. Il explique complètement ce que c'est, pourquoi il confond tellement les gens et ce qu'il faut faire à ce sujet. Livre blanc sur l'alias strict . En particulier, il explique pourquoi les unions sont un comportement risqué pour C ++ et pourquoi l'utilisation de memcpy est le seul correctif portable à la fois en C et C ++. J'espère que cela vous sera utile.
la source
En complément de ce que Doug T. a déjà écrit, voici un cas de test simple qui le déclenche probablement avec gcc:
check.c
Compilez avec
gcc -O2 -o check check.c
. Habituellement (avec la plupart des versions de gcc que j'ai essayées), cela génère un "problème d'alias strict", car le compilateur suppose que "h" ne peut pas être la même adresse que "k" dans la fonction "check". Pour cette raison, le compilateur optimise leif (*h == 5)
away et appelle toujours le printf.Pour ceux qui sont intéressés, voici le code assembleur x64, produit par gcc 4.6.3, fonctionnant sur ubuntu 12.04.2 pour x64:
Ainsi, la condition if a complètement disparu du code assembleur.
la source
long long*
etint64_t
*). On pourrait s'attendre à ce qu'un compilateur sensé reconnaisse que along long*
etint64_t*
puisse accéder au même stockage s'ils sont stockés de manière identique, mais un tel traitement n'est plus à la mode.La punition de type via des transtypages de pointeurs (par opposition à l'utilisation d'une union) est un exemple majeur de rupture de l'aliasing strict.
la source
fpsync()
directive entre l'écriture comme fp et la lecture comme int ou vice versa [sur les implémentations avec des pipelines et des caches FPU et entiers séparés , une telle directive pourrait être coûteuse, mais pas aussi coûteuse que de demander au compilateur d'effectuer une telle synchronisation sur chaque accès à l'union]. Ou une implémentation pourrait spécifier que la valeur résultante ne sera jamais utilisable sauf dans des circonstances utilisant des séquences initiales communes.Selon la justification C89, les auteurs de la norme ne voulaient pas exiger que les compilateurs reçoivent un code comme:
devrait être requis pour recharger la valeur de
x
entre l'affectation et l'instruction return afin de tenir compte de la possibilité quip
pourrait pointer versx
, et l'affectation à*p
pourrait par conséquent modifier la valeur dex
. L'idée qu'un compilateur devrait avoir le droit de présumer qu'il n'y aura pas d'alias dans des situations comme celles ci-dessus n'était pas controversée.Malheureusement, les auteurs du C89 ont écrit leur règle d'une manière qui, si elle était lue littéralement, inciterait même la fonction suivante à invoquer un comportement indéfini:
car il utilise une valeur l de type
int
pour accéder à un objet de typestruct S
etint
ne fait pas partie des types pouvant être utilisés pour accéder à astruct S
. Parce qu'il serait absurde de traiter toute utilisation de membres non structurés de structures et d'unions comme un comportement indéfini, presque tout le monde reconnaît qu'il existe au moins certaines circonstances où une valeur l d'un type peut être utilisée pour accéder à un objet d'un autre type . Malheureusement, le Comité des normes C n'a pas défini quelles sont ces circonstances.Une grande partie du problème est le résultat du rapport de défaut # 028, qui a posé des questions sur le comportement d'un programme comme:
Le rapport de défauts # 28 indique que le programme appelle un comportement indéfini car l'action d'écrire un membre d'union de type "double" et de lire un membre de type "int" appelle un comportement défini par l'implémentation. Un tel raisonnement n'a pas de sens, mais constitue la base des règles de type effectif qui compliquent inutilement le langage tout en ne faisant rien pour résoudre le problème d'origine.
La meilleure façon de résoudre le problème d'origine serait probablement de traiter la note de bas de page sur le but de la règle comme si elle était normative et de la rendre inapplicable, sauf dans les cas qui impliquent réellement des accès conflictuels à l'aide d'alias. Étant donné quelque chose comme:
Il n'y a pas de conflit à l'intérieur
inc_int
parce que tous les accès au stockage accédé via*p
se font avec une valeur de type lint
, et il n'y a pas de conflittest
parce qu'ilp
est visiblement dérivé d'unstruct S
, et à la prochaines
utilisation, tous les accès à ce stockage qui seront jamais effectués à traversp
aura déjà eu lieu.Si le code a été légèrement modifié ...
Ici, il existe un conflit d'alias entre
p
et l'accès às.x
la ligne marquée car à ce stade de l'exécution, il existe une autre référence qui sera utilisée pour accéder au même stockage .Si le rapport de défaut 028 avait indiqué que l'exemple d'origine invoquait UB en raison du chevauchement entre la création et l'utilisation des deux pointeurs, cela aurait rendu les choses beaucoup plus claires sans avoir à ajouter des "types efficaces" ou une autre complexité de ce type.
la source
Après avoir lu bon nombre des réponses, je ressens le besoin d'ajouter quelque chose:
Un aliasing strict (que je décrirai un peu) est important car :
L'accès à la mémoire peut être coûteux (en termes de performances), c'est pourquoi les données sont manipulées dans les registres du processeur avant d'être réécrites dans la mémoire physique.
Si les données de deux registres CPU différents seront écrites dans le même espace mémoire, nous ne pouvons pas prédire quelles données "survivront" lorsque nous coderons en C.
En assemblage, où nous codons le chargement et le déchargement des registres CPU manuellement, nous saurons quelles données restent intactes. Mais C (heureusement) résume ce détail.
Étant donné que deux pointeurs peuvent pointer vers le même emplacement dans la mémoire, cela peut entraîner un code complexe qui gère les collisions possibles .
Ce code supplémentaire est lent et nuit aux performances car il effectue des opérations de lecture / écriture de mémoire supplémentaires qui sont à la fois plus lentes et (éventuellement) inutiles.
La règle d'alias stricte nous permet d'éviter le code machine redondant dans les cas où il devrait être sûr de supposer que deux pointeurs ne pointent pas vers le même bloc de mémoire (voir aussi le
restrict
mot - clé).L'aliasing strict indique qu'il est sûr de supposer que les pointeurs vers différents types pointent vers différents emplacements dans la mémoire.
Si un compilateur remarque que deux pointeurs pointent vers des types différents (par exemple, un
int *
et unfloat *
), il supposera que l'adresse mémoire est différente et ne protégera pas contre les collisions d'adresses mémoire, ce qui accélérera le code machine.Par exemple :
Supposons la fonction suivante:
Afin de gérer le cas dans lequel
a == b
(les deux pointeurs pointent vers la même mémoire), nous devons ordonner et tester la façon dont nous chargeons les données de la mémoire vers les registres du processeur, de sorte que le code puisse se retrouver comme suit:charger
a
etb
de la mémoire.ajouter
a
àb
.enregistrer
b
et rechargera
.(sauvegarde du registre CPU dans la mémoire et chargement de la mémoire dans le registre CPU).
ajouter
b
àa
.enregistrer
a
(du registre CPU) dans la mémoire.L'étape 3 est très lente car elle doit accéder à la mémoire physique. Cependant, il est nécessaire de se protéger contre les instances où
a
etb
pointer vers la même adresse mémoire.Un aliasing strict nous permettrait d'éviter cela en indiquant au compilateur que ces adresses mémoire sont distinctement différentes (ce qui, dans ce cas, permettra une optimisation encore plus poussée qui ne peut pas être effectuée si les pointeurs partagent une adresse mémoire).
Cela peut être dit au compilateur de deux manières, en utilisant différents types pour pointer. c'est à dire:
En utilisant le
restrict
mot - clé. c'est à dire:Désormais, en satisfaisant à la règle de repli strict, l'étape 3 peut être évitée et le code s'exécutera beaucoup plus rapidement.
En fait, en ajoutant le
restrict
mot - clé, toute la fonction pourrait être optimisée pour:charger
a
etb
de la mémoire.ajouter
a
àb
.enregistrer le résultat vers
a
et versb
.Cette optimisation n'aurait pas pu être effectuée auparavant, en raison de la collision possible (où
a
etb
serait triplé au lieu de doublé).la source
b
(pas le recharger) et rechargera
. J'espère que c'est plus clair maintenant.restrict
, mais je pense que ce dernier serait dans la plupart des cas plus efficace, et le relâchement de certaines contraintesregister
lui permettrait de remplir certains des cas oùrestrict
cela ne serait pas utile. Je ne suis pas sûr qu'il ait jamais été "important" de traiter la norme comme décrivant pleinement tous les cas où les programmeurs devraient s'attendre à ce que les compilateurs reconnaissent les preuves d'alias, plutôt que de simplement décrire les endroits où les compilateurs doivent présumer l'alias même lorsqu'aucune preuve particulière n'existe .restrict
mot - clé minimise non seulement la vitesse des opérations mais aussi leur nombre, ce qui pourrait être significatif ... Je veux dire, après tout, l'opération la plus rapide n'est pas une opération du tout :)Un alias strict n'autorise pas différents types de pointeurs vers les mêmes données.
Cet article devrait vous aider à comprendre le problème en détail.
la source
int
et une structure qui contient unint
).Techniquement en C ++, la règle d'aliasing stricte n'est probablement jamais applicable.
Notez la définition de l'indirection ( opérateur * ):
Également de la définition de glvalue
Ainsi, dans toute trace de programme bien définie, une valeur gl fait référence à un objet. Donc, la soi-disant règle d'aliasing stricte ne s'applique jamais. Ce n'est peut-être pas ce que les concepteurs voulaient.
la source
int foo;
, à quoi accède l'expression lvalue*(char*)&foo
? Est-ce un objet de typechar
? Cet objet existe-t-il en même temps quefoo
? Est-ce que l'écriturefoo
changerait la valeur stockée de cet objet de type susmentionnéchar
? Dans l'affirmative, existe-t-il une règle permettant d'char
accéder à la valeur stockée d'un objet de type à l'aide d'une valeur de type lint
?int i;
crée-t-elle quatre objets de chaque type de caractèrein addition to one of type
int? I see no way to apply a consistent definition of "object" which would allow for operations on both
* (char *) & i` eti
. Enfin, rien dans la norme ne permet même à unvolatile
pointeur qualifié d'accéder à des registres matériels qui ne répondent pas à la définition d '"objet".