104 votes

Est-il légal d'indexer dans une structure?

Indépendamment de la façon dont les "mauvaises" le code de l'est, et en supposant que l'alignement, etc ne sont pas un problème sur le compilateur/plate-forme, n'est ce pas défini ou cassé comportement?

Si j'ai une structure comme ceci :-

struct data
{
    int a, b, c;
};

struct data thing;

Est-il légal pour accéder a, b et c comme (&thing.a)[0], (&thing.a)[1], et (&thing.a)[2]?

Dans tous les cas, sur chaque compilateur et de la plate-forme, j'ai essayé sur, avec tous les paramètres, j'ai essayé de "travaillé". Je suis juste inquiet que le compilateur ne pouvez pas réaliser que b et chose[1] sont la même chose et magasins à " b " peut être mise dans un registre et chose[1] lit la valeur erronée de la mémoire (par exemple). Dans tous les cas, j'ai essayé, il a fait la bonne chose. (Je me rends compte, bien sûr, qui ne prouve pas grand chose)

Ce n'est pas mon code, c'est le code que j'ai à travailler avec, je suis intéressé de savoir si c'est mauvais code ou cassé code que les différentes affecte mes priorités pour le changer beaucoup :)

Tagged C et C++ . Je suis surtout intéressé à la C++, mais aussi en C, si elle est différente, juste pour les intérêts.

72voto

WhiZTiM Points 3351

Il est illégal 1. C'est un comportement Indéfini en C++.

Vous prenez le les membres dans un tableau de la mode, mais ici, c'est ce que le C++ standard dit (l'emphase est mienne):

[dcl.tableau/1]: ...Un objet de type tableau contient une manière contiguë alloué non-jeu vide de N les sous-objets de type T...

Mais, pour les membres, il n'y a pas contigus exigence:

[classe.mem/17]: ...;la mise en Œuvre de l'alignement des exigences peut entraîner deux adjacentes les membres de ne pas être affectés immédiatement les uns après les autres...

Alors que les deux citations ci-dessus devrait être suffisant de faire allusion pourquoi l'indexation en struct comme vous l'avez fait n'est pas un comportement défini par la norme C++, prenons un exemple: regardez l'expression (&thing.a)[2] - en ce qui Concerne l'indice de l'opérateur:

