Accessing a struct field in adjacent to a conditional expression - c

why there is a difference between the 2 next code segments:
struct g {
int m[100];
};
struct a {
struct g ttt[40];
struct g hhh[40];
}man;
extern int bar(int z);
//this code generate a call to memcopy.
void foo1(int idx){
bar(((idx == 5) ? man.hhh[idx+7] : man.ttt[idx+7]).m[idx+3]);
}
//this code doesn't generate a call to memcopy.
void foo2(int idx){
bar(((idx == 5) ? man.hhh[idx+7].m[idx+3] : man.ttt[idx+7].m[idx+3]));
}
In both codes segment I want to send the same field (depends on the conditional expression) to bar function. However one the first code generate a call to memcopy (when compiled with clang to powerpc arch it can be seen clearly). I wrote a little main and run the 2 functions and they gave me the same output (compiled with gcc 4.4.7).

This answer applies to C only - the question is dual-tagged but I am assuming OP is using C for reasons that will become clear later.
Here's the first expression again:
((idx == 5) ? man.hhh[idx+7] : man.ttt[idx+7]).m[idx+3]
The type of the conditional expression is struct g. However, the result of the conditional operator in C is not an lvalue. What is it then?
In C11 6.2.4p8 it's explicitly defined as a value of temporary lifetime.
In C90 the m[idx+3] is ill-formed: m is not an lvalue because the . operator only yields an lvalue if the left operand was an lvalue; and the array-pointer decay only applies to lvalues.
In C99 array-pointer decay happens to all values, but it's not explicitly stated where decayed m points.
Personally I think it's clear enough that in C99, something akin to the C11 behaviour was intended, so I would regard the code as well-defined in C99. Further discussion here. This is probably a moot point, as on all the compilers I tried, they gave the same result for -std=c99 as they did for -std=c11.
Moving forward then: In C11 (and probably C99), Snippet 1 should give the right result. Your compiler does that, but it seems that it optimizes the code poorly. It naively copies the whole value resulting from the conditional operator before indexing into it.
Testing with godbolt, I found that all versions of "x86 clang" and "PowerPC gcc 4.8" used memcpy; but "x86 gcc" was able to optimize the code.
In C++, the result of the conditional operator is an lvalue if the second and third operands were lvalues of the same type, so this problem shouldn't arise in that language.
To avoid this problem, use an alternative where the result of the conditional operator is not a struct or union value. For example you could just use Snippet 2; or either of:
bar( ((idx == 5) ? &man.hhh[idx+7] : &man.ttt[idx+7]))->m[idx+3] );
bar( ((idx == 5) ? man.hhh : man.ttt)[idx+7].m[idx+3] );

Related

C: Reading 8 bytes from a region of size 0 [-Wstringop-overread] [duplicate]

