Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Offsetof with array elements

1,094 views
Skip to first unread message

David Brown

unread,
Feb 11, 2020, 9:31:31 AM2/11/20
to
#include <stddef.h>

struct S {
int a[10];
};

int test(int i) {
return offsetof(struct S, a[i]);
}


Is this allowed in standard C? It works in most compilers I tried (even
in the most pedantic modes I checked on gcc), but failed in one embedded
compiler. According to the standard, "offsetof" expands to "an integer
constant expression", and "&s.a[i]" must evaluate to an address constant
(where "s" is an object of type "struct S").

So by my reading, the standard C does not require the expression in
"test" to be valid, and allows a conforming compiler to reject it.
Equally, it does not require a diagnostic, and allows a conforming
compiler to accept it without complaint.

Guillaume

unread,
Feb 11, 2020, 10:08:14 AM2/11/20
to
From what I read: "The type and member designator shall be such that
given static type t; then the expression &(t.member-designator)
evaluates to an address constant. (If the specified member is a bit-field,
the behavior is undefined.)"

In your example, &(t.a[i]) would evaluate to an address constant indeed,
and obviously a[i] doesn't fall into the undefined behavior category as
it's not a bit field.

There's just a small "but": the standard defines offsetof() with its
second parameter being a struct member designator. a[i] is not, unless
I'm mistaken, a member designator, but an expression derived from one.
Thus you'd be using offsetof() with (IMO) an invalid parameter, and this
should be reasonably rejected by a conforming compiler. It looks like
some compilers choose to make this implementation-defined instead. Just
my opinion on this.

James Kuyper

unread,
Feb 11, 2020, 11:33:05 AM2/11/20
to
"The array-subscript [ ] operator ... may be used in the creation of an address constant, but the value of an object may not be accessed by the use of [that] operator." (6,6p9).

Bart

unread,
Feb 11, 2020, 11:56:52 AM2/11/20
to
I tried this with the expected results on the handful of compilers tested:

--------------------
#include <stdio.h>
#include <stddef.h>

struct S {
int a[10];
};

int test(int i) {
return offsetof(struct S, a[i]);
}

int main(void) {
for (int i=0; i<10; ++i)
printf("Offset of A[%d] is %d\n",i,test(i));
}
------------------

I say 'expected' but I thought my bcc at least would fail, because I
consider 'offsetof' a device to return the offset of a struct member,
which is always going to be a compile-time constant.

It works in my case because offsetof is defined as:

#define offsetof(a,b) (size_t) &( ((a*)0) -> b)

And the body of test() becomes:

return(size_t)&(((struct S*)0)->a[i]);

Presumably your odd compiler tries to be too clever.

David Brown

unread,
Feb 11, 2020, 12:46:20 PM2/11/20
to
On 11/02/2020 17:32, James Kuyper wrote:
> "The array-subscript [ ] operator ... may be used in the creation of an address constant, but the value of an object may not be accessed by the use of [that] operator." (6,6p9).
>

Right. So since the expression "offsetof(struct S, a[i])" has to access
the object "i" in order to calculate the address (or the offset, in this
case), it is disallowed?

David Brown

unread,
Feb 11, 2020, 12:49:33 PM2/11/20
to
Yes, that's a common way to define "offsetof". It involves behaviour
that is undefined in the C standard (there is AFAIK no way to write the
offsetof macro using only standards-defined C), but as long as the
compiler is happy with it, it's no problem.

>
> And the body of test() becomes:
>
>   return(size_t)&(((struct S*)0)->a[i]);
>
> Presumably your odd compiler tries to be too clever.

It sounds like it is justified in rejecting the code, while most
compilers are more lenient than the standard requires.

Manfred

unread,
Feb 11, 2020, 1:24:33 PM2/11/20
to
It sound to me like this part says the address constant creation /is/
allowed, since the object is not accessed, i.e. its address is not
dereferenced. (This is obvious in the common implementation of offsetof
that places S at address 0).

But the problem still holds for the other part of the specification of
offsetof, the one pointed out by Guillaume, which is my main doubt as well:

Guillaume wrote:
> the standard defines offsetof() with its second parameter being a struct member designator. a[i] is not, unless I'm mistaken, a member designator, but an expression derived from one. Thus you'd be using offsetof() with (IMO) an invalid parameter

If you look at 7.19p3, the specification of offsetof(type,
member-designator) has requirements for both /type/ and /member-designator/

The requirement for /member-designator/ uses the wording of the
Guillaume quote above, the requirement for /type/ refers to the creation
of an address constant, which is what James' comment is about.

Manfred

