10

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?
  • -
lsiebert
  • 667
  • 1
  • 5
  • 16

7 Answers7

7

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)

abelenky
  • 63,815
  • 23
  • 109
  • 159
  • 1
    How can you guarantee there is no padding between `string1` and `string2` members? – ouah Jun 05 '12 at 21:58
  • 1
    Hmm... according to http://stackoverflow.com/questions/2748995/c-struct-memory-layout "The method for altering this behavior is very compiler dependent, there is nothing in the language specifying how this is to be handled. In MSVC you would use #pragma pack(1) to turn off alignment (the 1 says align everything on 1 byte boundaries). IN GCC you would use __attribute__((packed)) in the structure definition. " however, if they are all of the same type, I don't believe padding will be added by most compilers. – lsiebert Jun 05 '12 at 22:42
6

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 = ...;
twalberg
  • 59,951
  • 11
  • 89
  • 84
5

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.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
3

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

ouah
  • 142,963
  • 15
  • 272
  • 331
  • The question explicitly said, "do I have to cast the struct to an array first". As my answer below demonstrates, it is clearly possible with a simple cast. – abelenky Jun 05 '12 at 22:00
  • The question is `can I reference string1 with tw[0] and string2 with two[1]? If so:` and I answer "no" to the question because of the constraints of the `[]` operator. What is wrong in your opinion in my answer? – ouah Jun 05 '12 at 22:02
  • @abelenky: Your cast-based solution performs illegal memory reinterpretation. It "works" only by coincidence. The language specification does not guarantee its validity. The behavior of your solution is undefined in general case. In other words, it is a hack, not a solution from the point of view if C language. – AnT stands with Russia Jun 05 '12 at 22:27
1

Nope, you can't do that in C. You can only access struct members in C via their names.

What you can do is build an array that has pointers to the same strings as those in your struct, and then use indexes for the array.

But why would you want to do that? What is the problem you're actually trying to solve with this?

1

You can use ((char **)&tw)[0] to do it, if you really wanted to, but not tw[0].

abelenky
  • 63,815
  • 23
  • 109
  • 159
univerio
  • 19,548
  • 3
  • 66
  • 68
0

If one has a struct which starts with fields that are all of the same type, one may declare a union which includes a struct of that type as well as an array of the appropriate field type. If one does this, reading or writing an element of the array will read or write the appropriate struct member. This behavior will work on all implementations I know of, and I believe it is portable if all the fields are the same size.

struct quad_int {int n0; int n1; int n2; int n3;}
union quad_int_union {struct pair p; int n[4];}

union quad_int_union my_thing;

my_thing.n[0] is synonymous with my_thing.p.n0
my_thing.n[1] is synonymous with my_thing.p.n1
etc.

supercat
  • 77,689
  • 9
  • 166
  • 211