如何在C中使用指针操作多维数组?

6ljaweal  于 2023-08-03  发布在  其他
关注(0)|答案(3)|浏览(63)

假设我有以下数组:

int array[4][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};

字符串
我知道我可以两次解引用它来从数组中获得我想要的任何值。举例来说:

printf("%d", *(*array+8));
//Output will be 9


这让我假设C并没有真正区分一维和多维数组,因此维度为4x 4的多维数组将以与16个元素的一维数组相同的方式存储在内存中。所以我想,如果我想使用一个函数只对数组的后半部分做一些事情,我可以像这样将它传递给函数:

void foo(array+8)
{
    
}


但它不起作用,并导致分段错误。
那么,是否有一种方法可以只将数组的后半部分传递给函数(在前面的例子中,这将是{{9,10,11,12},{13,14,15,16}})?如果有这样的方法,我会很高兴听到它。
Thanks ahead:)

xytpbqjk

xytpbqjk1#

数组是C中真正的类型,而不是经常被不小心暗示的指针。在大多数表达式中,数组会衰减为指向其第一个元素的指针,这就是为什么array + 8在函数调用中不起作用的原因。
的确,多维数组存储在连续的内存中,但例如,2d数组是1d数组的数组。考虑2d数组:

int array[4][4] = { { 1,  2,  3,  4  },
                    { 5,  6,  7,  8  },
                    { 9,  10, 11, 12 },
                    { 13, 14, 15, 16 } };

字符串
这是一个由四个int元素组成的数组。由于数组衰减为指向其第一个元素的指针,因此在函数调用foo(array)中,数组标识符array衰减为指向其第一个元素的指针,这是一个由四个int组成的数组。第一个元素的大小是一个四个int s的数组的大小,它正好是四个int s的大小。
指针算术是这样的,即向指针值添加一个整数会使指针值增加所引用类型大小的整数倍。也就是说,向指向int的指针添加1会使指针增加一个int的大小,向指向四个int s的数组的指针添加1会使指针增加四个int s的数组的大小。
因此,函数调用foo(array + 8)尝试将指向array的第一个数组的指针递增8个数组的大小,每个数组有4个int。现在,array + 3将导致指向array的第四个数组的指针,即2d数组的最后一行,而array + 4将导致指向array的最后一个元素的指针。任何试图使用指针算术形成超过数组末尾的地址的尝试都会导致C中的未定义行为,因此函数调用foo(array + 8)会导致未定义行为,而不管foo中发生了什么。
考虑到这一点,你可以通过传递由array + 2构成的指针来传递数组的后半部分:

#include <stdio.h>

void array_print_2d(size_t rows, size_t cols, int arr[][cols]) {
    for (size_t i = 0; i < rows; i++) {
        for (size_t j = 0; j < cols; j++) {
            printf("%-2d ", arr[i][j]);
        }
        putchar('\n');
    }
}

int main(void) {
    int array[4][4] = { { 1,  2,  3,  4  },
                        { 5,  6,  7,  8  },
                        { 9,  10, 11, 12 },
                        { 13, 14, 15, 16 } };
    array_print_2d(2, 4, array + 2);
}


示例运行:

$ ./array_half 
9  10 11 12 
13 14 15 16


这里array_print_2d接受一个可变长度数组(在C99中添加,在后来的标准中是可选的,但广泛使用)和数组的行数和列数。通过传递array + 2作为数组参数,传递指向2d数组第三行的指针。注意,这意味着函数“看到”的数组只有两行,这必须在函数调用中考虑。
考虑到上面关于指针算术的讨论,看起来你可以这样做:

int *elem = &array[0][0];
foo(elem + 8);


这里elem是指向array第一行第一个元素的指针,增加该指针将增加int大小的倍数。只要操作的结果不指向第一行末尾后一行以外的位置,并且不试图取消引用第一行末尾后一行的指针,就可以执行此操作。但是elem + 8试图形成一个远超过第一行末尾的地址,因此将array视为平面1d数组的尝试会导致未定义的行为。虽然有些实现的行为和你在这里所期望的一样,有些程序员利用了这一点,但C标准非常清楚这是未定义的行为。C标准的附录J显示了导致未定义行为的相关示例:
数组下标超出范围,即使一个对象显然可以用给定的下标访问(如左值表达式a[1][7]给定声明int a[4][5])(6.5.6)。
上面的引用来自C99标准,但该示例在今天的C23标准中仍然存在。
There is some discussion of undefined behavior due to treatment of multidimensional arrays as flat arrays here.

