在6.5.6 加法运算符下:
语义
8 - [...] 如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果和原始下标的差异数组元素等于整数表达式。[...]如果结果指向数组对象的最后一个元素,则不应将其用作*
评估的一元运算符的操作数。
如果此时分配了内存malloc
:
7.22.3 内存管理功能
1 - [...] 如果分配成功,则返回的指针经过适当对齐,以便可以将其分配给指向具有基本对齐要求的任何类型对象的指针,然后用于访问此类对象或此类对象的数组在分配的空间中(直到空间被显式释放)。已分配对象的生命周期从分配一直延伸到解除分配。
然而,这并不支持在没有适当强制转换的情况下使用此类内存,因此MyStruct
如上定义的,只能使用对象的声明成员。这就是添加灵活数组成员(6.7.2.1:18) 的原因。
另请注意,附录J.2 未定义行为调用了数组访问:
1 - 在以下情况下行为未定义: [...]
- 将指针添加或减少到数组对象或仅超出数组对象和整数类型会产生不指向或仅超出数组对象的结果相同的数组对象。
— 对数组对象和整数类型的指针进行加法或减法运算会产生刚好超出数组对象的结果,并用作计算的一元运算符的操作数*
。
— 数组下标超出范围,即使对象显然可以使用给定的下标访问(如在a[1][7]
给定声明的左值表达式中) int
a[4][5])
。
因此,正如您所注意到的,这将是未定义的行为:
MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10);
foo->data[5] = 1;
但是,您可以执行以下操作:
MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10);
((int *) foo)[(offsetof(MyStruct, data) / sizeof(int)) + 5] = 1;
C++ 在这方面比较宽松;3.9.2 复合类型 [basic.compound]有:
3 - [...]如果一个类型的对象T
位于一个地址A
,那么一个类型的指针,cv T*
其值为地址的指针A
被称为指向该对象,而不管该值是如何获得的。
restrict
考虑到 C 对指针的更积极的优化机会,例如使用限定符,这是有道理的。