2017-02-03 9 views
4

test_func L'extrait suivant déclenche-t-il un comportement indéfini sous les strictes règles d'alias lorsque les deux arguments se chevauchent partiellement?Un alias strict s'applique-t-il lorsque vous utilisez des pointeurs sur des membres de la structure?

qui est le deuxième argument est un membre de la première:

#include <stdio.h> 

typedef struct 
{ 
    //... Other fields 
    int x; 
    //... Other fields 
} A; 

int test_func(A *a, int *x) 
{ 
    a->x = 0; 
    *x = 1; 
    return a->x; 
} 

int main() 
{ 
    A a = {0}; 

    printf("%d\n", test_func(&a, &a.x)); 

    return 0; 
} 

est le compilateur permis de penser test_func va simplement retourner 0, basé sur l'hypothèse que A* et int* ne sera pas alias? donc le *x ne peut pas écraser le membre?

+1

Je ne pense pas que ce soit le but du mot-clé ['restrict'] (https://en.wikipedia.org/wiki/Restrict) dans C. – Stargateur

+0

Je pensais que * strict aliasing * fait référence à la cas de conversion entre les types de pointeurs, en supposant que les types étaient de la même taille. Par exemple, convertir 'int *' en float * ', sur une plateforme avec' sizeof int == sizeof float'. Et je pensais que cette restriction était due au fait que chaque plate-forme peut avoir des exigences d'alignement différentes, ce qui dans certains scénarios peut conduire à un comportement indéfini. Je ne vois rien de tout cela dans votre code. –

+2

En règle générale, vous pouvez dire qu'il ne peut y avoir de violation de '' strict aliasing '' que si vous lancez un pointeur vers un type différent ou si vous le convertissez en 'void *' et ensuite en un autre type. Ici, dans votre cas, toutes les informations de type sont présentes au compilateur, il ne doit donc pas créer de problèmes. –

Répondre

4

Ceci ne constitue pas une violation du strict aliasing. La règle stricte d'alias dit (simplifié) que vous pouvez accéder à la valeur d'un objet uniquement en utilisant une expression lvalue d'un type compatible. Dans ce cas, l'objet auquel vous accédez est le membre x de la variable a de main. Ce membre a le type int. Et l'expression que vous utilisez pour y accéder (*x) a également le type int. Donc, il n'y a pas de problème. Vous pouvez confondre un aliasing strict avec restrict. Si vous aviez utilisé le mot-clé restrict dans la déclaration de l'un des paramètres du pointeur, le code serait invalide car restrict vous empêche d'utiliser différents pointeurs pour accéder au même objet - mais c'est un problème différent de celui de l'alias strict.

+0

La façon dont gcc interprète l'aliasing strict, donné 'struct foo {int x;}; struct bar {int x;}; union u {struct foo vf; struct bar fb;}; test int (struct foo * pf; struct bar * pb) {pf-> x = 1; pb-> x = 2; return pf-> x; } 'il va générer du code qui suppose que' pf-> x' et 'pb-> x' ne peuvent pas alias malgré le fait que les deux structures sont des membres du même type d'union dont la définition complète est visible au point d'accès. Je ne pense pas que les Normes puissent raisonnablement être interprétées pour permettre un tel comportement (cela rendrait inutile l'exigence que le "type d'union complet" soit visible) ... – supercat

+0

... mais les auteurs de gcc ont indiqué qu'ils n'avaient aucune intention de prendre en charge la règle CIS pour les accès qui ne sont pas effectués via un pointeur du type union (même si le type union est visible, et les objets identifiés par les pointeurs en question pourraient être - ou même déclarés comme-- membres du même syndicat). – supercat

5

Strict aliasing fait référence au moment où un pointeur est converti en un autre type de pointeur, après quoi le contenu est accédé. L'aliasing strict signifie que les types pointés impliqués doivent être compatibles. Cela ne s'applique pas ici. Il existe cependant le terme alias de pointeur, ce qui signifie que deux pointeurs peuvent faire référence à la même mémoire. Le compilateur n'est pas autorisé à supposer que c'est le cas ici. S'il veut faire des optimisations comme celles que vous décrivez, il devra peut-être ajouter un code machine qui compare les pointeurs les uns avec les autres, afin de déterminer s'ils sont identiques ou non. Ce qui en soi rendrait la fonction légèrement plus lente.

Pour aider le compilateur à optimiser un tel code, vous pouvez déclarer les pointeurs comme restrict, ce qui indique au compilateur que le programmeur garantit que les pointeurs ne pointent pas sur la même mémoire.

Votre fonction compilée avec gcc -O3 de ce code machine:

0x00402D09 mov $0x1,%edx 

Ce qui signifie essentiellement que toute fonction a été remplacée (inline) par "set a.x 1".

Mais si je réécris votre fonction

int test_func(A* restrict a, int* restrict x) 
{ 
    a->x = 0; 
    *x = 1; 
    return a->x; 
} 

et compilez avec gcc -O3, il ne retourne 0. Parce que je l'ai maintenant dit au compilateur que a->X et x ne pointent pas la même mémoire, il peut Supposons que *x = 1; n'affecte pas le résultat et ignore la ligne *x = 1; ou la séquence avant la ligne a->x = 0;.

Le code machine optimisé de la version restrict ignore l'appel de la fonction entière, car il sait que la valeur est déjà 0 selon votre initialisation.

Ceci est bien sûr un bug, mais le programmeur est à blâmer pour cela, pour une utilisation imprudente de restrict.