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

huangapple go评论93阅读模式
英文:

How to manipulate multidimensional arrays with pointers in C?

问题

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

我知道我可以两次解引用它以获取数组中的任何值。例如:

printf("%d", *(*array+8));
//输出将为9

这让我觉得C语言似乎没有区分一维数组和多维数组,因此一个维度为4x4的多维数组会以与一个具有16个元素的一维数组相同的方式存储在内存中。因此,如果我想要使用一个函数仅处理数组的后半部分,我可以像这样将它传递给函数:

void foo(array+8)
{

}

但这不起作用,导致分段错误。

那么,是否有一种方法可以将数组的后半部分传递给函数(在上述示例中,即{{9,10,11,12},{13,14,15,16}})?如果存在这样的方法,我将非常高兴听到它。

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

英文:

Say I have the following array:

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

I know that I can twice dereference it to get any value I want from the array. For example:

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

It kind of made me assume that C doesn't really differentiate between one-dimensional and multidimensional arrays, and so a multidimensional array of dimensions 4x4 will be stored in memory in the same manner as a one-dimensional array with 16 elements. So I thought that if I wanted to use a function to do something only to the second half of the array, I could just pass it to the function like that:

void foo(array+8)
{
    
}

But it doesn't work and results in a segmentation fault.

So, IS there a way to pass to the function only the latter half of the array (in the aforementioned example, that would be {{9,10,11,12},{13,14,15,16}})? If such a way exists, I would be really happy to hear about it.

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

答案1

得分: 3

C语言中,数组是真正的类型,而不是经常被随意建议的指针。在大多数表达式中,数组会衰变为指向它们的第一个元素的指针,这就是为什么在函数调用中array + 8对您而言无法工作的原因。

确实,多维数组存储在连续的内存中,但是一个二维数组,例如,是一个一维数组的数组。考虑这个二维数组:

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的数组的大小,也就是四个int的大小。

指针算术是这样的,将整数添加到指针的值会将指针值递增为所引用类型的大小的整数倍。也就是说,将1添加到指向int的指针会将指针递增为一个int的大小,将1添加到指向包含四个int的数组的指针会将指针递增为一个包含四个int的数组的大小。

因此,函数调用foo(array + 8)试图将指向array的第一个数组的指针递增为包含八个四个int数组的大小。现在,array + 3会得到一个指向array的第四个数组的指针,也就是二维数组的最后一行,而array + 4会得到一个指向array的最后一个元素之后的指针。在C中,任何尝试使用指针算术形成超出数组一过去的地址的尝试都会导致未定义的行为,因此不管在foo中发生了什么,函数调用foo(array + 8)都会导致未定义的行为。

有了这个理解,您可以通过传递由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作为数组参数传递,传递了一个指向二维数组的第三行的指针。请注意,这意味着函数所见到的数组只有两行,这必须在函数调用中考虑到。

鉴于上面关于指针算术的讨论,您可能会认为可以像这样做一些事情:

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

这里,elem是指向array的第一行的第一个元素的指针,递增该指针会递增int的大小的倍数。只要操作的结果不指向第一行的末尾一过去,只要不尝试解引用第一行末尾一过去的指针,就可以这样做。但是elem + 8尝试形成一个远远超出第一行末尾一过去的地址,因此这种尝试将array视为平坦的一维数组会导致未定义的行为。虽然一些实现在这里的行为与您可能期望的相同,而一些程序员会利用这一点,但C标准非常明确,这是未定义的行为。 C标准的Annex J中显示了一个导致未定义行为的相关示例:

数组下标超出范围,即使通过给定的下标似乎可以访问对象(如给定声明int a[4][5]的lvalue表达式a[1][7])(6.5.6)。

上述引用来自C99标准,但这个示例在C23标准中仍然存在。

这里有一些关于由于将多维数组视为平坦数组而导致的未定义行为的讨论。

英文:

Arrays are genuine types in C and not pointers as is often carelessly suggested. Arrays decay to pointers to their first elements in most expressions, and this is why array + 8 is not working for you in a function call.

It is true that multidimensional arrays are stored in contiguous memory, but a 2d array, for example, is an array of 1d arrays. Consider the 2d array:

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

This is an array of four arrays of four int elements. Since arrays decay to pointers to their first elements, in a function call foo(array) the array identifier array decays to a pointer to its first element, which is an array of four ints. The size of this first element is the size of an array of four ints, which is just the size of four ints.

Pointer arithmetic is such that adding an integer to the value of a pointer increments the pointer value by the integral multiple of the size of the referenced type. That is, adding 1 to a pointer to int increments the pointer by the size of one int, and adding 1 to a pointer to an array of four ints increments the pointer by the size of an array of four ints.