[expr.post//expr.sous/1]: Un postfix expression suivie par une expression entre crochets est une postfix expression. L'une des expressions est un glvalue de type "tableau de T" ou un prvalue de type "pointeur sur T" et l'autre est être un prvalue de non délimité énumération ou de type intégral. Le résultat est de type "T". Le type "T" doit être complètement définie par type d'objet.66 L'expression E1[E2] est identique (par définition) ((E1)+(E2))

Creuser dans le gras du texte de la citation ci-dessus: en ce qui concerne l'ajout d'une partie intégrante de type pour un type de pointeur (note de l'accent mis ici)..

[expr.ajouter/4]: Lorsqu'une expression qui a le type intégral est ajoutée ou déduite à partir d'un pointeur, le résultat est du type du pointeur de l'opérande. Sil' l'expression P de points à l'élément x[i] d' un tableau d'objet x avec n éléments, les expressions P + J et J + P (où Ja la valeur j) point à l' (éventuellement) hypothétique) de l'élément x[i + j] si 0 ≤ i + j ≤ n; autrement, le comportement est indéfini. ...

Remarque le tableau exigence pour la si la clause; d'autre le contraire dans la citation ci-dessus. L'expression (&thing.a)[2] n'est évidemment pas se qualifier pour le si la clause; par conséquent, un Comportement Indéfini.


Sur une note de côté: même Si j'ai beaucoup expérimenté le code et de ses variations sur les différents compilateurs et qu'ils n'introduisent pas de tout remplissage ici, (il fonctionne); à partir d'un entretien de vue, le code est extrêmement fragile. vous devriez tout de même affirmer que la mise en œuvre alloués aux membres consécutivement avant de le faire. Et de rester dans des limites :-). Mais sa reste un comportement Indéfini....

Certains viable solutions de contournement (avec comportement défini) ont été fournies par d'autres réponses.



Comme l'a justement souligné dans les commentaires, [de base.lval/8], qui a été dans mon édition précédente ne s'applique pas. Merci @2501 et @M. M.

1: Voir @Barry, la réponse à cette question pour un seul cas où vous pouvez accéder thing.a membre de la structure par l'intermédiaire de ce parttern.

48voto

2501 Points 4807

Pas de. En C, c'est un comportement indéfini, même si il n'y a pas de rembourrage.

La chose qui provoque un comportement indéfini est hors des limites d'accès1. Lorsque vous avez un scalaire (un des membres a,b,c de la struct) et essayez de l'utiliser comme un tableau de2 pour accéder à la prochaine hypothétique élément, vous provoquer un comportement indéfini, même si il arrive à être un autre objet du même type à cette adresse.

Cependant, vous pouvez utiliser l'adresse de la structure de l'objet et de calculer le décalage d'un membre:

struct data thing = { 0 };
char* p = ( char* )&thing + offsetof( thing , b );
int* b = ( int* )p;
*b = 123;
assert( thing.b == 123 );

Ce qui doit être fait pour chaque membre individuellement, mais peuvent être mis dans une fonction qui ressemble à un tableau.


1 (Cité: ISO/IEC 9899:201x 6.5.6 Additif opérateurs de 8)
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.

2 (Cité: ISO/IEC 9899:201x 6.5.6 Additif opérateurs 7)
Pour l'application de ces opérateurs, un pointeur vers un objet qui n'est pas un élément d'une tableau se comporte comme un pointeur vers le premier élément d'un tableau de longueur avec le type de l'objet comme type d'élément.

43voto

Slava Points 4119

En C++, si vous en avez vraiment besoin - créer de l'opérateur[]:

struct data
{
    int a, b, c;
    int &operator[]( size_t idx ) {
        switch( idx ) {
            case 0 : return a;
            case 1 : return b;
            case 2 : return c;
            default: throw std::runtime_error( "bad index" );
        }
    }
};


data d;
d[0] = 123; // assign 123 to data.a

ce n'est pas seulement garanti, mais l'utilisation est plus simple, vous n'avez pas besoin d'écrire illisible expression (&thing.a)[0]

Note: cette réponse est donnée dans l'hypothèse que vous avez déjà une structure avec des champs, et vous avez besoin d'ajouter de l'accès par index. Si la vitesse est un problème et vous pouvez modifier la structure de ce qui pourrait être plus efficace:

struct data 
{
     int array[3];
     int &a = array[0];
     int &b = array[1];
     int &c = array[2];
};

Cette solution permettrait de changer la taille de la structure, de sorte que vous pouvez utiliser les méthodes ainsi:

struct data 
{
     int array[3];
     int &a() { return array[0]; }
     int &b() { return array[1]; }
     int &c() { return array[2]; }
};

14voto

StoryTeller Points 6139

Pour c ++: Si vous devez accéder à un membre sans connaître son nom, vous pouvez utiliser un pointeur sur une variable membre.

 struct data {
  int a, b, c;
};

typedef int data::* data_int_ptr;

data_int_ptr arr[] = {&data::a, &data::b, &data::c};

data thing;
thing.*arr[0] = 123;
 

10voto

Peter Cordes Points 1375

Dans la norme ISO C99/C11, de l'union de type beaucoup les jeux de mots est légal, donc vous pouvez l'utiliser à la place de l'indexation des pointeurs vers des non-tableaux (voir les divers autres réponses).

ISO C++ ne permet pas de l'union de type beaucoup les jeux de mots. GNU C++, comme une extension, et je pense que certains d'autres compilateurs qui ne supportent pas les extensions GNU, en général, ne l'appui de l'union de type beaucoup les jeux de mots. Mais cela ne veut pas vous aider à écrire strictement code portable.

Les dernières versions de gcc et clang, l'écriture d'une fonction de membre C++ à l'aide d'un switch(idx) pour sélectionner un membre permettra d'optimiser loin pour constante à la compilation des indices, mais produira terrible branchu asm pour l'exécution des indices. Il n'y a rien d'intrinsèquement mauvais avec des switch() pour cela; c'est tout simplement un incontournable-l'optimisation de bug dans le courant de compilateurs. Ils pourraient compilateur Slava' () la fonction de manière efficace.


La solution à cela est de faire dans l'autre sens: donner votre class/struct un membre de groupe, et d'écrire des fonctions d'accesseur pour attacher les noms d'éléments spécifiques.

struct array_data
{
  int arr[3];

  int &operator[]( unsigned idx ) {
      // assert(idx <= 2);
      //idx = (idx > 2) ? 2 : idx;
      return arr[idx];
  }
  int &a(){ return arr[0]; } // TODO: const versions
  int &b(){ return arr[1]; }
  int &c(){ return arr[2]; }
};

Nous pouvons avoir un coup d'oeil à l'asm de sortie pour les différents cas d'utilisation, sur la Godbolt compilateur explorer. Ces sont complètes x86-64 fonctions V, avec la fuite instruction RET omis afin de mieux montrer ce que vous obtiendrez quand ils en ligne. BRAS/MIPS/tout ce qui serait similaire.

# asm from g++6.2 -O3
int getb(array_data &d) { return d.b(); }
    mov     eax, DWORD PTR [rdi+4]

void setc(array_data &d, int val) { d.c() = val; }
    mov     DWORD PTR [rdi+8], esi

int getidx(array_data &d, int idx) { return d[idx]; }
    mov     esi, esi                   # zero-extend to 64-bit
    mov     eax, DWORD PTR [rdi+rsi*4]

Par comparaison, @Slava réponse à l'aide d'un switch() pour le C++ permet à l'asm de ce genre pour l'exécution, la variable d'index. (Code de la précédente Godbolt lien).

int cpp(data *d, int idx) {
    return (*d)[idx];
}

    # gcc6.2 -O3, using `default: __builtin_unreachable()` to promise the compiler that idx=0..2,
    # avoiding an extra cmov for idx=min(idx,2), or an extra branch to a throw, or whatever
    cmp     esi, 1
    je      .L6
    cmp     esi, 2
    je      .L7
    mov     eax, DWORD PTR [rdi]
    ret
.L6:
    mov     eax, DWORD PTR [rdi+4]
    ret
.L7:
    mov     eax, DWORD PTR [rdi+8]
    ret

C'est évidemment terrible, par rapport à la C (ou C++ de GNU) de l'union type beaucoup les jeux de mots version:

c(type_t*, int):
    movsx   rsi, esi                   # sign-extend this time, since I didn't change idx to unsigned here
    mov     eax, DWORD PTR [rdi+rsi*4]

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