51 votes

Dans une structure, est-il légal d'utiliser un champ de tableau pour en accéder un autre?

Considérez, par exemple, la structure suivante:

struct S {
  int a[4];
  int b[4];
} s;

Serait-il légal d'écrire s.a[6] et s'attendre à être égal à s.b[2]? Personnellement, je pense qu'il doit être AC en C++, alors que je ne suis pas sûr de C. Cependant, je n'ai pas réussi à trouver quelque chose de pertinent dans les normes des langages C et C++.


Mise à jour

Il y a plusieurs réponses suggérant des moyens pour s'assurer il n'y a pas de rembourrage entre les champs afin de rendre le code du travail de manière fiable. Je voudrais souligner que si ce type de code est UB, puis absense de rembourrage n'est pas assez. Si c'est UB, ensuite, le compilateur est libre de supposer que des accès à des S.a[i] et S.b[j] ne pas le chevauchement et le compilateur est libre de réorganiser tels les accès à la mémoire. Par exemple,

    int x = s.b[2];
    s.a[6] = 2;
    return x;

peut être transformé à

    s.a[6] = 2;
    int x = s.b[2];
    return x;

qui revient toujours 2.

60voto

Rxmsc Points 927

Serait-il légal d'écrire s.un[6] et s'attendre à être égal à s.b[2]?

Pas de. Parce que l'accès à un réseau de lié invoquée comportement indéfini en C et C++.

C11 J. 2 comportement Indéfini

  • L'Addition ou la soustraction d'un pointeur, ou juste au-delà, un tableau d'objet et un type entier, produit un résultat qui pointe juste au-delà de l'objet tableau et est utilisé comme opérande de unaire * opérateur est évaluée (6.5.6).

  • Un indice de tableau est hors de portée, même si un objet est apparemment accessible avec l'indice donné (comme dans la lvalue expression a[1][7] compte tenu de la déclaration int a[4][5]) (6.5.6).

Norme C++ projet de la section 5.7 Additif opérateurs paragraphe 5 dit:

Lorsqu'une expression qui a le type intégral est ajouté ou soustrait à partir d'un pointeur, le résultat est du type du pointeur de l'opérande. Si l' pointeur opérande points à un élément d'un objet de tableau, et le tableau est assez grand, le résultat des points à un élément de décalage à partir de la les éléments d'origine tels que la différence des indices de la résultant et original éléments du tableau est égale à l'intégrale de l'expression. [...] Si le pointeur de l'opérande et le résultat de point d'éléments de la même objet de tableau, ou un au-delà du dernier élément du tableau l'objet, l'évaluation ne doit pas produire un dépassement de capacité; sinon, l' le comportement est indéfini.

33voto

alinsoar Points 3583

En dehors de la réponse de l' @rsp (Undefined behavior for an array subscript that is out of range) je peux ajouter qu'il n'est pas légal pour accéder b par a parce que le langage C ne permet pas de spécifier la quantité de remplissage de l'espace entre l'extrémité de la zone allouée pour l'un et le début de b, de sorte que même si vous pouvez l'exécuter sur une mise en oeuvre particulière , il n'est pas portable.

instance of struct:
+-----------+----------------+-----------+---------------+
|  array a  |  maybe padding |  array b  | maybe padding |
+-----------+----------------+-----------+---------------+

La deuxième rembourrage peut manquer ainsi que l'alignement de l' struct object est l'alignement de l' a qui est le même que l'alignement de l' b mais le langage C aussi de ne pas imposer la deuxième rembourrage de ne pas être là.

11voto

Stephan Lechner Points 29375

a et b sont deux différents tableaux, et a est défini comme contenant 4 - éléments. Par conséquent, a[6] accède au tableau de limites et est donc un comportement indéterminé. Noter que l'indice de tableau a[6] est défini comme *(a+6), de sorte que la preuve de l'AC est en fait donnée par la section Additif "opérateurs" en collaboration avec des pointeurs". Voir la section suivante de la C11-standard (par exemple, ce en ligne de la version provisoire) décrivant cet aspect:

6.5.6 Additif opérateurs

Quand une expression de type entier est ajouté ou soustrait à partir d'un pointeur, le résultat est du type du pointeur de l'opérande. Si l' pointeur opérande points à un élément d'un objet de tableau, et le tableau est assez grand, le résultat des points à un élément de décalage à partir de la les éléments d'origine tels que la différence des indices de la résultant et original éléments du tableau est égale à l'expression entière. En d'autres termes, si l'expression de P points à la i-ème élément d'une objet de tableau, les expressions (P)+N (de manière équivalente, N+(P)) et (P)-N (où N est la valeur de n), point de pour, respectivement, le i+n-ième et en th des éléments de l'objet tableau, à condition qu'ils existent. En outre, si l'expression de P points pour le dernier élément d'un tableau d'objet, l' l'expression (P)+1 points l'un après le dernier élément de l'objet array, et si l'expression de Q points l'un après le dernier élément d'un tableau l'objet, l'expression (Q)-1 points pour le dernier élément du tableau objet. Si le pointeur de l'opérande et le résultat de point d'éléments de la même objet de tableau, ou un au-delà du dernier élément du tableau l'objet, l'évaluation ne doit pas produire un dépassement de capacité; sinon, l' le comportement est indéfini. Si le résultat des points l'un après le dernier élément de l'objet tableau, il ne doit pas être utilisé comme opérande de unaire * l'opérateur qui est évaluée.

