Slik får tilgang til medlemmer av en 'struct' ifølge en variabel heltall i C?

stemmer
8

Anta at jeg har denne struct(som for øvrig inneholder bit-felt, men du bør ikke bryr seg):

struct Element {
    unsigned int a1 : 1;
    unsigned int a2 : 1;
    ...
    unsigned int an : 1;
};

og jeg ønsker å få tilgang til i'th medlem i en praktisk måte. La oss undersøke en henting løsning.
Jeg kom opp med denne funksjonen:

int getval(struct Element *ep, int n)
{
    int val;
    switch(n) { 
         case 1: val = ep->a1; break;
         case 2: val = ep->a2; break;
         ...
         case n: val = ep->an; break;
    }
    return val;
}

Men jeg mistenker at det er en mye enklere løsning. Noe som matrise tilgang stil, kanskje.

Jeg prøvde å gjøre noe sånt:

 #define getval(s,n)   s.a##n

Men expectedly fungerer det ikke.
Er det en bedre løsning?

Publisert på 20/05/2009 klokken 13:20
kilden bruker
På andre språk...                            


12 svar

stemmer
12

Med mindre du har spesifikk kunnskap om den underliggende strukturen i struct, er det ingen måte å implementere en slik metode i C. Det er alle slags problemer som vil komme i veien inkludert

  • Medlemmer av forskjellige størrelser
  • pakking saker
  • innretting problemer
  • Triks som bitfields vil være problematisk

Du er best av å implementere en metode for hånd for din struct som har en dyp forståelse av de interne medlemmene av strukturen.

Svarte 20/05/2009 kl. 13:24
kilden bruker

stemmer
6

Hvis struct var noe annet enn bitfields, kan du bare bruke matrise-tilgang, hvis jeg har rett i å huske at C garanterer at en rekke medlemmer av en struct alle av samme type, har samme layout som en matrise. Hvis du vet hvilke biter i hvilken rekkefølge kompilatoren lagrer bitfields inn heltall typer, så kan du bruke shift / maske ops, men det er da gjennomføringen avhengig.

Hvis du vil ha tilgang til biter av variabel indeks, så er det nok best å erstatte bitfields med et heltall som inneholder flagg biter. Tilgang med variabelen er egentlig ikke hva bitfields er på: a1 ... en er i utgangspunktet uavhengige medlemmer, ikke en rekke biter.

Du kan gjøre noe sånt som dette:

struct Element {
    unsigned int a1 : 1;
    unsigned int a2 : 1;
    ...
    unsigned int an : 1;
};

typedef unsigned int (*get_fn)(const struct Element*);

#define DEFINE_GETTER(ARG) \
    unsigned int getter_##ARG (const struct Element *ep) { \
        return ep-> a##ARG ; \
    }

DEFINE_GETTER(1);
DEFINE_GETTER(2);
...
DEFINE_GETTER(N);

get_fn jump_table[n] = { getter_1, getter_2, ... getter_n};

int getval(struct Element *ep, int n) {
    return jump_table[n-1](ep);
}

Og noen av gjentakelse kan unngås ved å lure hvor du inkluderer de samme header flere ganger, etter å ha hver gang definert en makro annerledes. Toppteksten utvider seg at makro en gang for hver 1 ... N.

Men jeg er ikke overbevist om at det er verdt det.

Det gjør avtale med JaredPar poeng at du er i trøbbel hvis struct blander forskjellige typer - her alle medlemmene tilgang til via en spesiell hoppe tabellen må selvfølgelig være av samme type, men de kan ha noen gamle søppel i mellom dem. Som likevel etterlater resten av JaredPar poeng, skjønt, og dette er mye kode bloat for egentlig ingen fordel sammenlignet med bryteren.

Svarte 20/05/2009 kl. 13:45
kilden bruker

stemmer
6

Dersom alle felt i struct er en int, så bør du i utgangspunktet være i stand til å si

int getval(struct Element *ep, int n)
{
    return *(((int*)ep) + n);
}

Dette kaster pekeren til struct til en peker til en matrise hvis heltall, åpner deretter nte element av denne matrisen. Siden alt i struct ser ut til å være et heltall, er dette helt gyldig. Merk at dette vil mislykkes forferdelig hvis du noen gang har en ikke-int medlem.

En mer generell løsning vil være å opprettholde en matrise av felt forskyvninger:

int offsets[3];
void initOffsets()
{
    struct Element e;
    offsets[0] = (int)&e.x - (int)&e;
    offsets[1] = (int)&e.y - (int)&e;
    offsets[2] = (int)&e.z - (int)&e;
}

