Issue 1090: Pointer arithmetic involving flexible array members

Authors: Jay Ghiron
Date: 2026-06-30
Submitted against: C23
Status: Open
Cross-references: 0282

Consider the following:

struct S{char x,y[];};
struct S s;
struct S*p=&s+1;

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.

(C23 6.5.7 "Additive operators" paragraph 8.)

This wording assumes that for any object of type T which is not part of an array, there can exist an object whose type is T[1]. However, this assumption is broken with flexible array members:

A structure or union shall not contain a member with incomplete or function type (hence, a structure shall not contain an instance of itself, but may contain a pointer to an instance of itself), except that the last member of a structure with more than one named member may have incomplete array type; such a structure (and any union containing, possibly recursively, a member that is such a structure) shall not be a member of a structure or an element of an array.

(C23 6.7.3.2 "Structure and union specifiers" paragraph 3.)

Moreover, for a pointer one past the end of this hypothetical array to be formed it would need to know the true size of the flexible array member. GCC, Clang, and MSVC all allow the original example but obviously cannot do this so they just advance by the size of the structure. The consequence of this is that the resulting pointer can end up pointing into the flexible array of the object which it should be pointing one past.

No suggested correction is provided due to significant wording changes in N3517 and N3322. The following is a possible resolution to this issue without any specific wording:

Preserving pointer arithmetic with zero seems useful, at least to continue to keep a[0] valid as used in the idiom &a[0]. Additionally with N3322 being accepted, it would not make sense to forbid this when even null pointers allow for pointer arithmetic with zero. Allowing a+1 with the current behavior given by GCC, Clang, and MSVC does not seem useful however. For some context, consider the following:

#include<stddef.h>
#include<stdlib.h>
struct T{int i;short s;char c[];};
static_assert(sizeof(struct T)==8);
static_assert(offsetof(struct T,c)==6);
int main(){
struct T*q=malloc(/* ? */);
if(q){
*q->c=0;
free(q);
}
}

Suppose these static_assert declarations succeed, what is the minimum size to ensure that the behavior of this program is defined? Here would be three ways of calculating that size:

  1. offsetof(struct T,c)+1 which says the minimum size is seven.
  2. offsetof(struct T,c)+1<sizeof(struct T)?sizeof(struct T):offsetof(struct T,c)+1 which says the minimum size is eight.
  3. sizeof(struct T)+1 which says the minimum size is nine.

The third approach seems definitely safe, as the standard provides multiple examples using this style. The second approach appears fine too, though the standard provides the following example in C23 6.7.3.2 (sizeof(double)==8 is assumed to be true):

struct s { int n; double d[]; };
struct s *s1;
struct s *s2;
s1 = malloc(sizeof(struct s) + 10);
s2 = malloc(sizeof(struct s) + 6);
double *dp;
dp = &(s1->d[0]); // valid
*dp = 42; // valid
dp = &(s2->d[0]); // valid
*dp = 42; // undefined behavior

This is really multiple examples combined, since there are multiple examples which refer back to previous examples. It is stated that the last *dp=42 is undefined behavior, though it is not clear why it would be undefined if sizeof(struct s)-offsetof(struct s,d)>=2 is true. Though such an implementation would be uncommon, perhaps it was just forgotten as a possibility. The first approach seems to be the most useful and is also commonly used, but it would not work with q+1 using the semantics given by GCC, Clang, and MSVC. It would also not work with assignment to the whole struct T for example *q=*q, but assignments involving flexible array members do not actually copy the whole flexible array member so they are useless even though it is possible to do. And as mentioned in another example, some of the bytes in the destination flexible array member can even become indeterminate after copying. If the first approach is sufficient for the access to *q->c to be valid, it would create a strange edge case to allow q+1 but not for this specific scenario.