简体   繁体   中英

Why does a non-constant offsetof expression work?

Why does this work:

#include <sys/types.h>
#include <stdio.h>
#include <stddef.h>

typedef struct x {
    int a;
    int b[128];
} x_t;


int function(int i)
{
  size_t a;

  a = offsetof(x_t, b[i]);

  return a;
}

int main(int argc, char **argv)
{
    printf("%d\n", function(atoi(argv[1])));
}

If I remember the definition of offsetof correctly, it's a compile time construct. Using 'i' as the array index results in a non-constant expression. I don't understand how the compiler can evaluate the expression at compile time. Why isn't this flagged as an error?

The C standard does not require this to work, but it likely works in some C implementations because offsetof(type, member) expands to something like:

type t; // Declare an object of type "type".
char *start = (char *) &t; // Find starting address of object.
char *p = (char *) &t->member; // Find address of member.
p - start; // Evaluate offset from start to member.

I have separated the above into parts to display the essential logic. The actual implementation of offsetof would be different, possibly using implementation-dependent features, but the core idea is that the address of a fictitious or temporary object would be subtracted from the address of the member within the object, and this results in the offset. It is designed to work for members but, as an unintended effect, it also works (in some C implementations) for elements of arrays in structures.

It works for these elements simply because the construction used to find the address of a member also works to find the address of an element of an array member, and the subtraction of the pointers works in a natural way.

it's a compile time construct

AFAICS, there are no such constraints. All the standard says is:

[C99, 7.17]:

The macro...

 offsetof(type, member-designator)

...

The type and member designator shall be such that given

static type t;

then the expression &(t.member-designator) evaluates to an address constant.

offsetof (type,member) Return member offset: This macro with functional form returns the offset value in bytes of member member in the data structure or union type type.

http://www.cplusplus.com/reference/cstddef/offsetof/ (C, C++98 and C++11 standards)

I think I understand this now.

The offsetof() macro does not evaluate to a constant, it evaluates to a run-time expression that returns the offset. Thus as long as type.member is valid syntax, the compiler doesn't care what it is. You can use arbitrary expressions for the array index. I had thought it was like sizeof and had to be constant at compile time.

There has been some confusion on what exactly is permitted as a member-designator. Here are two papers I am aware of:

However, even quite old versions of GCC, clang, and ICC support calculating array elements with dynamic offset. Based onRaymond's blog I guess that MSVC has long supported it too.


I believe it is based out of pragmatism. For those not familiar, the "struct hack" and flexible array members use variable-length data in the last member of a struct:

struct string {
  size_t size;
  const char data[];
};

This type is often allocated with something like this:

string *string_alloc(size_t size) {
  string *s = malloc(offsetof(string, data[size]));
  s->size = size;
  return s;
}

Admittedly, this latter part is just a theory. It's such a useful optimization that I imagine that initially it was permitted on purpose for such cases, or it was accidentally supported and then found to be useful for exactly such cases.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM