30

Ignoring padding/alignment issues and given the following struct, what is best way to get and set the value of member_b without using the member name.

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

Put another way; How would you express the following in terms of pointers/offsets:

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

My guess is to

  • calculate the offset by finding the sizeof the member_a (int)
  • cast the struct to a single word pointer type (char?)
  • create an int pointer and set the address (to *charpointer + offset?)
  • use my int pointer to set the memory contents

but I get a bit confused about casting to a char type or if something like memset is more apropriate or if generally i'm aproching this totally wrong.

Cheers for any help

Chris Farmiloe
  • 13,935
  • 5
  • 48
  • 57

6 Answers6

34

The approach you've outlined is roughly correct, although you should use offsetof instead of attempting to figure out the offset on your own. I'm not sure why you mention memset -- it sets the contents of a block to a specified value, which seems quite unrelated to the question at hand.

Here's some code to demonstrate how it works:

#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;
}
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • thanks for your response, memset was just an passing thought since it was just another way of writing memory at an address, It was more of a "don't hold me to my method - I'm open to doing it a different way" comment :) The offset is actaully the one part I'm fine with, it's how to actually offset a void pointer I think I'm struggling with.... is it suitable to cast my struct pointer to char* for instance? – Chris Farmiloe Jan 11 '10 at 18:37
  • Or: `((struct mystruct *)p)->member_b = 3;` – Alok Singhal Jan 11 '10 at 18:43
  • Is the `(char*)` cast needed because of memory (mis)alignment? – Marco Bolis Oct 31 '21 at 11:44
31

The full technique:

  1. Get the offset using offsetof:

    b_offset = offsetof(struct mystruct, member_b);

  2. Get the address of your structure as a char * pointer.

    char *sc = (char *)s;

  3. Add the add the offset to the structure address, cast the value to a pointer to the appropriate type and dereference:

    *(int *)(sc + b_offset)

R Samuel Klatchko
  • 74,869
  • 16
  • 134
  • 187
  • 1
    +1 for portable method [link](http://www.safercode.com/blog/2009/05/05/find-element-offset-structure-c-offsetof.html) – sfossen Jun 02 '10 at 17:04
4

Ignoring padding and alignment, as you said...

If the elements you're pointing to are entirely of a single type, as in your example, you can just cast the structure to the desired type and treat it as an array:

printf("%i", ((int *)(&s))[1]);
mskfisher
  • 3,291
  • 4
  • 35
  • 48
4

It's possible calculate the offset based on the struct and NULL as reference pointer e.g " &(((type *)0)->field)"

Example:

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;
}
Obik
  • 49
  • 2
  • I'm relatively new to C so I find this amazing, but how does it work? Why can you access h from that casted null pointer? – seshoumara Feb 04 '21 at 21:21
1

In this particular example, you can address it by *((int *) ((char *) s + sizeof(int))). I'm not sure why you want that, so I'm assuming didactic purposes, therefore the explanation follows.

The bit of code translates as: take the memory starting at address s and treat it as memory pointing to char. To that address, add sizeof(int) char-chunks - you will get a new address. Take the value that the address thus created and treat it as an int.

Note that writing *(s + sizeof(int)) would give the address at s plus sizeof(int) sizeof(mystruct) chunks

Edit: as per Andrey's comment, using offsetof:

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

Edit 2: I replaced all bytes with chars as sizeof(char) is guaranteed to be 1.

laura
  • 7,280
  • 4
  • 35
  • 43
  • Why would you use `byte` and not `char`? Also `((byte *) s + sizeof(int))` is of type `byte *`, which is the same as `char *`, so dereferencing it will (may) not give you an `int` - if `sizeof(int) != sizeof(char)`. You need a cast to `int *` before dereferencing. – Alok Singhal Jan 11 '10 at 18:38
  • Correct, partially - your example is more didactic. I've used byte because I feel it has more didactic meaning. Note however, that sizeof(byte) might not be equal to sizeof(char), depending on the architecture. – laura Jan 11 '10 at 18:39
  • Not correct. In ordert to access the `member_b` field you have to add `offsetof(mystruct, member_b)` which is not necessarily the same as `sizeof(int)`. – AnT stands with Russia Jan 11 '10 at 18:40
  • And what is `byte`? The C standard doesn't define it. Also, if `sizeof(byte) != sizeof(char)`, then you definitely want `char` and not `byte` in your code since `sizeof(char)` is 1. – Alok Singhal Jan 11 '10 at 18:44
  • AndreyT: I did allow this to ignore padding issues so sizeof/offset can be considered same. Laura: thanks that did it for me! – Chris Farmiloe Jan 11 '10 at 18:45
  • @AndreyT: I agree, `offsetof` is better, I was trying to "correct" laura's code without changing its form (I know even then it's not correct). – Alok Singhal Jan 11 '10 at 18:47
  • @ozone: `sizeof`/`offsetof` cannot be coinsidered "the same" in this case regardless of what you allowed. – AnT stands with Russia Jan 11 '10 at 18:50
  • @Alok: actually the C standard does define "byte" -- it's the amount of storage needed for one `char`. IOW, as far as C (and C++) care, 'byte' and 'char' are virtually interchangeable terms. – Jerry Coffin Jan 11 '10 at 18:52
  • @laura, not correct even now. You want `offsetof(struct mystruct, member_b)`, or better: `((struct mystruct *)s)->member_b = 3;` – Alok Singhal Jan 11 '10 at 18:55
  • @Jerry, not as a type though. Without a `typedef`, the cast `(byte *)` doesn't mean anything in C. – Alok Singhal Jan 11 '10 at 18:56
  • @Alok, I think he wanted to avoid using `((struct mystruct *)s)->member_b = 3;`. – laura Jan 11 '10 at 18:56
0

It sounds from your comments that what you're really doing is packing and unpacking a bunch of disparate data types into a single block of memory. While you can get away with doing that with direct pointer casts, as most of the other answers have suggested:

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);
}

The problem is that this is non-portable. There's no general way to ensure that the types are stored within your block with the correct alignment, and some architectures simply cannot do loads or stores to unaligned addresses. In the special case where the layout of your block is defined by a declared structure, it will be OK, because the struct layout will include the necessary padding to ensure the right alignment. However since you can't access the members by name, it sounds like this isn't actually what you're doing.

To do this portably, you need to use 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;
}

(similar for the other types).

caf
  • 233,326
  • 40
  • 323
  • 462