当它传递给函数时,二维数组的第二个下标有什么用?

问题描述 投票:1回答:2

在C编程语言中,他们说只有数组的第一维(下标)可以自由指定;必须指定第二个下标:

如果要将二维数组传递给函数,则函数中的参数声明必须包含列数;行数是无关紧要的,因为传递的是和以前一样的指向行数组的指针,其中每行是5个ints的数组。在这种特殊情况下,它是指向5个ints数组的对象的指针。因此,如果要将数组daytab传递给函数ff的声明将是:

f(int daytab[][5]) { ... }

更一般地说,只有数组的第一维(下标)是自由的;必须指定所有其他人。

但是在我的程序中,当我更改第二个下标(列)的值时,程序仍然可以正常工作。例如,该计划

#include"stdio.h"
void arf(int[][2]); // I wrote 2 instead of 5
main() {
    int a[][5] = {{1,2,3,4,5}, {9,29,39,49,59}};
    arf(a);
}
void arf(int arr[][2]) {    //here too, changed the 2nd subscript
    size_t i;
    printf("%d", arr[0][4]);    //a[0][4] is 5
}

打印输出5,而不是垃圾值(我的期望)。

c multidimensional-array
2个回答
1
投票

我有两个想法,讨论最终未定义的行为是否可取。这是问题中代码的最低修改版本:

#include <stdio.h>

void arf(int arr[][2]);

int main(void)
{
    int a[][5] = { { 1, 2, 3, 4, 5 }, { 0, 9, 8, 7, 6 } };
    arf(a);
}

void arf(int arr[][2])
{
    printf("%d\n", arr[0][4]);
    printf("%d\n", arr[1][4]);
}

在Mac上使用GCC 7.3.0编译时,我得到:

$ gcc -o arr2d-13 arr2d-13.c
arr2d-13.c: In function ‘main’:
arr2d-13.c:8:9: warning: passing argument 1 of ‘arf’ from incompatible pointer type [-Wincompatible-pointer-types]
     arf(a);
         ^
arr2d-13.c:3:6: note: expected ‘int (*)[2]’ but argument is of type ‘int (*)[5]’
 void arf(int arr[][2]);
      ^~~
$

仅此一点就足以告诉你,你做错了。我不得不压制我常用的编译标志;我无法接受代码,因为它有编译器警告。但是,运行时输出为:

5
9

为什么?嗯,这是官方未定义的行为,但......

内存中数组的布局如下:

╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
║ 1 ║ 2 ║ 3 ║ 4 ║ 5 ║ 0 ║ 9 ║ 8 ║ 7 ║ 6 ║
╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

当尽管有警告,数组传递给arf()时,函数认为输入数组的第0行以包含1的元素开头,输入数组的第1行以包含3的元素开头,依此类推 - 因为你告诉编译器该函数采用每行2个元素的数组。

当你滥用下标4(参数的声明说有效的第二个下标是01)时,它会在行的起始地址加4,最后在第一种情况下打印5;它在第二种情况下打印9,因为计数从包含3的元素开始(之后的0,1,2,3,4元素是9)。

如果您的编译器没有警告您类型不匹配,则需要获得更好的编译器。如果编译器警告您类型不匹配,则需要注意编译器。在编程生涯的这个阶段,如果编译器需要警告你的代码,它就会发现你需要修复的bug。我仍然认为这样的编译器警告 - 我通常用选项来编译来强制执行我的规则(gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes) - 但我只用C编写了将近35年,所以我知道我还有更多要学习的东西。

您还需要确保在C11模式(当前标准)或C99(旧标准)中进行编译 - 在C90(古老标准)或预标准模式下编译没有真正的借口。 C99表示main()本身已经过时,因为没有返回类型。始终明确指定返回类型,最好在忽略参数列表时使用int main(void),如果坚持则使用int main()(标准中有使用该表示法的示例)。


1
投票

将多维数组转换为具有相同大小但具有不同几何的另一个数组的行为并非未定义:标准保证多维数组的元素将按行主顺序连续布局。

如果要让编译器更改数组的几何图形,可以转换为所需尺寸的指针,然后取消引用,如下所示:

int main(void)
{
  const int a[][5] = { { 1, 2, 3, 4, 5 }, { 0, 9, 8, 7, 6 } };
  arf(*(const int (*const)[][2])a);
}

使用其他下标,即转换为维数[5][2](或[sizeof(a)/sizeof(int[2])][2]以获得更多弹性)的数组,是告诉编译器有多少行,因此它可能在编译时捕获边界错误。在这个特定的例子中,信息只是在接收端丢弃,这对你没有任何好处,但C也允许你声明像void arf( const ptrdiff_t m, const ptrdiff_t n, const int a[m][n])这样的函数原型。 C ++没有,但仍然允许你声明void arf(const int a[rows][cols]),其中rowscolsconstexpr值。

请注意,您始终应始终检查C和C ++中溢出的数组边界。

© www.soinside.com 2019 - 2024. All rights reserved.