Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/55.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
为什么在C中展平多维数组是非法的?_C - Fatal编程技术网

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

为什么在C中展平多维数组是非法的?,c,C,我的书(Kenneth Reek的《C上的指针》(Pointers on 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] 这本书说把一行留到下一行是违法的。但我不明白为什么。是的。这在C中是非法的。事

我的书(Kenneth Reek的《C上的指针》(Pointers on 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]

这本书说把一行留到下一行是违法的。但我不明白为什么。

是的。这在C中是非法的。事实上,这样做是在向编译器撒谎
p
指向元素
arr[2][2]
(并且是指向
int
类型的指针),即第三行的第三个元素。语句
p=p+3
将把指针
p
增加到
arr[2][5]
,这相当于
arr[3][0]

但是,每当在某些架构上将内存分配为
2
2
n
)的幂时,这将失败。现在,在这种情况下,内存分配将取整为
2
n
,也就是说,在您的情况下,每行将取整为
64
字节。
请参阅一个测试程序,其中分配的内存为10个整数的5次分配。在某些机器上,内存分配是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");
    }
}
Win7上的样本输出;通用条款4.8.1:


这本书之所以说它是非法的,是因为指针算法保证只对指向同一数组中元素的指针有效,或者只对超过末尾的元素有效

arr
是由5个元素组成的数组,其中每个元素都是由5个整数组成的数组。因此,从理论上讲,如果希望在
arr[i]
中有指向数组元素的指针,则只能执行指针算术,生成
&arr[i][0..4]
arr[i]+5
范围内的指针,保持
i
恒定

例如,假设arr是一个由5个整数组成的一维。然后一个指针
p
只能指向
&arr[0..4]
arr+5
(一个在末尾)。多维数组也是如此

int-arr[5][5]
,您只能执行指针算术,这样您的指针总是在
&arr[i][0..4]
arr[i]+5
范围内-这就是规则所说的。这可能会让人困惑,因为这些是数组中的数组,但无论发生什么,规则都是一样的。从概念上讲,
arr[0]
arr[1]
是不同的数组,即使您知道它们在内存中是连续的,在
arr[0]
arr[1]
的元素之间执行指针算术也是非法的。请记住,从概念上讲,
arr[i]
中的每个元素都是不同的数组

但是,在您的示例中,
p+3
将指向一个超出
arr[2][2]
末尾的位置,因此在我看来它仍然有效。这是一个糟糕的示例选择,因为它会使
p
精确地指向结束后的一个,使其仍然有效。如果作者选择了
p+4
,那么这个例子将是正确的

不管怎样,我从来没有遇到过使用类似方法在C中展平多维数组的问题


还可以看到这个问题,它还有其他有用的信息:

我对这个问题思考了一会儿,我会尽力解释我认为他是从哪里来的,尽管没有读这本书,充其量只是猜测

首先,从技术上讲,你提议(或他提议)的增量并不违法;取消对它的引用。该标准允许您将指针前进到数组序列的最后一个元素的前面,该数组序列的最后一个元素是用来求值的,但不用于解引用。将其更改为
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
,而且声明相同的大小为5。它与其他两个相同类型的数组相对应,这与当前正在进行的寻址无关。我相信,作为重复提出的问题本应被选为彻底解决的问题。特别是,对C99§6.5.6,p8的引用,虽然冗长,但如下所示:

将具有整数类型的表达式添加到或减去时 对于指针,结果具有指针操作数的类型。如果 指针操作数指向数组对象的元素,数组 如果足够大,则结果将指向与 原始元素,例如 结果和原始数组元素等于整数表达式。 换句话说,如果表达式P指向 数组对象,表达式(P)+N(等价地,N+(P))和(P)-N (其中N的值为N)分别指向i+N和 我−数组对象的第n个元素,,前提是它们存在。此外,如果 表达式P指向数组对象的最后一个元素 表达式(P)+1点超过数组对象的最后一个元素, 如果表达式Q指向数组最后一个元素的前面一个 对象,表达式(Q)-1指向数组的最后一个元素 对象如果指针操作数和结果都指向元素 同一数组对象的,或超过数组最后一个元素的 对象时,评估不应产生溢出否则 行为未定义。如果结果指向最后一个元素上方的一个 对于数组对象,不应将其用作
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.