So, the function call foo(array + 8) attempts to increment a pointer to the first array of array by the size of eight arrays of four ints. Now, array + 3 would result in a pointer to the fourth array of array, i.e., the last row of the 2d array, while array + 4 would result in a pointer "one past" the last element of array. Any attempt to form an address beyond one past the end of an array using pointer arithmetic results in undefined behavior in C, so the function call foo(array + 8) results in undefined behavior regardless of anything that happens in foo.

With this in mind, you can pass the latter half of the array by passing the pointer formed by array + 2:

#include &lt;stdio.h&gt;

void array_print_2d(size_t rows, size_t cols, int arr[][cols]) {
    for (size_t i = 0; i &lt; rows; i++) {
        for (size_t j = 0; j &lt; cols; j++) {
            printf(&quot;%-2d &quot;, arr[i][j]);
        }
        putchar(&#39;\n&#39;);
    }
}

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);
}

Example run:

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

Here array_print_2d takes a variable length array (added in C99, optional in later standards but widely available) and the number of rows and columns of the array. By passing array + 2 as the array argument, a pointer to the third row of the 2d array is passed. Note that this means that the array "seen" by the function has only two rows, and this must be accounted for in the function call.

Given the discussion above about pointer arithmetic, it might seem that you could do something like:

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

Here elem is a pointer to the first element of the first row of array, and incrementing that pointer increments by multiples of the size of an int. You can do this so long as the result of the operation does not point beyond one past the end of the first row, and so long as you don't attempt to dereference a pointer one past the end of the first row. But elem + 8 attempts to form an address well beyond one past the end of the first row, so this attempt to treat array as a flat 1d array leads to undefined behavior. While some implementations behave as you might expect here and some programmers take advantage of this, the C Standard is very clear that this is undefined behavior. Annex J of the C Standard shows a relevant example causing undefined behavior:

> An array subscript is out of range, even if an object is apparently
> accessible with the given subscript (as in the lvalue expression
> a[1][7] given the declaration int a[4][5]) (6.5.6).

The above citation is from the C99 Standard, but the example remains today in the C23 Standard.

There is some discussion of undefined behavior due to treatment of multidimensional arrays as flat arrays here.

答案2

得分: 0

以下是您要翻译的内容:

您可以执行:

```c
void foo(int arr[][4], size_t len) // 或者: void foo(int (*arr)[4], size_t len)
{
}

(我添加了len来指示函数应该操作多少个类型为int[4]的元素。)

要将int array[4][4]的后半部分传递给函数:

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

关于您关于将整个数组展平为int类型的一维数组的问题,例如通过定义:

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

并将其与array的后半部分一起调用:

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

如果函数bar()访问arr[4],根据C标准,这将导致未定义行为,因为arr[4]超出了内部维数组元素的范围。从技术上讲,这与调用者访问array[2][4]是相同的。尽管有些实现实际上会将array[2][4]视为访问与array[3][0]相同的元素,但从技术上讲,这是未定义行为。


<details>
<summary>英文:</summary>

You can do:

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


(I added `len` to indicate how many elements of type `int[4]` the function should operate on.)

To pass the second half of the `int array[4][4]` to the function:

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

---
Regarding your question about flattening the entire array to a single dimensional array of `int`, e.g. by defining:

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


and calling it with the second half of `array` as:

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

If function `bar()` accesses `arr[4]`, that will result in *undefined behavior* according to the C standard because `arr[4]` is out of bounds of the one of the inner-dimensional array elements. It is effectively the same as if the caller accessed `array[2][4]`. Although several implementations will effectively treat `array[2][4]` as accessing the same element as `array[3][0]`, it is technically undefined behavior.

</details>



# 答案3
**得分**: 0

好的,让我们评估你发布的表达式,根据定义:

```c
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] 又是一个包含四个整数的数组,它的地址(由 & 运算符表示)是一个指向数组的指针,这是一个包含四个整数的数组,通过 * 运算符解引用后,得到数组的四个整数,位于数组的第一个位置(参见1.):

*(array[0] + 8)

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

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

这是一个指向 int 的指针,加上 8,可以访问数组视为线性元素数组的第九个元素(有些人会争论这里涉及到未定义行为,严格来说是正确的,因为我们超出了数组的边界)。这里发生的是,由于二维数组的所有元素都是连续的,第九个元素恰好是位于 array[2][0] 位置的元素,即 9。这是在输出中观察到的结果。

