Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

referencing struct fields in c with square brackets and an index instead of . and ->?

Tags:

c

struct

Assuming I have a structure such as:

typedef struct
{
    char * string1;
    char * string2;
} TWO_WORDS;

such that all the fields are of the same type, and my main has

TWO_WORDS tw;

can I reference string1 with tw[0] and string2 with two[1]? If so:

  • is this part of the c standard?
  • do i have to cast the struct to an array first?
  • what about fields which are different sizes in memory
  • what about fields which are different types but the same size?
  • can you do pointer arithmetic within a structure?
  • -
like image 754
lsiebert Avatar asked Jun 05 '12 21:06

lsiebert


4 Answers

No, you cannot use index access to struct data members, unless you take specific steps to emulate it.

In C++ this functionality can be emulated by using a C++-specific pointer type known as "pointer-to-data-member". C language has no such type, but it can in turn be emulated by using the standard offsetof macro and pointer arithmetic.

In you example it might look as follows. First, we prepare a special offset array

const size_t TW_OFFSETS[] = 
  { offsetof(TWO_WORDS, string1), offsetof(TWO_WORDS, string2) };

This offset array is later used to organize index access to struct members

*(char **)((char *) &tw + TW_OFFSETS[i]); 
/* Provides lvalue access to either `tw.string1` or `tw.string2` depending on 
   the value of `i` */

It doesn't look pretty (although it can be made to look better by using macros), but that's the way it is in C.

For example, we can define

 #define TW_MEMBER(T, t, i) *(T *)((char *) &(t) + TW_OFFSETS[i])

and use it in the code as

 TW_MEMBER(char *, tw, 0) = "Hello";
 TW_MEMBER(char *, tw, 1) = "World";

 for (int i = 0; i < 2; ++i)
   printf("%s\n", TW_MEMBER(char *, tw, i));

Note that this approach is free from the serious issues present in the solution based on reinterpreting the struct as char*[2] array (regradless of whether it is done through a union or through a cast). The latter is a hack, illegal from the formal point of view and generally invalid. The offsetof-based solution is perfectly valid and legal.

like image 111
AnT Avatar answered Nov 07 '22 14:11

AnT


can I reference string1 with tw[0] and string2 with two[1]?

No you cannot in C, tw is a structure not a pointer.

The constraints of the [] operator require one of the operand to be of a pointer type.

To access string1, you can use this expression: tw.string1

like image 33
ouah Avatar answered Sep 23 '22 12:09

ouah


I got pretty close with this construct:

((char**)&tw)[0];

As an example:

int main()
{
    typedef struct
    {
        char * string1;
        char * string2;
    } TWO_WORDS;

    TWO_WORDS tw = {"Hello", "World"};

    printf("String1: %s\n", ((char**)&tw)[0]);
    printf("String2: %s\n", ((char**)&tw)[1]);

    return 0;
}

It is not guaranteed to work, as the compiler may add padding between fields. (Many compilers have a #pragma that will avoid padding of structs)

To answer each of your questions:

  • is this part of the c standard? NO

  • do i have to cast the struct to an array first? YES

  • what about fields which are different sizes in memory
    This can be done with even more "evil" casting and pointer-math

  • what about fields which are different types but the same size?
    This can be done with even more "evil" casting and pointer-math

  • can you do pointer arithmetic within a structure?
    Yes (not guaranteed to always work as you might expect, but a structure is just a piece of memory that you can access with pointers and pointer-math)

like image 7
abelenky Avatar answered Nov 07 '22 15:11

abelenky


As @ouah points out, no you can't do it quite that way. However, you could:

typedef union
{ char *a[2];
  struct
  { char *string1;
    char *string2;
  } s;
} TWO_WORDS;

TWO_WORDS t;

t.a[0] = ...;
t.a[1] = ...;
t.s.string1 = ...;
t.s.string2 = ...;
like image 6
twalberg Avatar answered Nov 07 '22 15:11

twalberg