Just curious, what actually happens if I define a zero-length array int array[0]; in code? GCC doesn't complain at all.
Sample Program
#include <stdio.h>
int main() {
int arr[0];
return 0;
}
Clarification
I'm actually trying to figure out if zero-length arrays initialised this way, instead of being pointed at like the variable length in Darhazer's comments, are optimised out or not.
This is because I have to release some code out into the wild, so I'm trying to figure out if I have to handle cases where the SIZE is defined as 0, which happens in some code with a statically defined int array[SIZE];
I was actually surprised that GCC does not complain, which led to my question. From the answers I've received, I believe the lack of a warning is largely due to supporting old code which has not been updated with the new [] syntax.
Because I was mainly wondering about the error, I am tagging Lundin's answer as correct (Nawaz's was first, but it wasn't as complete) -- the others were pointing out its actual use for tail-padded structures, while relevant, isn't exactly what I was looking for.
An array cannot have zero size.
ISO 9899:2011 6.7.6.2:
If the expression is a constant expression, it shall have a value greater than zero.
The above text is true both for a plain array (paragraph 1). For a VLA (variable length array), the behavior is undefined if the expression's value is less than or equal to zero (paragraph 5). This is normative text in the C standard. A compiler is not allowed to implement it differently.
gcc -std=c99 -pedantic gives a warning for the non-VLA case.
As per the standard, it is not allowed.
However it's been current practice in C compilers to treat those declarations as a flexible array member (FAM) declaration:
C99 6.7.2.1, §16: As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member.
The standard syntax of a FAM is:
struct Array {
size_t size;
int content[];
};
The idea is that you would then allocate it so:
void foo(size_t x) {
Array* array = malloc(sizeof(size_t) + x * sizeof(int));
array->size = x;
for (size_t i = 0; i != x; ++i) {
array->content[i] = 0;
}
}
You might also use it statically (gcc extension):
Array a = { 3, { 1, 2, 3 } };
This is also known as tail-padded structures (this term predates the publication of the C99 Standard) or struct hack (thanks to Joe Wreschnig for pointing it out).
However this syntax was standardized (and the effects guaranteed) only lately in C99. Before a constant size was necessary.
1 was the portable way to go, though it was rather strange.
0 was better at indicating intent, but not legal as far as the Standard was concerned and supported as an extension by some compilers (including gcc).
The tail padding practice, however, relies on the fact that storage is available (careful malloc) so is not suited to stack usage in general.
In Standard C and C++, zero-size array is not allowed..
If you're using GCC, compile it with -pedantic option. It will give warning, saying:
zero.c:3:6: warning: ISO C forbids zero-size array 'a' [-pedantic]
In case of C++, it gives similar warning.
It's totally illegal, and always has been, but a lot of compilers
neglect to signal the error. I'm not sure why you want to do this.
The one use I know of is to trigger a compile time error from a boolean:
char someCondition[ condition ];
If condition is a false, then I get a compile time error. Because
compilers do allow this, however, I've taken to using:
char someCondition[ 2 * condition - 1 ];
This gives a size of either 1 or -1, and I've never found a compiler
which would accept a size of -1.
Another use of zero-length arrays is for making variable-length object (pre-C99). Zero-length arrays are different from flexible arrays which have [] without 0.
Quoted from gcc doc:
Zero-length arrays are allowed in GNU C. They are very useful as the last element of a structure that is really a header for a variable-length object:
struct line {
int length;
char contents[0];
};
struct line *thisline = (struct line *)
malloc (sizeof (struct line) + this_length);
thisline->length = this_length;
In ISO C99, you would use a flexible array member, which is slightly different in syntax and semantics:
Flexible array members are written as contents[] without the 0.
Flexible array members have incomplete type, and so the sizeof operator may not be applied.
A real-world example is zero-length arrays of struct kdbus_item in kdbus.h (a Linux kernel module).
I'll add that there is a whole page of the online documentation of gcc on this argument.
Some quotes:
Zero-length arrays are allowed in GNU C.
In ISO C90, you would have to give contents a length of 1
and
GCC versions before 3.0 allowed zero-length arrays to be statically initialized, as if they were flexible arrays. In addition to those cases that were useful, it also allowed initializations in situations that would corrupt later data
so you could
int arr[0] = { 1 };
and boom :-)
Zero-size array declarations within structs would be useful if they were allowed, and if the semantics were such that (1) they would force alignment but otherwise not allocate any space, and (2) indexing the array would be considered defined behavior in the case where the resulting pointer would be within the same block of memory as the struct. Such behavior was never permitted by any C standard, but some older compilers allowed it before it became standard for compilers to allow incomplete array declarations with empty brackets.
The struct hack, as commonly implemented using an array of size 1, is dodgy and I don't think there's any requirement that compilers refrain from breaking it. For example, I would expect that if a compiler sees int a[1], it would be within its rights to regard a[i] as a[0]. If someone tries to work around the alignment issues of the struct hack via something like
typedef struct {
uint32_t size;
uint8_t data[4]; // Use four, to avoid having padding throw off the size of the struct
}
a compiler might get clever and assume the array size really is four:
; As written
foo = myStruct->data[i];
; As interpreted (assuming little-endian hardware)
foo = ((*(uint32_t*)myStruct->data) >> (i << 3)) & 0xFF;
Such an optimization might be reasonable, especially if myStruct->data could be loaded into a register in the same operation as myStruct->size. I know nothing in the standard that would forbid such optimization, though of course it would break any code which might expect to access stuff beyond the fourth element.
Definitely you can't have zero sized arrays by standard, but actually every most popular compiler gives you to do that. So I will try to explain why it can be bad
#include <cstdio>
int main() {
struct A {
A() {
printf("A()\n");
}
~A() {
printf("~A()\n");
}
int empty[0];
};
A vals[3];
}
I am like a human would expect such output:
A()
A()
A()
~A()
~A()
~A()
Clang prints this:
A()
~A()
GCC prints this:
A()
A()
A()
It is totally strange, so it is a good reason not to use empty arrays in C++ if you can.
Also there is extension in GNU C, which gives you to create zero length array in C, but as I understand it right, there should be at least one member in structure prior, or you will get very strange examples as above if you use C++.