可以通过仅更改该元素的初始值来进行测试,并看到打印结果相应变化。但我要强调的是,你已经解引用了一个只有 4 个元素的数组的第九个元素(因此该操作在 C 结构中是不合法的)。(这只能基于四个整数数组本身被组成为数组的事实进行预测)

那么,有没有办法将数组的后半部分传递给函数(在前述示例中,这将是 {{9,10,11,12},{13,14,15,16}})?如果存在这样一种方式,我将非常乐意听到。

是的,该数组是线性的,因此 array + 2 将是指向第三个元素的指针(这是指向包含四个元素的数组的指针),再加上两倍的单元格大小(这是矩阵的一整行的大小),将会指向数组的第三行。

我们也可以将这个表达式解释为一个练习:

array + 2

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

&array[0] + 2

这是一个包含四个整数的数组的地址,向上偏移了两个位置,即两行的数组...

...再加上 2,这是再往前两个数组,因此它将指向数组的第三行,并且类型是 int (*)[4](这与下面的参数定义兼容)。所以做你想要的事情的方式是调用 foo()

foo(array + 2);

这是完全合法的。你应该用以下原型定义 foo()

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

该函数将接受一个指向包含四个整数的数组的指针。

注:

  1. 这里通常有一个微妙的误解,因为数组名(即 array)本身意味着一回事,而数组的地址(即 &array)意味着另一回事。在 C 中,数组名本身意味着它的第一个元素的地址(这是数组具有的一种特殊解释),因此它的类型是数组单元格类型的指针,而数组的地址意味着对数组本身的指针,因此它的类型是指向包含 4 个整数的数组的指针,而不是指向 int 的指针。这使得 *& 运算符可以互相抵消,并且是在解释 & 运算符之前评估 [0] 索引的结果 --- 因为它比 & 优先级更高)
英文:

well, let's evaluate the expression you post, given the definition:

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

The expression to interpret is:

*(*array + 8)

array is an array of four arrays of four elements, so its name appearing in an expression can be interpreted as the address of the first element of the array, so let's substitute it where array itself appears:

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

but array[0] is, again, itself an array of 4 integers, and its address --as denoted by the &amp; operator--, is a pointer to the array, this is an array of four integers, which dereferenced by the * operator,
again gives the array of four integers at first position of array(see 1. below):

*(array[0] + 8)

now we have the first array which, appearing in an expression is the address of its first element, so the expression becomes:

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

and that is a pointer to int which, plus 8, gives you access to the ninth element of the array viewed as a linear array of elements (some will argue U.B. is invoked here, and strictly that is true, as we got out of bounds of the array) What is happening here is that, as all the elements of the two dimensional array are in sequence, the ninth element happens to be the element in position of array[2][0], which is 9. This is the output observed in the printout.

This can be tested by changing the initial value of that element only, and see that the printed result varies accordingly. But I insist, you have dereferenced the ninth element of an array that has only 4 elements (so the operation is not legal as a C construct). (this can be predicted only based upon the fact that the four integer arrays are composed themselves as array of arrays)

> So, IS there a way to pass to the function only the latter half of the array (in the aforementioned example, that would be {{9,10,11,12},{13,14,15,16}})? If such a way exists, I would be really happy to hear about it.

Yes, the array is linear, so array + 2 will be a pointer to the third element (this is a pointer to a four element array) two places higher (this is two times the size of a cell, which is the size of a full row of the matrix)

We can also interpret this expression as an exercise:

array + 2

array will be interpreted as a pointer to the first element (this is a pointer to the first array of four ints),

&amp;array[0] + 2

which is the address of an array of four integers, shifted up two positions, this is two rows of the array...

... plus 2 which is two arrays of four ints further, so it will point to the third row of the array, and will have type int (*)[4] (which is compatible with the parameter definition below). So the way of doing what you want is to call foo() as:

   foo(array + 2);

and this is perfectly legal. You should define foo() with the following prototype:

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

This is the function will take a pointer to <arrays of four integers>.

Notes:

  1. There's generally a subtle misunderstanding here, because the array name (this is array) by itself means one thing while the array address (this is &amp;array) means a different thing. The array name itself in C means the address of it's first element (which is a special interpretation that arrays have), so its type is a pointer to the array cell type, while the adress of the array means a pointer to the array itself, so it's of type pointer to an array of 4 integers, and not a pointer to int. This allows the * and &amp; operators to cancel each other, and is a consequence of the evaluation of the [0] index that happens before the interpretation of &amp; operator ---as it has higher precedence than &amp;)

huangapple
  • 本文由 发表于 2023年7月6日 16:03:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/76626721.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定