对multidimensional array的一维访问:定义明确的C?
我想我们都同意,通过以一维方式解引用其第一个元素(可能是偏移量)的指针来访问一个真正的multidimensional array被认为是惯用的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]
之间填充内容? -
通常,索引数组的末尾(除了末尾之外)是未定义的(C99,6.5.6 / 8)。 所以下面显然是不确定的:
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]
types的对象不允许相互混淆。 因此,如果编译器意识到types为int *
的指针指向前者的int [3]
数组,则可能会施加数组边界限制,阻止访问该int [3]
数组之外的任何内容。
你可能能够解决这个问题,把一切都包含在int [5][3]
数组和int [15]
数组中,但是我真的不清楚联盟是否使用types实际上是明确的。 这种情况可能会稍微less一些问题,因为你不会打字单个单元格,只有数组逻辑,但我还是不确定。
一个特殊情况应该注意:如果你的types是unsigned char
(或任何char
types),访问multidimensional array作为一维数组将被完美定义。 这是因为与它重叠的unsigned char
的一维数组被标准明确定义为对象的“表示”,并且固有地允许它被别名。
所有数组(包括multidimensional array)都是无填充的。 即使从未明确提及,也可以从规则的sizeof
推断出来。
现在,数组订阅是指针算术的一个特例,C99第6.5.6节和§8节清楚地表明,只有当指针操作数和结果指针位于同一个数组(或一个元素过去)时才定义行为,这使得C语言的边界检查实现是可能的。
这意味着你的例子实际上是未定义的行为。 然而,由于大多数C实现不检查边界,它将按预期工作 – 大多数编译器处理未定义的指针expression式
mtx[0] + 5
与明确定义的同类相同
(int *)((char *)mtx + 5 * sizeof (int))
这是很好定义的,因为任何对象(包括整个二维数组)总是可以被视为char
types的一维数组。
在进一步思考6.5.6节的措辞时,将界外界限分解成看似明确的子expression式
(mtx[0] + 3) + 2
推理mtx[0] + 3
是指向mtx[0]
末尾的一个元素的指针(使第一个加法定义良好)以及指向mtx[1]
的第一个元素的指针(使第二个元素另外定义明确)是不正确的:
即使mtx[0] + 3
和mtx[1] + 0
保证比较相等(见6.5.9,§6),它们在语义上是不同的。 例如,前者不能被解除引用,因此不指向mtx[1]
的元素。
-
可以肯定的是,在数组的元素之间没有填充。
-
有规定做地址计算比整个地址空间更小的尺寸。 这可以在8086的巨大模式下使用,这样如果编译器知道你不能跨越段边界,段部分就不会总是被更新。 (如果我使用的编译器是否有利于我,提醒我太久了)。
用我的内部模型 – 我不确定它和标准模型是否完全一样,检查这些信息太痛苦了,信息无处不在 –
-
你在
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标准的理解是, 不要求multidimensional array必须按照连续的顺序排列在内存中。 遵循我在标准中find的唯一相关信息(每个维度都保证是连续的)。
如果你想使用x [COLS * r + c]访问,我build议你坚持单维数组。
数组下标
连续的下标运算符指定multidimensional array对象的元素。 如果E是尺寸为i×j×的n维arrays(n≥2)。 。 。 ×k,则E(用作不是左值)被转换为指向维数为j×(n-1)维数组的指针。 。 。 ×k。 如果一元运算符被显式地应用于这个指针,或者由于下标而隐式地应用,那么结果就是指向(n-1)维的数组,它本身被转换成一个指针,如果用作非左值。 由此可见,数组以行优先顺序存储(最后的下标变化最快)。
数组types
– 一个数组types描述了一个连续分配的非空对象集合,它具有特定的成员对象types,称为元素types。 36)数组types的特征是它们的元素types和数组中元素的数量。 一个数组types被认为是从它的元素types中派生出来的,如果它的元素types是T,那么这个数组types有时被称为“T”的数组。 从元素types构造数组types被称为“数组types派生”。