我想我们都同意,通过以一维方式解引用(可能是偏移的)指向其第一个元素的指针来访问真正的多维数组被认为是惯用的C,例如:
void clearBottomRightElement(int *array, int M, int N)
{
array[M*N-1] = 0; // Pretend the array is one-dimensional
}
int mtx[5][3];
...
clearBottomRightElement(&mtx[0][0], 5, 3);
然而,我的语言律师需要说服这实际上是明确定义的C!特别是:
mtx[0][2]
和mtx[1][0]
?struct {
int row[3]; // The object in question is an int[3]
int other[10];
} foo;
int *p = &foo.row[7]; // ERROR: A crude attempt to get &foo.other[4];
因此,根据相同的规则,人们会期望以下内容未定义:
int mtx[5][3];
int (*row)[3] = &mtx[0]; // The object in question is still an int[3]
int *p = &(*row)[7]; // Why is this any better?
那为什么要定义呢?
int mtx[5][3];
int *p = &(&mtx[0][0])[7];
那么C标准的哪一部分明确允许这个? (为了讨论,让我们假设c99。)
编辑
请注意,我毫不怀疑这在所有编译器中都能正常工作。我要查询的是标准是否明确允许这样做。
您想要进行访问的唯一障碍是int [5][3]
和int [15]
类型的对象不允许彼此别名。因此,如果编译器知道类型为int *
的指针指向前者的int [3]
数组之一,则它可能会施加数组边界限制,从而阻止访问int [3]
数组之外的任何内容。
您可以通过将所有内容放在包含int [5][3]
数组和int [15]
数组的联合内部来解决这个问题,但是我真的不清楚联合黑客是否真的定义了用于类型惩罚的人。这种情况可能稍微有点问题,因为你不会打字单个单元格,只有数组逻辑,但我仍然不确定。
应该注意的一个特殊情况是:如果你的类型是unsigned char
(或任何char
类型),那么访问多维数组作为一维数组将是非常明确的。这是因为与标准重叠的unsigned char
的一维数组被标准明确定义为对象的“表示”,并且本质上允许对其进行别名。
所有数组(包括多维数组)都是无填充的。即使它从未被明确提及,也可以从sizeof
规则推断出来。
现在,数组预订是指针算术的一个特例,C99第6.5.6节§8明确规定只有当指针操作数和结果指针位于同一个数组(或者一个元素过去)时才会定义行为,这使得可能的边界检查C语言的实现。
这意味着您的示例实际上是未定义的行为。但是,由于大多数C实现不检查边界,它将按预期工作 - 大多数编译器处理未定义的指针表达式,如
mtx[0] + 5
与明确定义的同行相同
(int *)((char *)mtx + 5 * sizeof (int))
这是明确定义的,因为任何对象(包括整个二维数组)总是可以被视为char
类型的一维数组。
关于6.5.6节的措辞的进一步冥想,将越界访问划分为看似明确定义的子表达式,如
(mtx[0] + 3) + 2
推理mtx[0] + 3
是指向mtx[0]
末尾的一个元素的指针(使第一个加法定义明确)以及指向mtx[1]
的第一个元素的指针(使第二个加法定义明确)是不正确的:
尽管mtx[0] + 3
和mtx[1] + 0
保证比较相等(参见6.5.9节,§6),但它们在语义上是不同的。例如,前者不能被解除引用,因此不指向mtx[1]
的元素。
使用我的内部模型 - 我不确定它与标准模型完全相同,检查太痛苦,信息在各处传播 -
clearBottomRightElement
做的是有效的。int *p = &foo.row[7];
未定义int i = mtx[0][5];
未定义int *p = &row[7];
不编译(gcc同意我)int *p = &(&mtx[0][0])[7];
处于灰色区域(上次我查看了类似这样的细节,最后我考虑了无效的C90和有效的C99,这可能是这里的情况,或者我可能错过了什么)。我对C99 standard的理解是,不要求多维数组必须在内存中以连续的顺序排列。遵循我在标准中找到的唯一相关信息(每个维度都保证是连续的)。
如果你想使用x [COLS * r + c]访问,我建议你坚持使用单维数组。
连续的下标运算符指定多维数组对象的元素。如果E是n维阵列(n≥2),其尺寸为i×j×。 。 。 ×k,则E(用作除左值以外的值)被转换为指向尺寸为j×的(n-1)维数组的指针。 。 。 ×k。如果将unary *运算符显式地应用于此指针,或者作为预订的结果隐式应用,则结果是指向的(n-1)维数组,如果用作除左值之外的其他数据本身将被转换为指针。由此得出,数组以行主要顺序存储(最后一个下标变化最快)。
- 数组类型描述具有特定成员对象类型的连续分配的非空对象集,称为元素类型。 36)数组类型的特征在于它们的元素类型和数组中元素的数量。数组类型据说是从其元素类型派生的,如果它的元素类型是T,则数组类型有时称为''T'数组。从元素类型构造数组类型称为“数组类型派生”。