int getval(struct Element *ep, int n)
{
    return *((int*)((int)ep+offsets[n]));
}

Dette vil fungere i den forstand at du vil være i stand til å ringe getvalfor noen av int felt av din struct, selv om du har andre ikke-int felt i din struct, siden forskyvninger vil alle være riktige. Men hvis du prøvde å ringe getvalpå en av de ikke-int felt det vil returnere en helt feil verdi.

Selvfølgelig kan du skrive en annen funksjon for hver datatype, for eksempel

double getDoubleVal(struct Element *ep, int n)
{
    return *((double*)((int)ep+offsets[n]));
}

og deretter bare ringe riktig funksjon for hvilken datatype du ønsker. Forresten, hvis du bruker C ++ du kan si noe sånt

template<typename T>
T getval(struct Element *ep, int n)
{
    return *((T*)((int)ep+offsets[n]));
}

og da ville det fungere uansett datatype du ønsker.

Svarte 20/05/2009 kl. 13:34
kilden bruker

stemmer
2

Nei, det er ingen enkel måte å gjøre dette enklere. Spesielt for bitfields, som er vanskelig å få tilgang indirekte gjennom pekere (du kan ikke ta adressen til en bitfield).

Du kan selvsagt forenkle denne funksjonen til noe sånt som dette:

int getval(const struct Element *ep, int n)
{
    switch(n)
    {
      case 1: return ep->a1;
      case 2: return ep->a2;
      /* And so on ... */
    }
    return -1; /* Indicates illegal field index. */
}

Og det synes opplagt hvordan implementeringen kan forenkles ytterligere ved hjelp av en preprosessor makro som utvides til case-linje, men det er bare sukker .

Svarte 20/05/2009 kl. 13:25
kilden bruker

stemmer
0

Selv om OP presiserer at vi ikke skal bry seg om innholdet i struct, siden de er bare bitfields ville det være mulig å bruke en char eller int (eller hva datatype har størrelsen som kreves) for å lage en n-bit "array " i dette tilfellet?

void writebit(char *array, int n)
{
  char mask = (1 << n);
  *array = *array & mask;
}

med røye typer erstattet med en større type hvis en lengre "array" var nødvendig. Ikke sikker på at dette er en endelig løsning i andre structs men det skal fungere her, med en lignende readbit funcition.

Svarte 29/12/2011 kl. 10:37
kilden bruker

stemmer
0

Basert på eli-courtwright løsning, men uten å bruke rekke felt forskyvninger ...... hvis du har en struktur som inneholder peker felt som dette, kanskje du kunne skrive:

struct  int_pointers
 {
   int  *ptr1;
   int  *ptr2;
   long *ptr3;
   double *ptr4;
   std::string * strDescrPtr;

};

Da vet du at hver pekeren har en 4 byte offset fra en peker til strukturen, slik at du kan skrive:

struct int_pointers  ptrs;
int  i1 = 154;
int i2 = -97;
long i3 = 100000;
double i4  = (double)i1/i2;
std::string strDescr = "sample-string";
ptrs.ptr1 =  &i1;
ptrs.ptr2 =  &i2;
ptrs.ptr3 = &i3;
ptrs.ptr4 = &i4;
ptrs.strDescrPtr = &strDescr;

da, for eksempel for en int verdi du kan skrive:

int GetIntVal (struct int_pointers *ep, int intByteOffset) 
{ 
   int * intValuePtr =  (int *)(*(int*)((int)ep + intByteOffset)); 
   return *intValuePtr; 
}

Ringe ved å:

int intResult = GetIntVal(&ptrs,0) //to retrieve the first int value in ptrs structure variable

int intResult = GetIntVal(&ptrs,4) //to retrieve the second int value in ptrs structure variable

og så videre for de andre struktur felt verdier (skrive andre spesifikke funksjoner, og ved hjelp av riktig bytes forskyvningsverdi (multiplum av 4)).

Svarte 15/01/2010 kl. 14:21
kilden bruker

stemmer
0

Hvis du har

  1. Bare bitfields eller alle bitfields første i din struct
  2. mindre enn 32 (eller 64) bitfields

så er dette løsningen for deg.

#include <stdio.h>
#include <stdint.h>

struct Element {
  unsigned int a1 : 1;
  unsigned int a2 : 1;
  unsigned int a3 : 1;
  unsigned int a4 : 1;
};

#define ELEMENT_COUNT 4 /* the number of bit fields in the struct */