unread,
Feb 11, 2020, 1:36:31 PM2/11/20
to
On 2/11/2020 7:24 PM, Manfred wrote:
> On 2/11/2020 6:46 PM, David Brown wrote:
>> On 11/02/2020 17:32, James Kuyper wrote:
>>> "The array-subscript [ ] operator ... may be used in the creation of
>>> an address constant, but the value of an object may not be accessed
>>> by the use of [that] operator." (6,6p9).
>>>
>>
>> Right.  So since the expression "offsetof(struct S, a[i])" has to
>> access the object "i" in order to calculate the address (or the
>> offset, in this case), it is disallowed?
>>
>
> It sound to me like this part says the address constant creation /is/
> allowed, since the object is not accessed, i.e. its address is not
> dereferenced. (This is obvious in the common implementation of offsetof
> that places S at address 0).
>

Hmm. doubting myself here - "i" is not a constant, thus &s.a[i] is not
constant either, so how can it be an address constant? Which is probably
another derivation of your conclusion above.

Bonita Montero

unread,
Feb 11, 2020, 3:00:41 PM2/11/20
to
How about this:

#include <stddef.h>
#define array_offsetof(type, member, index) (size_t)(offsetof(type,
member) + sizeof(((type *)NULL)->##member[0]) * index)

Bart

unread,
Feb 11, 2020, 3:13:53 PM2/11/20
to
Did you test it? It fails on most (not all) compilers including gcc.

This version seems to work on all the ones I tried:

#define array_offsetof(type, member, index) \
(offsetof(type, member) + sizeof( ((type *)0)->member[0]) * index)

It's invoked like this:

return array_offsetof(struct S, a, i);

Bonita Montero

unread,
Feb 11, 2020, 3:24:26 PM2/11/20
to
Don't know why, but my version works when compiled as C++ and not
as C. I thought that might be o.k. for C also.
But I'm wondering why you won't have to write (struct type *) but
(type *). Did you typedef ?

Bart

unread,
Feb 11, 2020, 3:33:54 PM2/11/20
to
No. That means that in C, the 'type' of the stuct is 'struct S' rather
than just 'S' if you do this:

typedef struct S S;

Bonita Montero

unread,
Feb 11, 2020, 3:39:39 PM2/11/20
to
Of course, but you wrote "((type *)0)->member[0])" without struct.
So you rely on a duplicate typedef.

Bart

unread,
Feb 11, 2020, 3:48:32 PM2/11/20
to
'type' is the name of a macro parameter. The macro argument was 'struct S':

return array_offsetof(struct S, a, i);

I could have chosen to insert 'struct' into the macro body, but then it
could only be invoked with a struct tag name, not either 'struct tag' or
a typedefed struct.

James Kuyper

unread,
Feb 11, 2020, 10:50:48 PM2/11/20
to
On 2/11/20 1:24 PM, Manfred wrote:
> On 2/11/2020 6:46 PM, David Brown wrote:
>> On 11/02/2020 17:32, James Kuyper wrote:
>>> "The array-subscript [ ] operator ... may be used in the creation of
>>> an address constant, but the value of an object may not be accessed by
>>> the use of [that] operator." (6,6p9).
>>>
>>
>> Right.  So since the expression "offsetof(struct S, a[i])" has to access
>> the object "i" in order to calculate the address (or the offset, in this
>> case), it is disallowed?
>>
>
> It sound to me like this part says the address constant creation /is/
> allowed, since the object is not accessed, i.e. its address is not
> dereferenced.

The relevant object is "i", not S. And it's the value of the object, not
it's address, which, if accessed, violates that rule. The value stored
in 'i' is needed to determine that offset, so this construct does
violate that rule.

David Brown

unread,
Feb 12, 2020, 4:56:53 AM2/12/20
to
I have no problem finding the offset to the array item. My question was
about whether it can be done portably using offsetof directly - was this
a bug or limitation in the one particular compiler, or are other
compilers being helpfully lenient? (The conclusion seems to be the later.)

Ben Bacarisse

unread,
Feb 12, 2020, 5:39:59 AM2/12/20
to
The ## is wrong. Maybe it was typo? There are no valid tokens that
start -> (other than -> itself) so pasting any other token to -> will
always be an error.

It has undefined behaviour (dereferencing a null pointer) but may well
work. However, if you are prepared to accept that portability risk, why
not use the simpler, two argument, macro that has already been posted?

--
Ben.

fir

unread,
Feb 12, 2020, 7:41:36 AM2/12/20
to
W dniu wtorek, 11 lutego 2020 15:31:31 UTC+1 użytkownik David Brown napisał:
> #include <stddef.h>
>
> struct S {
> int a[10];
> };
>
> int test(int i) {
> return offsetof(struct S, a[i]);
> }
>
>
dont know how as to foofset of but i ytesterday noticed that my structure is
aligned up to 4 bytes, this its hget bugger than it really is

(this was fat_char for char in my aditor
when i want to store not only character code but also a color and a kinda timestamp, timestamp i wold beed to make kolor of text based on how freshly it was
written)

thus maybe 2 questions (i know they are very lazy but im tired, the more im tired the less i can do, sometimes even googling is to much)

1) can samoe know how i could disable this upaligment? (asking for suriosity)

