对多维数组的一维访问:它是明确定义的行为吗?

Oli*_*rth 16 c standards pointers multidimensional-array language-lawyer

我想我们都同意,通过以一维方式解引用(可能是偏移的)指向其第一个元素的指针来访问真正的多维数组被认为是惯用的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);
Run Code Online (Sandbox Code Playgroud)

然而,我的语言律师需要说服这实际上是明确定义的C!特别是:

  1. 是否标准保证编译器不会把填充例如,在中间mtx[0][2]mtx[1][0]

  2. 通常,索引关闭数组的末尾(除了结尾之外)是未定义的(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];
    
    Run Code Online (Sandbox Code Playgroud)

    因此,根据相同的规则,人们会期望以下内容未定义:

    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?
    
    Run Code Online (Sandbox Code Playgroud)

    那为什么要定义呢?

    int mtx[5][3];
    int *p = &(&mtx[0][0])[7];
    
    Run Code Online (Sandbox Code Playgroud)

那么C标准的哪一部分明确允许这个?(为了讨论,我们假设是.)

编辑

请注意,我毫不怀疑这在所有编译器中都能正常工作.我要查询的是标准是否明确允许这样做.

Chr*_*oph 13

所有数组(包括多维数组)都是无填充的.即使从未明确提及过,也可以从sizeof规则中推断出来.

现在,数组预订是指针算术的一个特例,C99第6.5.6节§8明确规定只有当指针操作数和结果指针位于同一个数组(或者一个元素过去)时才会定义行为,这使得可能的边界检查C语言的实现.

这意味着您的示例实际上是未定义的行为.但是,由于大多数C实现不检查边界,它将按预期工作 - 大多数编译器处理未定义的指针表达式,如

mtx[0] + 5 
Run Code Online (Sandbox Code Playgroud)

与明确定义的同行相同

(int *)((char *)mtx + 5 * sizeof (int))
Run Code Online (Sandbox Code Playgroud)

这是明确定义的,因为任何对象(包括整个二维数组)总是可以被视为一维的数组类型char.


关于6.5.6节的措辞的进一步冥想,将越界访问划分为看似明确定义的子表达式,如

(mtx[0] + 3) + 2
Run Code Online (Sandbox Code Playgroud)

推理mtx[0] + 3是一个指向一个元素的指针mtx[0](使第一个加法定义明确)以及指向第一个元素的指针mtx[1](使第二个加法定义明确)是不正确的:

尽管mtx[0] + 3并且mtx[1] + 0保证比较相等(参见6.5.9节,§6),但它们在语义上是不同的.例如,前者不能被解除引用,因此不会指向的元素mtx[1].


R..*_*R.. 9

您想要进行访问的唯一障碍是类型对象int [5][3]并且int [15]不允许彼此别名.因此,如果编译器知道类型的指针指向前者的int *一个int [3]数组,它可能会施加数组边界限制,从而阻止访问该int [3]数组之外的任何内容.

您可以通过将所有内容放在包含int [5][3]数组和int [15]数组的联合内部来解决此问题,但是我真的不清楚联合黑客是否会将人们用于类型惩罚实际上是明确定义的.这种情况可能稍微有点问题,因为你不会打字单个单元格,只有数组逻辑,但我仍然不确定.

应该注意的一个特殊情况是:如果你的类型是unsigned char(或任何char类型),那么将多维数组作为一维数组访问将是非常明确的.这是因为unsigned char与标准重叠的一维数组被标准明确定义为对象的"表示",并且本质上允许对其进行别名.

  • @Pascal:C99 允许通过联合进行类型双关 - 这在脚注 82(第 73 页)中明确提到,它是随 TC3 添加的 (2认同)