Comprendre restreindre qualificatif par des exemples

Le comportement du mot clé ressortingct est défini dans C99 par 6.7.3.1:

Soit D la déclaration d’un identifiant ordinaire fournissant un moyen de désigner un object P en tant que pointeur qualifié de ressortingction sur le type T.

Si D apparaît à l’intérieur d’un bloc et n’a pas de classe de stockage extern, laissez B désigner le bloc. Si D apparaît dans la liste des déclarations de parameters d’une définition de fonction, notons B le bloc associé. Autrement, notons B le bloc principal (ou le bloc d’une fonction quelconque appelée au démarrage du programme dans un environnement indépendant).

Dans ce qui suit, on dit qu’une expression de pointeur E est basée sur l’object P si (à un moment donné de l’exécution de B avant l’évaluation de E), modifie P pour qu’il pointe vers une copie de l’object tableau dans lequel il était précédemment pointé changerait la valeur de E.119) Notez que ” basé ” est défini uniquement pour les expressions avec des types de pointeur.

Lors de chaque exécution de B, prenons L toute valeur qui a & L basée sur P. Si L est utilisé pour accéder à la valeur de l’object X qu’il désigne, et que X est également modifié (par tout moyen), les conditions suivantes s’appliquent : T ne doit pas être qualifié de const. Toute autre valeur utilisée pour accéder à la valeur de X doit également avoir son adresse basée sur P. Tout access qui modifie X doit également être considéré comme modifiant P, aux fins du présent sous-paragraphe. Si on atsortingbue à P la valeur d’une expression de pointeur E basée sur un autre object pointeur restreint P2, associé au bloc B2, l’exécution de B2 doit commencer avant l’exécution de B ou l’exécution de B2 doit se terminer avant l’exécution de B2. affectation. Si ces conditions ne sont pas remplies, le comportement n’est pas défini.

Comme à peu près tout le monde, j’ai du mal à comprendre toutes les subtilités de cette définition. En réponse à cette question, j’aimerais voir un ensemble de bons exemples, pour chaque exigence du quasortingème paragraphe, d’usages qui violeraient cette exigence. Cet article:

http://web.archive.org/web/20120225055041/http://developers.sun.com/solaris/articles/cc_ressortingct.html

fait un bon travail de présentation des règles en termes de “un compilateur peut assumer …”; développer ce modèle et lier les hypothèses que le compilateur peut faire, et comment ils ne tiennent pas, avec chaque exemple serait formidable.

Ci-dessous, je ferai référence aux cas d’utilisation du papier Sun lié à la question.

Le cas (relativement) évident serait le cas mem_copy (), qui entre dans la catégorie 2nd Usecase du papier Sun (la fonction f1() ). Disons que nous avons les deux implémentations suivantes:

 void mem_copy_1(void * ressortingct s1, const void * ressortingct s2, size_t n); void mem_copy_2(void * s1, const void * s2, size_t n); 

Parce que nous soaps qu’il n’y a pas de chevauchement entre les deux tableaux pointés par s1 et s2, le code de la 1ère fonction serait simple:

 void mem_copy_1(void * ressortingct s1, const void * ressortingct s2, size_t n) { // naively copy array s2 to array s1. for (int i=0; i 

s2 = '....................1234567890abcde' <- s2 before the naive copy
s1 = '1234567890abcde....................' <- s1 after the naive copy
s2 = '....................1234567890abcde' <- s2 after the naive copy

OTOH, dans la 2e fonction, il peut y avoir un chevauchement. Dans ce cas, nous devons vérifier si le tableau source est situé avant la destination ou inversement, et choisir les limites de l'index de boucle en conséquence.

Par exemple, disons s1 = 100 et s2 = 105 . Ensuite, si n=15 , après la copie, le tableau s1 nouvellement copié s1 les 10 premiers octets du tableau s2 source. Nous devons nous assurer que nous avons d'abord copié les octets inférieurs.

s2 = '.....1234567890abcde' <- s2 before the naive copy
s1 = '1234567890abcde.....' <- s1 after the naive copy
s2 = '.....67890abcdeabcde' <- s2 after the naive copy

Cependant, si s1 = 105 et s2 = 100 , écrire les octets les plus bas en premier envahira les 10 derniers octets de la source s2 et nous obtiendrons une copie erronée.

s2 = '1234567890abcde.....' <- s2 before the naive copy
s1 = '.....123451234512345' <- s1 after the naive copy - not what we wanted
s2 = '123451234512345.....' <- s2 after the naive copy

Dans ce cas, nous devons d'abord copier les derniers octets du tableau, éventuellement en arrière. Le code ressemblera à quelque chose comme:

 void mem_copy_2(void *s1, const void *s2, size_t n) { if (((unsigned) s1) < ((unsigned) s2)) for (int i=0; i=0; i--) s1[i] = s2[i]; return; } 

Il est facile de voir comment le modificateur ressortingct donne une chance d'optimiser la vitesse, d'éliminer le code supplémentaire et de prendre une décision si-sinon.

Dans le même temps, cette situation est dangereuse pour le programmeur imprudent, qui transmet des tableaux superposés à la fonction ressortingct . Dans ce cas, aucun protecteur n’est là pour assurer la copie correcte de la masortingce. En fonction du chemin d'optimisation choisi par le compilateur, le résultat est indéfini.


Le 1er cas d'utilisation (la fonction init() ) peut être vu comme une variation du 2e décrit ci-dessus. Ici, deux tableaux sont créés avec un seul appel d'allocation de mémoire dynamic.

Désigner les deux pointeurs comme ressortingct permet une optimisation dans laquelle l'ordre des instructions importerait autrement. Par exemple, si nous avons le code:

 a1[5] = 4; a2[3] = 8; 

l'optimiseur peut alors réorganiser ces instructions s'il le trouve utile.

OTOH, si les pointeurs ne sont pas ressortingct , il est important que la 1ère affectation soit effectuée avant la deuxième. En effet, il est possible que a1[5] et a2[3] soient en fait le même emplacement mémoire! Il est facile de voir que, dans ce cas, la valeur finale doit être égale à 8. Si nous réorganisons les instructions, la valeur finale sera 4!

De nouveau, si des pointeurs non disjoints sont donnés à ce code supposé ressortingct , le résultat est indéfini.