2) does someone maybe know what to use to store such timestamp, i got 32 bits of space i think and i would like to get 1 second of resolution in that timestamp

simply comparing timestamp of given char with current timestamp and colorise based on such distances as
- not older than 15 minutes
- not older than 4 hours
- not older than 2 days
- not older than a month

etc

Manfred

unread,
Feb 12, 2020, 7:54:02 AM2/12/20
to
Yes, in fact in my follow-up I had an afterthought about "i" as well -
being an "object" whose value is accessed is substantially incompatible
with a constant expression (which by definition can be evaluated at
compile time and may have no storage) - this is to say that the
requirement that "an object may not be accessed" is to guarantee that an
address constant is a constant expression (p6.6)

Bonita Montero

unread,
Feb 12, 2020, 8:07:43 AM2/12/20
to
> I have no problem finding the offset to the array item. ...

So your question is a waste of time.

Bonita Montero

unread,
Feb 12, 2020, 8:08:54 AM2/12/20
to
> The ## is wrong. ...

Maybe useless, but not necessarily wrong.
Compiled as C++ with MSVC++ everything is o.k..

James Kuyper

unread,
Feb 12, 2020, 8:31:36 AM2/12/20
to
On Wednesday, February 12, 2020 at 8:08:54 AM UTC-5, Bonita Montero wrote:
> > The ## is wrong. ...
>
> Maybe useless, but not necessarily wrong.

Yes, it's a syntax error.

> Compiled as C++ with MSVC++ everything is o.k..

Every compiler accepts some code that is wrong, MSVC++ more than most.

David Brown

unread,
Feb 12, 2020, 9:25:02 AM2/12/20
to
On 12/02/2020 14:07, Bonita Montero wrote:
>> I have no problem finding the offset to the array item. ...
>
> So your question is a waste of time.
>

No - otherwise I would not have asked it.

Ben Bacarisse

unread,
Feb 12, 2020, 12:50:25 PM2/12/20
to
This is comp.lang.c but C++ happens to agree here -- in both C and C++
any use of your macro produced undefined behaviour. That can include
accepting the code by not doing what ## usually means. That's somewhat
worse than "useless".

--
Ben.

James Kuyper

unread,
Feb 12, 2020, 2:45:30 PM2/12/20
to
I said that wrong. In the macro you suggested,

#define array_offsetof(type, member, index) (size_t)(offsetof(type,
member) + sizeof(((type *)NULL)->##member[0]) * index)

your use of ## is not, in itself, a syntax error.
However, it is guaranteed to result in a syntax
error. Think for a moment about what ->##member
is supposed to do. It is supposed to first
replace "member" with the reprocessing tokens
Of the corresponding macro argument. Then it's
supposed to take the reprocessing token that
immediately precedes the ## (which is "->" in
this case), and the one that immediately
follows it (which, in this case, will normally
be the identifier for a member of the struct),
and concatenate them together to form a single
preprocessing token.
Now, why would you tell it to do that? The
character sequence "->member_identifier"
doesn't match any of the permitted forms for a
single preprocessing token. There's nothing
that you could pass as the second argument that
would result in a valid preprocessing token
after that concatenation. That sequence of
characters would normally be parsed as two
separate tokens, "->" and "member_identifier",
which would work fine. However, such parsing is
already complete by the time ## operators are
evaluated. What ## puts together will never
again be taken apart. If it doesn't already have
the correct form to qualify as a single
preprocessing token, the behavior is simply
undefined.
I suspect you had a subtly different concept of
what ## does, a concept that makes it not merely
harmless in this context, but actually necessary
for some reason. There is no such reason - with
the ## removed, that macro would do precisely
what I believe you wanted it to do.

already...@yahoo.com

unread,
Feb 13, 2020, 5:42:32 AM2/13/20
to
#define array_offsetof(type, member, index) \
(offsetof(type, member[0]) + \
(offsetof(type, member[1]) - offsetof(type, member[0]))*(index))

James Kuyper