biswetbf

biswetbf2#

您可以:

void foo(int arr[][4], size_t len) // or: void foo(int (*arr)[4], size_t len)
{
}

字符串
(我添加了len,以指示该函数应操作多少个int[4]类型的元素。
int array[4][4]的后半部分传递给函数:

foo(array + 2, 4 - 2);  // or: foo(&array[2], 4 - 2);


关于将整个数组扁平化为int的一维数组的问题,例如通过定义:

void bar(int arr[], size_t len) // or: void bar(int *arr, size_t len)
{
}


然后用array的后半部分调用它:

bar(&array[2][0], 8);


如果函数bar()访问arr[4],根据C标准,这将导致 *undefined行为 *,因为arr[4]超出了内部维度数组元素之一的边界。这实际上与调用者访问array[2][4]相同。虽然有几个实现会有效地将array[2][4]视为访问与array[3][0]相同的元素,但这在技术上是未定义的行为。

vecaoik1

vecaoik13#

好吧,让我们评估你发布的表达式,给出定义:

int array[4][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12},
    {13, 14, 15, 16},
};

字符串
要解释的表达式为:

*(*array + 8)


array是一个由四个元素组成的数组,所以它的名字出现在表达式中可以被解释为数组的第一个元素的地址,所以让我们在array本身出现的地方替换它:

*(*&array[0] + 8)


但是array[0]本身也是一个4个整数的数组,它的地址--由&运算符表示--是指向该数组的指针,这是一个4个整数的数组,由*运算符解引用,再次在数组的第一个位置给出4个整数的数组(见1.下面):

*(array[0] + 8)


现在我们有了第一个数组,它出现在表达式中是它的第一个元素的地址,所以表达式变成:

*(&array[0][0] + 8)


这是一个指向int的指针,加上8,可以访问数组的第九个元素,该数组被视为元素的线性数组(有些人会认为U. B.这里调用了,严格来说这是真的,因为我们超出了数组的范围)这里发生的是,由于二维数组的所有元素都是按顺序排列的,第九个元素恰好是array[2][0]位置上的元素,即9。这是在打印输出中观察到的输出。
这可以通过仅改变该元素的初始值来测试,并看到打印结果相应地变化。但我坚持认为,你已经解引用了一个只有4个元素的数组的第九个元素(所以这个操作作为C构造是不法律的的)。(这只能根据四个整数数组本身组成数组的事实来预测)
那么,是否有一种方法可以只将数组的后半部分传递给函数(在前面的例子中,这将是{{9,10,11,12},{13,14,15,16}})?如果有这样的方法,我会很高兴听到它。
是的,数组是线性的,所以array + 2将是指向第三个元素的指针(这是指向四个元素数组的指针)高出两个位置(这是单元格大小的两倍,也就是矩阵的一整行的大小)
我们也可以把这个表达式解释为一个练习:

array + 2


数组将被解释为指向第一个元素的指针(这是指向第一个四个int的数组的指针),

&array[0] + 2


这是一个四个整数的数组的地址,上移了两个位置,这是数组的两行……
...加上2,这是两个四个int的数组,所以它将指向数组的第三行,并且将具有类型int (*)[4](与下面的参数定义兼容)。因此,执行所需操作的方法是将foo()调用为:

foo(array + 2);


这完全法律的您应该使用以下原型定义foo()

void foo(int (*array)[4]);


这是一个函数将接受一个指针。

注意事项:

1.这里通常有一个微妙的误解,因为数组名称(这是array)本身意味着一件事,而数组地址(这是&array)意味着另一件事。C中的数组名本身意味着它的第一个元素的地址(这是数组的特殊解释),因此它的类型是指向数组单元格类型的指针,而数组的地址意味着指向数组本身的指针,因此它的类型是指向4个整数的数组的指针,而不是指向int的指针。这允许*&运算符相互抵消,这是在解释&运算符之前对[0]索引求值的结果--因为它的优先级高于&

相关问题