C99是否保证阵列是连续的?

kri*_*iss 17 c arrays pointers c99

在另一个问题的热门评论主题之后,我开始讨论C99标准中有关C数组的内容和内容.

基本上,当我定义一个2D数组时int a[5][5],标准C99是否保证它将是一个连续的整数块,我可以将它投射到(int *)a并确保我将拥有一个25个整数的有效一维数组.

正如我理解标准的那样,上面的属性隐含在sizeof定义和指针算术中,但其他人似乎不同意并且说铸造到(int*)上面的结构给出了一个未定义的行为(即使他们同意所有现有的实现实际分配连续的价值观).

更具体地说,如果我们认为一种实现可以检测数组检查所有维度的数组边界并在访问1D数组时返回某种错误,或者不能正确访问第1行以上的元素.这样的实施可以是标准的编制吗?在这种情况下,C99标准的哪些部分是相关的.

Sec*_*ure 18

我们应该从检查[5] [5]到底是什么开始.涉及的类型是:

  • INT
  • 整数[5]的整数
  • 数组的数组[5]

没有涉及整数[25]的整数.

正确的语义大小意味着整个数组是连续的.int的数组[5]必须具有5*sizeof(int),并且递归地应用,[5] [5]必须具有5*5*sizeof(int).没有额外填充的空间.

此外,当给予memset,memmove或memcpy sizeof时,整个数组必须正常工作.还必须能够使用(char*)迭代整个数组.所以一个有效的迭代是:

int  a[5][5], i, *pi;
char *pc;

pc = (char *)(&a[0][0]);
for (i = 0; i < 25; i++)
{
    pi = (int *)pc;
    DoSomething(pi);
    pc += sizeof(int);
}
Run Code Online (Sandbox Code Playgroud)

对(int*)执行相同操作将是未定义的行为,因为如上所述,没有涉及int的数组[25].在Christoph的回答中使用联合也应该是有效的.但还有另一点使这一点更加复杂,即等于运算符:

6.5.9.6两个指针比较相等,当且仅当两个都是空指针时,两者都是指向同一个对象的指针(包括指向一个对象的指针和一个开头的子对象)或函数,两者都指向一个超过最后一个元素的指针.相同的数组对象,或者一个是指向一个数组对象末尾的指针,另一个是指向不同数组对象的开头的指针,该数组对象恰好跟随地址空间中的第一个数组对象.91)

91)两个对象可能在内存中相邻,因为它们是较大数组的相邻元素或结构的相邻成员之间没有填充,或者因为实现选择放置它们,即使它们是不相关的.如果先前的无效指针操作(例如数组边界外的访问)产生了未定义的行为,则后续比较也会产生未定义的行为.

这意味着:

int a[5][5], *i1, *i2;

i1 = &a[0][0] + 5;
i2 = &a[1][0];
Run Code Online (Sandbox Code Playgroud)

i1与i2相等.但是当使用(int*)迭代数组时,它仍然是未定义的行为,因为它最初是从第一个子数组派生的.它没有神奇地转换为指向第二个子阵列的指针.

即使这样做

char *c = (char *)(&a[0][0]) + 5*sizeof(int);
int  *i3 = (int *)c;
Run Code Online (Sandbox Code Playgroud)

没有用.它比较等于i1和i2,但它不是从任何子阵列得出的; 它是指向单个int或int的数组[1]的指针.

我不认为这是标准中的错误.它是周围的其他方法:允许这将引入违反数组或任一类型的系统指针运算或两个规则的一个特例.它可能被认为是缺失的定义,但不是错误.

因此,即使[5] [5]的内存布局与[25]的布局相同,并且使用(char*)的相同循环也可用于迭代两者,允许实现吹如果一个被用作另一个.我不知道为什么它应该或知道任何实现,并且可能在标准中没有提到的任何事实直到现在才使其成为明确定义的行为.在那之前,我会认为它是未定义的并保持安全.


Chr*_*oph 11

我在原始讨论中添加了一些评论.

sizeof语义意味着int a[5][5]是连续的,而是通过增加一个指针等来访所有25点的整数int *p = *a是未定义的行为:指针算术只被定义,只要所有的指针invoved同一阵列(或一个过去的最后一个元素元素)内谎言,如例如&a[2][1]&a[3][1]不要(参见C99第6.5.6节).

原则上,你可以通过强制转换&a- 具有类型int (*)[5][5]- 来解决这个问题int (*)[25].根据6.3.2.3§7,这是合法的,因为它没有违反任何对齐要求.问题是通过这个新指针访问整数是违法的,因为它违反了6.5§7中的别名规则.您可以通过使用unionfor类型双关语来解决此问题(请参阅TC3中的脚注82):

int *p = ((union { int multi[5][5]; int flat[25]; } *)&a)->flat;
Run Code Online (Sandbox Code Playgroud)

据我所知,这是符合标准的C99.