unread,
Feb 14, 2020, 12:20:48 AM2/14/20
to
On a system where offsetof(type, member[1]) works as needed for that
expression to be valid, there is no need for array_offsetof().

David Brown

unread,
Feb 14, 2020, 2:24:41 AM2/14/20
to
I think "offsetof(type, member[1])" should always work, since the index
is a constant. It is "offsetof(type, member[i])" with variable "i" that
is not defined in the standard, and not always supported. (But it seems
that most compilers /do/ support it - I just happen to have come across
an unusual one that rejects it.)

already...@yahoo.com

unread,
Feb 14, 2020, 7:00:00 AM2/14/20
to
TI or Microchip ?

David Brown

unread,
Feb 14, 2020, 9:25:36 AM2/14/20
to
TI Code Composter. Given past experience (with joys such as "We know
that the C language says uninitialised file-scope variables will be
initialised to 0 before main() starts, but we thought it would be more
fun not to do this and just mention it as a footnote hidden in the
middle of the manual that no one reads"), when CC does something
different from other compilers, I assume CC is wrong. In this case,
however, it seems CC is justified - but less helpful than all other
compilers I tried.

With Microchip's own compilers (as distinct from their gcc builds for
MIPS microcontrollers), you assume that the authors have never even
looked at the C standards and merely guessed at some of the language
features people might want in a C compiler. I wouldn't have bothered
posting if it were their tools - I'd have been happy enough to see that
it supported a struct with an array member. (Yes, Microchip used to
make - and sell, at high prices - "C" compilers that supported structs
and arrays, but not arrays of structs or structs with array members.)

James Kuyper