Le même argument s'applique à C++ (bien que ne cite pas ici).

De plus, même si c'est clairement un comportement indéterminé en raison du fait de dépasser les limites du tableau de a, à noter que le compilateur peut introduire un rembourrage entre les membres de l' a et b, de telle sorte que - même si un tel pointeur de l'arithmétique ont été admis - a+6 ne produirait pas nécessairement la même adresse que l' b+2.

6voto

dwilliss Points 696

Est-il légal? Pas de. Comme d'autres l'ont mentionné, il invoque un Comportement Indéfini.

Ça va fonctionner? Cela dépend de votre compilateur. C'est la chose à propos d'un comportement indéfini: c'est pas défini.

Sur de nombreux compilateurs C et C++, la structure va être aménagé de sorte que b va suivre immédiatement un en mémoire et il n'y aura pas de vérification des limites. L'accès à un[6] sera en fait le même que b[2] et ne sera pas causer toute sorte d'exception.

Compte tenu de

struct S {
  int a[4];
  int b[4];
} s

et en supposant qu'aucun rembourrage supplémentaire, la structure est vraiment juste une façon de regarder un bloc de mémoire contenant 8 entiers. Vous pourriez jeter aux (int*) et ((int*)s)[6] serait point à la même mémoire que s.b[2].

Si vous comptez sur ce genre de comportement? Absolument pas. Indéfini signifie que le compilateur n'a pas à soutenir cette. Le compilateur est libre de tampon de la structure qui pourrait rendre l'hypothèse que &(s.b[2]) == &(s.un[6]) incorrect. Le compilateur pourrait également ajouter la vérification des limites sur l'accès au tableau (bien que permettant des optimisations du compilateur serait probablement désactiver cette vérification).

J'ai ressenti les effets de la ce dans le passé. Il est assez commun d'avoir une structure comme ceci

struct Bob {
    char name[16];
    char whatever[64];
} bob;
strcpy(bob.name, "some name longer than 16 characters");

Maintenant bob.quel que soit "moins de 16 caractères". (c'est pourquoi vous devriez toujours utiliser strncpy, BTW)

5voto

Jed Schaaf Points 192

Comme @MartinJames mentionné dans un commentaire, si vous avez besoin pour garantir que a et b sont dans la mémoire contiguë (ou au moins en mesure d'être traités comme tels, (edit) à moins que votre architecture/compilateur utilise une inhabituelle taille du bloc de mémoire/offset et forcé d'alignement qui aurait besoin de rembourrage pour être ajouté), vous devez utiliser un union.

union overlap {
    char all[8]; /* all the bytes in sequence */
    struct { /* (anonymous struct so its members can be accessed directly) */
        char a[4]; /* padding may be added after this if the alignment is not a sub-factor of 4 */
        char b[4];
    };
};

Vous ne pouvez pas accéder directement à la b de a (par exemple, a[6], comme vous l'avez demandé), mais vous pouvez avoir accès aux éléments de fois a et b par l'aide d' all (par exemple, all[6] se réfère à la même emplacement mémoire que b[2]).

(Edit: Vous pouvez remplacer 8 et 4 dans le code ci-dessus avec 2*sizeof(int) et sizeof(int), respectivement, pour être plus susceptible de correspondre à l'architecture d'alignement, notamment si le code doit être plus portable, mais alors vous devez être prudent afin d'éviter de faire des hypothèses sur la façon dont nombre d'octets dans a, bou all. Cependant, ce sera le travail sur ce qui sont probablement les plus courantes (1-, 2-et 4-octets) de mémoire alignements.)

Voici un exemple simple:

#include <stdio.h>

union overlap {
    char all[2*sizeof(int)]; /* all the bytes in sequence */
    struct { /* anonymous struct so its members can be accessed directly */
        char a[sizeof(int)]; /* low word */
        char b[sizeof(int)]; /* high word */
    };
};

int main()
{
    union overlap testing;
    testing.a[0] = 'a';
    testing.a[1] = 'b';
    testing.a[2] = 'c';
    testing.a[3] = '\0'; /* null terminator */
    testing.b[0] = 'e';
    testing.b[1] = 'f';
    testing.b[2] = 'g';
    testing.b[3] = '\0'; /* null terminator */
    printf("a=%s\n",testing.a); /* output: a=abc */
    printf("b=%s\n",testing.b); /* output: b=efg */
    printf("all=%s\n",testing.all); /* output: all=abc */

    testing.a[3] = 'd'; /* makes printf keep reading past the end of a */
    printf("a=%s\n",testing.a); /* output: a=abcdefg */
    printf("b=%s\n",testing.b); /* output: b=efg */
    printf("all=%s\n",testing.all); /* output: all=abcdefg */

    return 0;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X