web-dev-qa-db-fra.com

Comment puis-je obtenir / définir un membre struct par offset

Ignorer les problèmes de remplissage/d'alignement et étant donné la structure suivante, quelle est la meilleure façon d'obtenir et de définir la valeur de member_b sans utiliser le nom du membre.

struct mystruct {
    int member_a;
    int member_b;
}
struct mystruct *s = malloc(sizeof(struct mystruct));

En d'autres termes; Comment exprimeriez-vous ce qui suit en termes de pointeurs/décalages:

s->member_b = 3;
printf("%i",s->member_b);

Ma conjecture est de

  • calculer le décalage en trouvant la taille du membre_a (int)
  • transtyper la structure en un seul type de pointeur Word (char?)
  • créez un pointeur int et définissez l'adresse (sur *charpointer + offset?)
  • utiliser mon int pointeur pour définir le contenu de la mémoire

mais je suis un peu confus au sujet de la conversion en un type de caractère ou si quelque chose comme memset est plus approprié ou si généralement je suis en train de préconiser cela totalement faux.

Bravo pour toute aide

28
Chris Farmiloe

L'approche que vous avez décrite est à peu près correcte, bien que vous devriez utiliser offsetof au lieu d'essayer de déterminer le décalage par vous-même. Je ne sais pas pourquoi vous mentionnez memset - il définit le contenu d'un bloc à une valeur spécifiée, ce qui semble tout à fait indépendant de la question posée.

Voici du code pour montrer comment cela fonctionne:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

typedef struct x {
    int member_a;
    int member_b;
} x;

int main() { 
    x *s = malloc(sizeof(x));
    char *base;
    size_t offset;
    int *b;

    // initialize both members to known values
    s->member_a = 1;
    s->member_b = 2;

    // get base address
    base = (char *)s;

    // and the offset to member_b
    offset = offsetof(x, member_b);

    // Compute address of member_b
    b = (int *)(base+offset);

    // write to member_b via our pointer
    *b = 10;

    // print out via name, to show it was changed to new value.
    printf("%d\n", s->member_b);
    return 0;
}
32
Jerry Coffin

La technique complète:

  1. Obtenez le décalage en utilisant offsetof:

    b_offset = offsetof (struct mystruct, membre_b);

  2. Obtenez l'adresse de votre structure en tant que pointeur char *.

    char * sc = (char *) s;

  3. Ajoutez l'ajout de l'offset à l'adresse de la structure, convertissez la valeur en un pointeur sur le type et la déréférence appropriés:

    * (int *) (sc + décalage_b)

26
R Samuel Klatchko

Ignorer le rembourrage et l'alignement, comme vous l'avez dit ...

Si les éléments vers lesquels vous pointez sont entièrement d'un seul type, comme dans votre exemple, vous pouvez simplement convertir la structure en le type souhaité et la traiter comme un tableau:

printf("%i", ((int *)(&s))[1]);
4
mskfisher

Il est possible de calculer le décalage en fonction de la structure et de NULL comme pointeur de référence, par exemple "& (((type *) 0) -> champ)"

Exemple:

struct my_struct {
    int x;
    int y;
    int *m;
    int *h;
};
int main()
{
    printf("offset %d\n", (int) &((((struct my_struct*)0)->h)));
    return 0;
}
3
Obik

Dans cet exemple particulier, vous pouvez l'adresser par *((int *) ((char *) s + sizeof(int))). Je ne sais pas pourquoi vous voulez cela, donc je suppose des fins didactiques, donc l'explication suit.

Le morceau de code se traduit par: prenez la mémoire à partir de l'adresse s et traitez-la comme de la mémoire pointant vers char. À cette adresse, ajoutez sizeof(int)char- morceaux - vous obtiendrez une nouvelle adresse. Prenez la valeur que l'adresse ainsi créée et traitez-la comme un int.

Notez que l'écriture de *(s + sizeof(int)) donnerait l'adresse à s plus sizeof(int) sizeof (mystruct) chunks

Modifier: selon le commentaire d'Andrey, en utilisant offsetof :

*((int *) ((byte *) s + offsetof(struct mystruct, member_b)))

Edit 2: j'ai remplacé tous les bytes par chars car sizeof(char) est garanti d'être 1.

1
laura

Il ressort de vos commentaires que ce que vous faites réellement est de compresser et de décompresser un tas de types de données disparates dans un seul bloc de mémoire. Alors que vous pouvez vous en sortir avec des lancers de pointeurs directs, comme la plupart des autres réponses l'ont suggéré:

void set_int(void *block, size_t offset, int val)
{
    char *p = block;

    *(int *)(p + offset) = val;
}

int get_int(void *block, size_t offset)
{
    char *p = block;

    return *(int *)(p + offset);
}

Le problème est que ce n'est pas portable. Il n'y a aucun moyen général de garantir que les types sont stockés dans votre bloc avec l'alignement correct, et certaines architectures ne peuvent tout simplement pas effectuer des chargements ou des stockages vers des adresses non alignées. Dans le cas particulier où la disposition de votre bloc est définie par une structure déclarée, ce sera OK, car la disposition de la structure inclura le remplissage nécessaire pour assurer le bon alignement. Cependant, comme vous ne pouvez pas accéder aux membres par leur nom, il semble que ce ne soit pas vraiment ce que vous faites.

Pour ce faire, vous devez utiliser memcpy:

void set_int(void *block, size_t offset, int val)
{
    char *p = block;

    memcpy(p + offset, &val, sizeof val);
}

int get_int(void *block, size_t offset)
{
    char *p = block;
    int val;

    memcpy(&val, p + offset, sizeof val);
    return val;
}

(similaire pour les autres types).

0
caf