Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do one use `offsetof` to access a field in a standard conforming way?

Let's suppose I have a struct and extract the offset to a member:

struct A {
    int x;
};

size_t xoff = offsetof(A, x);

how can I, given a pointer to struct A extract the member in a standard conforming way? Assuming of course that we have a correct struct A* and a correct offset. One attempt would be to do something like:

int getint(struct A* base, size_t off) {
    return *(int*)((char*)base + off); 
}

Which probably will work, but note for example that pointer arithmetics only seem to be defined in the standard if the pointers are pointers of the same array (or one past the end), this need not be the case. So technically that construct would seem to rely on undefined behaviour.

Another approach would be

int getint(struct A* base, size_t off) {
    return *(int*)((uintptr_t)base + off);
}

which also probably would work, but note that intptr_t is not required to exist and as far as I know arithmetics on intptr_t doesn't need to yield the correct result (for example I recall some CPU has the capability to handle non-byte aligned addresses which would suggest that intptr_t increases in steps of 8 for each char in an array).

It looks like there's something forgotten in the standard (or something I've missed).

like image 331
skyking Avatar asked May 24 '16 11:05

skyking


1 Answers

Per the C Standard, 7.19 Common definitions <stddef.h>, paragraph 3, offsetof() is defined as:

The macros are

NULL

which expands to an implementation-defined null pointer constant; and

offsetof(*type*, *member-designator*)

which expands to an integer constant expression that has type size_t, the value of which is the offset in bytes, to the structure member (designated by member-designator), from the beginning of its structure (designated by type).

So, offsetoff() returns an offset in bytes.

And 6.2.6.1 General, paragraph 4 states:

Values stored in non-bit-field objects of any other object type consist of n × CHAR_BIT bits, where n is the size of an object of that type, in bytes.

Since CHAR_BIT is defined as the number of bits in a char, a char is one byte.

So, this is correct, per the standard:

int getint(struct A* base, size_t off) {
    return *(int*)((char*)base + off); 
}

That converts base to a char * and adds off bytes to the address. If off is the result of offsetof(A, x);, the resulting address is the address of x within the structure A that base points to.

Your second example:

int getint(struct A* base, size_t off) {
    return *(int*)((intptr_t)base + off);
}

is dependent upon the result of the addition of the signed intptr_t value with the unsigned size_t value being unsigned.

like image 91
Andrew Henle Avatar answered Sep 27 '22 22:09

Andrew Henle