Allowing struct field to overflow to the next field

Consider the following simple example:
struct __attribute__ ((__packed__)) {
int code[1];
int place_holder[100];
} s;
void test(int n)
{
int i;
for (i = 0; i < n; i++) {
s.code[i] = 1;
}
}
The for-loop is writing to the field code, which is of size 1. The next field after code is place_holder.
I would expect that in case of n > 1, the write to code array would overflow and 1 would be written to place_holder.
However, when compiling with -O2 (on gcc 4.9.4 but probably on other versions as well) something interesting happens.
The compiler identifies that the code might overflow array code, and limits loop unrolling to 1 iteration.
It's easy to see that when compiling with -fdump-tree-all and looking at the last tree pass ("t.optimized"):
;; Function test (test, funcdef_no=0, decl_uid=1366, symbol_order=1)
Removing basic block 5
test (int n)
{
<bb 2>:
# DEBUG i => 0
# DEBUG i => 0
if (n_4(D) > 0)
goto <bb 3>;
else
goto <bb 4>;
<bb 3>:
s.code[0] = 1;
# DEBUG i => 1
# DEBUG i => 1
<bb 4>:
return;
}
So in this case the compiler completely unrolled the loop to a single iteration.
My questions are:
From C specification viewpoint, is overflowing (deliberately) from one struct member to the next is illegal or undefined behavior?
Let's assume I'm aware of the struct layout in memory and know what I'm doing when deliberately overflowing the code array.
Is there a way to prevent gcc from unrolling the loop in such case? I know I can completely prevent loop unrolling, however I'm still interested in loop unrolling on other cases. I also suspect that the analysis the compiler is doing might affect passes other than loop unrolling.
gcc is assuming I'm not going to overflow when accessing my array, so what I'm really looking for is way to tell the compiler not to take this assumption (by providing some compiler option).
I'm aware it's a bad practice to write such code that overflows from one field to another, and I'm not intending to write such code.
I'm also aware of the practice to put an array (possibly zero sized) as the last struct field to allow it to overflow, this is well supported by compilers, while in this case the array code is not the last field.
So this is not a question of "how to fix the code", but rather a question of understanding the compiler assumptions and affecting them.
These questions came up when I observed existing code that was already written in such way, and debugged it to find out why it's not behaving as the original developer expected it to behave.
The risk is that there are other places in the code where such problem exists. Static analysis tools can help to find out, but I would also like to know if there's a way to make the compiler tolerate such code and still generate the result we would expect.
Update
I got clear answer to question (1) above, but not for question (2).
Can gcc allow this as an extension, by some compile options?
Is there a way to at least get a warning when gcc identifies it? (and it clearly identifies it, by optimizing things out).
That's important in order to identify such cases in a large existing code base.
From C specification viewpoint, is overflowing (deliberately) from one struct member to the next is illegal or undefined behavior?
It is undefined behavior. The arr[i] operator is syntactic sugar around *(arr + i). So array access boils down to the binary + operator for pointer arithmetic, C17 6.5.6 additive operators, from §7 and §8:
For the purposes of these operators, a pointer to an object that is not an element of an
array behaves the same as a pointer to the first element of an array of length one with the
type of the object as its element type.
When an expression that has integer type is added to or subtracted from a pointer, the
result has the type of the pointer operand. /--/
If both the pointer
operand and the result point to elements of the same array object, or one past the last
element of the array object, the evaluation shall not produce an overflow; otherwise, the
behavior is undefined.
If the result points one past the last element of the array object, it
shall not be used as the operand of a unary * operator that is evaluated.
As you noticed, optimizing compilers might exploit these rules to produce faster code.
Is there a way to prevent gcc from unrolling the loop in such case?
There is a a special exception rule that can be used, C17 6.3.2.3/7:
When a pointer to an object is converted to a pointer to a character type,
the result points to the lowest addressed byte of the object. Successive increments of the
result, up to the size of the object, yield pointers to the remaining bytes of the object.
Also, strict aliasing does not apply to character types, because of another special rule in C17 6.5 §7
An object shall have its stored value accessed only by an lvalue expression that has one of
the following types: ... a character type.
These two special rules co-exist in harmony. So assuming we don't mess up alignment etc during the pointer conversion, this means that we are allowed to do this:
unsigned char* i;
for(i = (unsigned char*)&mystruct; i < (unsigned char*)(&mystruct + 1); i++)
{
do_something(*i);
}
This may however read padding bytes etc so it's "implementation-defined". But in theory you can access the struct byte per byte, and as long as the struct offsets are calculated on byte-per-byte basis, you can iterate across multiple members of the struct (or any other object) in this manner.
As far as I can tell, this very questionable-looking code should be well-defined:
#include <stdint.h>
#include <string.h>
#include <stdio.h>
struct __attribute__ ((__packed__)) {
int code[1];
int place_holder[100];
} s;
void test(int val, int n)
{
for (unsigned char* i = (unsigned char*)&s;
i < (unsigned char*)&s + n*sizeof(int);
i += _Alignof(int))
{
if((uintptr_t)i % _Alignof(int) == 0) // not really necessary, just defensive prog.
{
memcpy(i, &val, sizeof(int));
printf("Writing %d to address %p\n", val, (void*)i);
}
}
}
int main (void)
{
test(42, 3);
printf("%d %d %d\n", s.code[0], s.place_holder[0], s.place_holder[1]);
}
This works fine on gcc and clang (x86). How efficient it is, well that's another story. Please don't write code like this, though.
From C specification viewpoint, is overflowing (deliberately) from one struct member to the next is illegal or undefined behavior?
It's undefined behavior to access an array out-of-bounds. From C11 J.2:
The behavior is undefined in the following circumstances:
[...]
An array subscript is out of range [...]
Is there a way to prevent gcc from unrolling the loop in such case?
Alias code with a volatile pointer. But even using an intermediary pointer seems to work. godbolt link
Just _Static_assert the layout and do the pointer arithmetic in (char*), then cast to (int*) and
do the access. No further tricks such as memcpy/_Alignof are required because ints are unpadded
and you are accessing ints where there really are ints.
This alone makes gcc unroll the loop.
Why character-pointer based (char*, signed char*, unsigned char*) pointer arithmetic is required is because
http://port70.net/~nsz/c/c11/n1570.html#J.2 (non-normatively, as it is just an appendix, but gcc seems to follow it) makes out-of bounds accesses UB,
but http://port70.net/~nsz/c/c99/n1256.html#6.2.6.1p4 and http://port70.net/~nsz/c/c99/n1256.html#6.5p6 still allow inspecting any object via character pointers (more discussion on this at Is accessing an element of a multidimensional array out of bounds undefined behavior?).
Alternatively you could do the pointer arithmetic via uintptr_t (then it will be implementation defined)
but gcc optimizes those worse in certain cases (gcc doesn't fold (uintptr_t)p < (uintptr_t)(p+10) into true, but it does so for (char*)p < (char*)(p+10). This could be considered a missed optimization).
struct __attribute__ ((__packed__)) s {
int code[1];
int place_holder[100];
} s;
void test_s(int n) //original
{
int i;
for (i = 0; i < n; i++) {
s.code[i] = 1;
}
}
#include <stddef.h> //offsetof
void test_s2(int n) //unrolls the loop
{
_Static_assert(offsetof(struct s,code)+sizeof(int)==offsetof(struct s,place_holder),"");
//^will practically hold even without __attribute__((__packed__))
int i; for (i = 0; i < n; i++)
*(int*)((char*)&s.code + (size_t)i*sizeof(s.code[0])) = 1;
}
/////////////
//same code as test_s2
struct r {
int code101[101];
} r;
void test_r(int n)
{
int i;
for (i = 0; i < n; i++) {
r.code101[i] = 1;
}
}
1. Question:
"From C specification viewpoint, is overflowing (deliberately) from one struct member to the next illegal or undefined behavior?"
It is undefined behavior. The C standard states (emphasize mine):
"A postfix expression followed by an expression in square brackets [] is a subscripted designation of an element of an array object. The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). Because of the conversion rules that apply to the binary + operator, if E1 is an array object (equivalently, a pointer to the initial element of an array object) and E2 is an integer, E1[E2] designates the E2-th element of E1 (counting from zero)."
Source: ISO/IEC 9899:2018 (C18), §6.5.2.1/2
"When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression P points to the i-th element of an array object, the expressions (P) + N (equivalently, N + (P)) and (P) - N (where N has the value n) point to, respectively, the i+n-th and i−n-th elements of the array object, provided they exist. Moreover, if the expression P points to the last element of an array object, the expression (P) + 1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q) - 1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated."
Source: ISO/IEC 9899:2018 (C18), §6.5.6/8
Also non-normative Annex J states with regard to paragraph §6.5.6 in the normative standard:
J.2 Undefined behavior
1 The behavior is undefined in the following circumstances:
....
An array subscript is out of range, even if an object is apparently accessible with the given subscript (as in the lvalue expression a[1][7] given the declaration int a[4][5]) (6.5.6).
2. Question (plus update):
"Is there a way to prevent gcc from unrolling the loop in such case?"
"Can gcc allow this as an extension, by some compile options?"
"Is there a way to at least get a warning when gcc identifies it? That's important in order to identify such cases in a large existing code base."
You could try to place an empty assembly code function like asm(""); into the loop, as shown in this answer by Denilson Sá Maia, f.e.:
for (i = 0; i < n; i++) {
s.code[i] = 1;
asm("");
}
or #pragma's around the test function, as shown here, f.e.:
#pragma GCC push_options
#pragma GCC optimize ("O0")
void test(int n)
{
int i;
for (i = 0; i < n; i++) {
s.code[i] = 1;
}
}
#pragma GCC pop_options
to prevent the optimization for that specific program part in general and with that the loop unrolling.
Related:
How to prevent gcc optimizing some statements in C?
How to prevent GCC from optimizing out a busy wait loop?
Is there a way to tell GCC not to optimise a particular piece of code?
It is not preventing the loop unrolling, but you can use AddressSanitizer, which also got LeakSanitizer integrated, and is built into GCC since version 4.8 to detect when the loop unrolling doesn't work/you access non-affiliated memory.
More information about this, you can find here.
Edit: As you said your target implementation is MIPS, you can still use Valgrind to detect memory leaks.
In the language Dennis Ritchie described in 1974, the behavior of struct member access operators and pointer arithmetic were defined in terms of machine addresses, and except for the use of object size to scale pointer arithmetic, were agnostic as to the types of objects the addresses represented. The C Standard allows implementations to behave in that fashion when their customers would find it useful, but would also allow them to do other things, such as trapping out-of-bounds array accesses, if customers would find those other behaviors more useful.
Although later C dialects effectively behaved as though struct member names are prefixed by the struct name, so as to give each structure type its own member namespace, in most other respects compilers can be configured, by disabling optimizations if nothing else, to behave in a fashion consistent with Ritchie's 1974 language. Unfortunately, there's no way to distinguish implementations that will consistently behave in that fashion from those that won't; some compilers, especially those which go back to a time before the Standard, don't explicitly document that they support the 1974 behaviors because they were written at a time when compilers were generally expected to do so unless they documented otherwise.

Evaluation on the right side of an assignment

This int c = (a==b) is exactly what I'd like to say in my C program, compiling with GCC. I can do it, obviously (it works just fine), but I don't know whether it may cause undefined behavior. My program will not be compiled with some other compiler or in other architectures. Is this legal ANSI C? Thanks.
It's completely legal. if a is equal to b, then c will be 1. else, it will be 0.
int c = (a == b);
this is perfectly legal. Initialization is part of the C standard (C99 §6.7.8), the right hand side can just be any assignment-expression, including a == b (of course, assuming a and b are defined and have comparable type).
It is perfectly valid if c is declared at block scope.
When declared at file scope it is not valid because the initializer has to be a constant expression.
a == b is an expression and in that sense is not different that another expression like a + b or a & b.
Well, it depends on what the types of a and b are. If they are types that support equality check, then yes, it's perfectly legal.

How to check if a parameter is an integral constant expression in a C preprocessor macro?

I'm currently cleaning up an existing C-library to publish it shamelessly.
A preprocessor macro NPOT is used to calculate the next greater power of two for a given integral constant expression at compile time. The macro is normally used in direct initialisations. For all other cases (e.g. using variable parameters), there is an inline function with the same function.
But if the user passes a variable, the algorithm expands to a huge piece of machine code. My question is:
What may I do to prevent a user from passing anything but an integral constant expression to my macro?
#define NPOT(x) complex_algorithm(x)
const int c=10;
int main(void) {
int i=5;
foo = NPOT(5); // works, and does everything it should
foo = NPOT(c); // works also, but blows up the code extremely
foo = NPOT(i); // blows up the code also
}
What I already tried:
Define the macro to #define NPOT(x) complex_algorithm(x ## u). It still works and throws a - even if hardly helpful - compiler error for variable parameters. Unless there is no variable like iu... Dirty, dangerous, don't want it.
Documentation, didn't work for most users.
You can use any expression that needs a constant integral expression and that will then be optimized out.
#define NPOT(X) \
(1 \
? complex_algorithm(X) \
: sizeof(struct { int needs_constant[1 ? 1 : (X)]; }) \
)
eventually you should cast the result of the sizeof to the appropriate integer type, so the return expression is of a type that you'd expect.
I am using an untagged struct here to
have a type so really no temporary is produced
have a unique type such that the expression can be repeated anywhere in the code without causing conflicts
trigger the use of a VLA, which is not allowed inside a struct as of C99:
A member of a structure or union may have any object type other than a
variably modified type.
I am using the ternary ?: with 1 as the selecting expression to ensure that the : is always evaluated for its type, but never evaluated as an expression.
Edit: It seems that gcc accepts VLA inside struct as an extension and doesn't even warn about it, even when I explicitly say -std=c99. This is really a bad idea of them.
For such a weird compiler :) you could use sizeof((int[X]){ 0 }), instead. This is "as forbidden" as the above version, but additionally even gcc complains about it.
#define INTEGRAL_CONST_EXPR(x) ((void) sizeof (struct {int a:(x);}), (x))
This will give a compile error if x is not a integral constant expression.
my_function(INTEGRAL_CONST_EXPR(1 + 2 + 3)); // OK
my_function(INTEGRAL_CONST_EXPR(1.0 + 2 + 3)); // compile error
Note that this solution does not work for initializing a static variable:
static int a = INTEGRAL_CONST_EXPR(2 + 3);
will trigger a compile error because of an expression with , is not a constant expression.
As #JensGustedt put in the comment, an integral constant expression resolving to a negative integer number cannot be used in this solution as bit-field width cannot be negative.