/* returns the bit at position N, or -1 on error (n out of bounds) */
int getval(struct Element* ep, int n) 
{
  if(n > ELEMENT_COUNT || n < 1)
    return -1;

  /* this union makes it possible to access bit fields at the beginning of 
     the struct Element as if they were a number.
   */
  union {
    struct Element el;
    uint32_t bits;
  } comb;

  comb.el = *ep;
  /* check if nth bit is set */
  if(comb.bits & (1<<(n-1))) {
    return 1;
  } else {
    return 0;
  }
}

int main(int argc, char** argv)
{
  int i;
  struct Element el;

  el.a1 = 0;
  el.a2 = 1;
  el.a3 = 1;
  el.a4 = 0;

  for(i = 1; i <= ELEMENT_COUNT; ++i) {
    printf("el.a%d = %d\n", i, getval(&el, i));
  }  

  printf("el.a%d = %d\n", 8, getval(&el, 8));

  return 0;
}
Svarte 20/05/2009 kl. 14:32
kilden bruker

stemmer
0

Hvis du ønsker å få tilgang til strukturen ved hjelp av både element Indeks:

int getval(struct Element *ep, int n)

og etter navn:

ep->a1

så du står fast med noe hardt for å opprettholde bryteren som metode som alle har foreslått.

Hvis, derimot, er alt du ønsker å gjøre tilgang ved indeks og aldri ved navn, så kan du være litt mer kreativ.

First off, definere en felttype:

typedef struct _FieldType
{
    int size_in_bits;
} FieldType;

og deretter lage en struktur definisjon:

FieldType structure_def [] = { {1}, {1}, {1}, {4}, {1}, {0} };

Den ovennevnte definerer en struktur med fem elementer av størrelse 1, 1, 1, 4 og 1 biter. Den endelige {0} markerer slutten av definisjonen.

Nå opprette et element Type:

typedef struct _Element
{
    FieldType *fields;
} Element;

For å opprette en instans av et Element:

Element *CreateElement (FieldType *field_defs)
{
  /* calculate number of bits defined by field_defs */
  int size = ?;
  /* allocate memory */
  Element *element = malloc (sizeof (Element) + (size + 7) / 8); /* replace 7 and 8 with bits per char */
  element->fields = field_defs;
  return element;
}

Og deretter å få tilgang til et element:

int GetValue (Element *element, int field)
{
   /* get number of bits in fields 0..(field - 1) */
   int bit_offset = ?;
   /* get char offset */
   int byte_offset = sizeof (Element) + bit_offset / 8;
   /* get pointer to byte containing start of data */
   char *ptr = ((char *) element) + byte_offset;
   /* extract bits of interest */
   int value = ?;
   return value;
}

Innstillingsverdier er lik for å få verdier, må bare den siste delen i endring.

Man kan forbedre den ovennevnte ved å utvide FieldTypestrukturen for å inkludere informasjon om typen av verdien som er lagret: char, int, flottør, etc, og deretter skrive accessors for hver type som kontrollerer deres nødvendige type, mot den definerte typen.

Svarte 20/05/2009 kl. 14:31
kilden bruker

stemmer
0

Jeg foreslår at kodegenerering. Hvis strukturene ikke inneholder store mengder av feltene kan du automatisk generere rutiner for hvert felt eller for en rekke felt og bruke dem som:

val = getfield_aN( myobject, n );

eller

val = getfield_foo( myobject );
Svarte 20/05/2009 kl. 14:08
kilden bruker

stemmer
0

Jeg tror din virkelige løsningen er å ikke bruke bitfields i struct, men i stedet definere enten et sett type eller litt array.

Svarte 20/05/2009 kl. 13:50
kilden bruker

stemmer
0

Hvorfor ikke bygge getval()inn til struct?

struct Whang {
    int a1;
    int a2;
    int getIth(int i) {
        int rval;
        switch (i) {
            case 1: rval = a1; break;
            case 2: rval = a2; break;
            default : rval = -1; break;
        }
        return rval;
    }
};    

int _tmain(int argc, _TCHAR* argv[])  
{  
        Whang w;  
    w.a1 = 1;  
    w.a2 = 200;

    int r = w.getIth(1);

    r = w.getIth(2);

    return 0;
}

getIth()ville ha kunnskap om innvendige av Whang, og kunne håndtere hva den inneholdt.

Svarte 20/05/2009 kl. 13:38
kilden bruker

stemmer
0

Hvis strukturen er virkelig så enkelt som beskrevet, kan du bruke en union med en rekke (eller en støpt til en array) og noen bit-tilgang magi (som i Hvordan du setter, klar og veksle en enkelt bit i C? ) .

Som Jared sier, er det generelle tilfellet hardt .

Svarte 20/05/2009 kl. 13:37
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more