unread,
Feb 15, 2020, 8:18:48 AM2/15/20
to
On 2/14/20 2:24 AM, David Brown wrote:
> On 14/02/2020 06:20, James Kuyper wrote:
>> On 2/13/20 5:42 AM, already...@yahoo.com wrote:
>>> On Tuesday, February 11, 2020 at 10:00:41 PM UTC+2, Bonita Montero wrote:
>>>> How about this:
>>>>
>>>> #include <stddef.h>
>>>> #define array_offsetof(type, member, index) (size_t)(offsetof(type,
>>>> member) + sizeof(((type *)NULL)->##member[0]) * index)
>>>
>>> #define array_offsetof(type, member, index) \
>>> (offsetof(type, member[0]) + \
>>> (offsetof(type, member[1]) - offsetof(type, member[0]))*(index))
>>
>> On a system where offsetof(type, member[1]) works as needed for that
>> expression to be valid, there is no need for array_offsetof().
>>
>
> I think "offsetof(type, member[1])" should always work, since the index
> is a constant.

You're right. I should have gotten a good sleep before posting that message.

David Brown

unread,
Feb 15, 2020, 9:49:17 AM2/15/20
to
If only more people (including myself) followed that advice!

Tim Rentsch

unread,
Feb 16, 2020, 5:42:07 AM2/16/20
to
Ben Bacarisse <ben.u...@bsb.me.uk> writes:

> Bonita Montero <Bonita....@gmail.com> writes:
>
>> How about this:
>>
>> #include <stddef.h>
>> #define array_offsetof(type, member, index) (size_t)(offsetof(type,
>> member) + sizeof(((type *)NULL)->##member[0]) * index)
>
> The ## is wrong. Maybe it was typo? There are no valid tokens that
> start -> (other than -> itself) so pasting any other token to -> will
> always be an error.
>
> It has undefined behaviour (dereferencing a null pointer) but may well
> work. [...]

Why do you say the pointer dereference has undefined behavior?
The -> operator is part of an operand to 'sizeof', and hence is
not evaluated.

already...@yahoo.com

unread,
Feb 16, 2020, 6:15:24 AM2/16/20
to
Well, I am aware of this "feature" of TI tools since time (18 or 20 years ago) when it was properly documented. So, they can't surprise me!

They did manage to surprise me last year when I found out that division of "unsigned long long" argument (40 bits) produces correct results only when the results of division fits in 32 bits. Cool, isn't it?

Ben Bacarisse

unread,
Feb 16, 2020, 6:18:13 AM2/16/20
to
Tim Rentsch <tr.1...@z991.linuxsc.com> writes:

> Ben Bacarisse <ben.u...@bsb.me.uk> writes:
>
>> Bonita Montero <Bonita....@gmail.com> writes:
>>
>>> How about this:
>>>
>>> #include <stddef.h>
>>> #define array_offsetof(type, member, index) (size_t)(offsetof(type,
>>> member) + sizeof(((type *)NULL)->##member[0]) * index)
>>
>> The ## is wrong. Maybe it was typo? There are no valid tokens that
>> start -> (other than -> itself) so pasting any other token to -> will
>> always be an error.
>>
>> It has undefined behaviour (dereferencing a null pointer) but may well
>> work. [...]
>
> Why do you say the pointer dereference has undefined behavior?

Because I was not paying attention.

> The -> operator is part of an operand to 'sizeof', and hence is
> not evaluated.

--
Ben.

David Brown

unread,
Feb 16, 2020, 8:09:31 AM2/16/20
to
It certainly surprised me the first time I saw it (no doubt 20+ years
ago). It was fun trying to figure out why behaviour was different from
power-on reset, or restarting with debugger after first running the program.

And it surprised me many years later when I used a different TI compiler
on a different platform, and they /still/ had this idiotic misfeature.
It surprised me that they hadn't learned.

>
> They did manage to surprise me last year when I found out that
> division of "unsigned long long" argument (40 bits) produces correct
> results only when the results of division fits in 32 bits. Cool,
> isn't it?

Which target was that with a 40-bit long long? I know they support
20-bit types on some msp430 cores (to match the 20-bit addressing). Was
this an accumulator type for a DSP or something?

already...@yahoo.com

unread,
Feb 16, 2020, 9:36:41 AM2/16/20
to
Yes, DSP. C55 family.
Yes, 40 bits of 'long long' are derived from the size of accumulators (it has four).

Chris M. Thomasson

unread,
Feb 16, 2020, 7:03:57 PM2/16/20
to
On 2/11/2020 6:31 AM, David Brown wrote:
> #include <stddef.h>
>
> struct S {
> int a[10];
> };
>
> int test(int i) {
> return offsetof(struct S, a[i]);
> }
>
>
> Is this allowed in standard C? It works in most compilers I tried (even
> in the most pedantic modes I checked on gcc), but failed in one embedded
> compiler. According to the standard, "offsetof" expands to "an integer
> constant expression", and "&s.a[i]" must evaluate to an address constant
> (where "s" is an object of type "struct S").
>
> So by my reading, the standard C does not require the expression in
> "test" to be valid, and allows a conforming compiler to reject it.
> Equally, it does not require a diagnostic, and allows a conforming
> compiler to accept it without complaint.
>

Don't know exactly why, but this reminds me of the following macro that
can determine the alignment of a type:

#define RALLOC_ALIGN_OF(mp_type) \
offsetof( \
struct { \
char pad_RALLOC_ALIGN_OF; \
mp_type type_RALLOC_ALIGN_OF; \
}, \
type_RALLOC_ALIGN_OF \
)

Tim Rentsch

unread,
Feb 18, 2020, 4:40:00 AM2/18/20
to
Guillaume <mes...@bottle.org> writes:

> Le 11/02/2020 at 15:31, David Brown a ecrit:
>
>> #include <stddef.h>
>>
>> struct S {
>> int a[10];
>> };
>>
>> int test(int i) {
>> return offsetof(struct S, a[i]);
>> }
>>
>>
>> Is this allowed in standard C? [...]

Undefined behavior, as was pointed out downthread, because of a
non-constant address calculation. However, leading into the next
point, what if the address calculation were suitably constant, and so
satisfies the stipulation that "the expression &(t.member-designator)
evaluates to an address constant"?

> [...]
>
> There's just a small "but": the standard defines offsetof() with its
> second parameter being a struct member designator. a[i] is not, unless
> I'm mistaken, a member designator, but an expression derived from
> one. Thus you'd be using offsetof() with (IMO) an invalid parameter,
> and this should be reasonably rejected by a conforming compiler. It
> looks like some compilers choose to make this implementation-defined
> instead. Just my opinion on this.

A strict reading of the text of the Standard supports this conclusion,
even if the array index expression were suitably constant. The
Standard consistently uses the term "stucture member" to mean an
first-level subobject, and not a sub-subobject for any level beyond
the first. The safest course here would use

offsetof( struct S, a )

and add in the adjustment for a[i] using sizeof and a multiplication.

Probably this reading is not what was intended for offsetof(), but I
haven't seen any evidence of that. (Fair disclosure: I haven't made
any special effort to find any either.) But a good rule to follow in
cases where the Standard isn't 100% definitive is to err on the side
of caution, choosing the most conservative path reasonably available.
And here that is easy to do, by using the simpler call to offsetof()
shown above, and adjusting the result with a sizeof, etc.

Tim Rentsch

unread,
Feb 18, 2020, 4:43:29 AM2/18/20
to
It isn't the use of [] that accesses an object. It is the lvalue
conversion of 'i' that accesses an object.
0 new messages