C standard addressing simplification inconsistency

Section §6.5.3.2 "Address and indirection operators" ¶3 says (relevant section only):
The unary & operator returns the address of its operand. ...
If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue. Similarly, if the operand is the result of a [] operator, neither the & operator nor the unary * that is implied by the [] is evaluated and the result is as if the & operator were removed and the [] operator were changed to a + operator. ...
This means that this:
#define NUM 10
int tmp[NUM];
int *i = tmp;
printf("%ti\n", (ptrdiff_t) (&*i - i) );
printf("%ti\n", (ptrdiff_t) (&i[NUM] - i) );
Should be perfectly legal, printing 0 and the NUM (10). The standard seems very clear that both of those cases are required to be optimized.
However, it doesn't seem to require the following to be optimized:
struct { int a; short b; } tmp, *s = tmp;
printf("%ti\n", (ptrdiff_t) (&s->b - s) );
This seems awfully inconsistent. I can see no reason that the above code shouldn't print the sizeof(int) plus (unlikely) padding (possibly 4).
Simplifying a &-> expression is going to be the same conceptually (IMHO) as &[], a simple address-plus-offset. It's even an offset that's going to be determinable at compile time, rather than potentially runtime with the [] operator.
Is there anything in the rationale about why this is so seemingly inconsistent?
In your example, &i[10] is actually not legal: it becomes i + 10, which becomes NULL + 10, and you can't perform arithmetic on a null pointer. (6.5.6/8 lists the conditions under which pointer arithmetic can be performed)
Anyway, this rule was added in C99; it was not present in C89. My understanding is that it was added in large part to make code like the following well-defined:
int* begin, * end;
int v[10];
begin = &v[0];
end = &v[10];
That last line is technically invalid in C89 (and in C++) but is allowed in C99 because of this rule. It was a relatively minor change that made a commonly used construct well-defined.
Because you can't perform arithmetic on a null pointer, your example (&s->b) would be invalid anyway.
As for why there is this "inconsistency," I can only guess. It's likely that no one thought to make it consistent or no one saw a compelling use case for this. It's possible that this was considered and ultimately rejected. There are no remarks about the &* reduction in the Rationale. You might be able to find some definitive information in the WG14 papers, but unfortunately they seem to be quite poorly organized, so trawling through them may be tedious.
I think that the rule hasn't been added for optimization purpose (what does it bring that the as-if rule doesn't?) but to allow &t[sizeof(t)/sizeof(*t)] and &*(t+sizeof(t)/sizeof(*t)) which would be undefined behaviour without it (writing such things directly may seem silly, but add a layer or two of macros and it can make sense). I don't see a case where special casing &p->m would bring such benefit. Note that as James pointed out, &p[10] with p a null pointer is still undefined behaviour; &p->m with p a null pointer would similarly have stayed invalid (and I must admit that I don't see any use when p is the null pointer).
I believe that the compiler can choose to pack in different ways, possibly adding padding between members of a struct to increase memory access speed. This means that you can't for sure say that b will always be an offset of 4 away. The single value does not have the same problem.
Also, the compiler may not know the layout of a struct in memory during the optimization phase, thus preventing any sort of optimization concerning struct member accesses and subsequent pointer casts.
edit:
I have another theory...
many times the compiler will optimize the abstract syntax tree just after lexical analysis and parsing. This means it will find things like operators that cancel out and expressions that evaluate to a constant and reduce those sections of the tree to one node. This also means that the information about structs is not available. later optimization passes that occur after some code generation may be able to take this into account because they have additional information, but for things like trimming the AST, that information is not yet there.

Resources