为什么在C中展平多维数组是非法的?

时间:2014-03-03 09:54:51

标签: c

我的书(Kenneth Reek的C指针)说,以下是非法虽然它可以正常工作。

  int arr[5][5];
  int *p=&arr[2][2];
  p=p+3; // As array is stored in row major form I think this 
         //should make p point to arr[3][0]

这本书说将一行留到下一行是违法的。但我无法理解为什么。

3 个答案:

答案 0 :(得分:6)

这本书说它是非法的原因是因为指针算法只能保证在指向同一数组中的元素的指针上,或者在结束时使用指针算法。

arr是一个包含5个元素的数组,其中每个元素是一个包含5个整数的数组。因此,理论上,如果你想在arr[i]中有指向数组元素的指针,你只能做指针运算,产生范围&arr[i][0..4]arr[i]+5 的指针,保持{{1常量

例如,假设i是一个5维整数。然后,指针arr只能指向p&arr[0..4]中的每一个(结尾一个)。这也是多维数组所发生的情况。

使用arr+5,您只能进行指针运算,以便始终拥有int arr[5][5];&arr[i][0..4]范围内的指针 - 这就是规则所说的内容。它可能令人困惑,因为这些是数组内部的数组,但无论如何规则都是相同的。从概念上讲,arr[i]+5arr[0]是不同的数组,即使您知道它们在内存中是连续的,在arr[1]arr[0]的元素之间进行指针运算也是违法的。请记住,概念上,arr[1]中的每个元素都是不同的数组。

但是,在您的示例中,arr[i]会将一个指向p+3的结尾,所以它看起来像是有效的。这是一个不好的选择,因为它会使arr[2][2]精确指向一个结尾,使其仍然有效。如果作者选择了p,那么这个例子就是正确的。

无论哪种方式,我都没有遇到任何使用类似方法在C中展平多维数组的问题。

另见这个问题,它还有其他有用的信息:One-dimensional access to a multidimensional array: well-defined C?

答案 1 :(得分:4)

我在这上面写了一段时间,我会尽力解释他想到来自哪里,虽然没有读过这本书,但这将是最好的推测。

首先,从技术上讲,你提议(或他提议)的增量并非违法; 解除引用它是。该标准允许您将指针前进到数组序列的最后一个元素,从中获取它以进行评估,但不能用于取消引用。将其更改为p = p + 4两者都是非法的。

除此之外,数组的线性足迹不能承受,ar[2]有一个类型,它是int[5]。如果你不相信,请考虑以下几点,所有这些都是正确输入的:

int ar[5][5];
int (*sub)[5] = ar+2;   // sub points to 3rd row
int *col = *sub + 2;    // col points to 3rd column of third row.
int *p = col + 3;       // p points to 5th colum of third row.

是否落在ar[3][0]上是不相关的你超过了参与指针数学的维度的声明幅度。结果不能合法地被解除引用,并且它大于3偏移,甚至不能进行合法评估。

请记住,正在寻址的数组是ar[2];不仅仅是ar,而且说 - 同样被宣称为size = 5。它与其他两个同类阵列的支持与目前正在进行的寻址无关。我认为Christoph's answer提出的问题应该是重复解决的问题。特别是,对C99§6.5.6,p8 的引用虽然冗长,但却出现在下面:

  

添加或减去具有整数类型的表达式时   从指针开始,结果具有指针操作数的类型。如果   指针操作数指向数组对象的元素和数组   足够大,结果指向一个偏离的元素   原始元素使得下标的差异   结果和原始数组元素等于整数表达式。   换句话说,如果表达式P指向一个的第i个元素   数组对象,表达式(P)+ N(等效地,N +(P))和(P)-N   (其中N具有值n)分别指向第i + n和第i   数组对象的第i个元素,如果它们存在。而且,如果   表达式P指向数组对象的最后一个元素,即   表达式(P)+1指向数组对象的最后一个元素,   如果表达式Q指向一个数组的最后一个元素   对象,表达式(Q)-1指向数组的最后一个元素   宾语。如果指针操作数和结果都指向元素   相同的数组对象,或一个超过数组的最后一个元素   对象,评估不得产生溢出; 否则,   行为未定义。如果结果指向一个经过最后一个元素   对于数组对象,它不应该用作一元*的操作数   被评估的运算符。

对于垃圾邮件感到抱歉,但我认为粗体突出显示与您的问题相关。通过按原样进行寻址,您将离开正在寻址的阵列,并因此走进UB。简而言之,它(通常)有效,但不合法。

答案 2 :(得分:2)

是。它在C中是非法的。事实上,这样做是为了让你的编译器。 p指向元素arr[2][2](并指向int类型的指针),即第三行的第3个元素。语句p=p+3;会将指针p增加到arr[2][5],这相当于arr[3][0]
但是,只要在某些架构上将内存分配为22 n )的强大功能,这就会失败。现在,在这种情况下,内存分配将向上舍入到2 n ,即,在您的情况下,每行将向上舍入到64个字节。 /> 查看一个测试程序,其中分配的内存是5个10个整数的分配。在某些机器上,内存分配是16个字节的倍数,因此请求的40个字节被舍入为每个分配48个字节:

#include <stdio.h>
#include <stdlib.h>

extern void print_numbers(int *num_ptr, int n, int m);
extern void print_numbers2(int **nums, int n, int m);

int main(void)
{
    int **nums;
    int n = 5;
    int m = 10;
    int count = 0;

    // Allocate rows
    nums = (int **)malloc(n * sizeof(int *));

    // Allocate columns for each row
    for (int i = 0; i < n; i++)
    {
        nums[i] = (int *)malloc(m * sizeof(int));
        printf("%2d: %p\n", i, (void *)nums[i]);
    }

    // Populate table
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            nums[i][j] = ++count;

    // Print table
    puts("print_numbers:");
    print_numbers(&nums[0][0], n, m);
    puts("print_numbers2:");
    print_numbers2(nums, n, m);
    return 0;
}

void print_numbers(int *nums_ptr, int n, int m)
{
    int (*nums)[m] = (int (*)[m])nums_ptr;

    for (int i = 0; i < n; i++)
    {
        printf("%2d: %p\n", i, (void *)nums[i]);
        for (int j = 0; j < m; j++)
        {
            printf("%3d", nums[i][j]);
        }
        printf("\n");
    }
}


void print_numbers2(int **nums, int n, int m)
{
    for (int i = 0; i < n; i++)
    {
        printf("%2d: %p\n", i, (void *)nums[i]);
        for (int j = 0; j < m; j++)
            printf("%3d", nums[i][j]);
        printf("\n");
    }
}

Mac OS X 10.8.5上的示例输出; GCC 4.8.1:

 0: 0x7f83a0403a50
 1: 0x7f83a0403a80
 2: 0x7f83a0403ab0
 3: 0x7f83a0403ae0
 4: 0x7f83a0403b10
print_numbers:
 0: 0x7f83a0403a50
  1  2  3  4  5  6  7  8  9 10
 1: 0x7f83a0403a78
  0  0 11 12 13 14 15 16 17 18
 2: 0x7f83a0403aa0
 19 20  0  0 21 22 23 24 25 26
 3: 0x7f83a0403ac8
 27 28 29 30  0  0 31 32 33 34
 4: 0x7f83a0403af0
 35 36 37 38 39 40  0  0 41 42
print_numbers2:
 0: 0x7f83a0403a50
  1  2  3  4  5  6  7  8  9 10
 1: 0x7f83a0403a80
 11 12 13 14 15 16 17 18 19 20
 2: 0x7f83a0403ab0
 21 22 23 24 25 26 27 28 29 30
 3: 0x7f83a0403ae0
 31 32 33 34 35 36 37 38 39 40
 4: 0x7f83a0403b10
 41 42 43 44 45 46 47 48 49 50  

Win7上的示例输出; GCC 4.8.1:

enter image description here