【问题标题】:What are the exact differences betwenn int **a and int a[][] as function parameters in C and C++?int **a 和 int a[][] 作为 C 和 C++ 中的函数参数之间的确切区别是什么?
【发布时间】:2020-09-24 18:08:19
【问题描述】:

我在编写带有矩阵的程序时遇到了这个问题,我使用int** m 来声明我的矩阵 - 因为我需要动态分配并且在函数中我使用了int a[][]。我不记得有任何问题。但是当我使用简单的m[6][6] 矩阵和f(int**m, int** m2, rest params) 时,我遇到了麻烦。

它已编译,当我运行程序(使用 GCC 的代码块)时,它就崩溃了。我尝试通过添加printf()s 进行调试,它在if() 块处崩溃,崩溃没有意义。将第一个函数参数从 int a[][] 修改为 int* a[6] 并继续前进,稍后修改了第二个参数,我的程序在第一次尝试时工作。通过更仔细地调试我保存在 int m[i][j] 中的内容并检查 if 是垃圾值,而不是我输入的内容,我只是输入了 10 来标记一些东西。

这么多年过去了,除非我在做这样的事情时遇到 GCC 的编译器错误,否则我只会写我想到的第一种方式。

在所有 4 种组合中使用 int**int [][] 声明变量/获取函数参数背后的逻辑是什么?我使用的大多数预定义函数在函数头中使用int**

我知道int [][] 不等于int**,它是int* [] 正确,但是我缺少什么? int[][] 是一个多维数组,表示数组的数组,所有 3 种写法看起来都一样。而对于int[][],它几乎总是要求只让第一个参数无效,比如对于int array[][][][],我需要在函数参数中放入int array a[][n1][n2][n3],对吗?它需要知道除第一个以外的多维数组的维度,因为int*int[]在声明函数参数时可以毫无问题地使用?

【问题讨论】:

  • 我使用int** m 来声明我的矩阵 int ** 没有创建二维数组。阅读:Correctly allocating multi-dimensional arrays
  • 经过多年的痛苦,我意识到在 c++ 中实际上永远不需要 int**int[][],这就是 c++ 开始变得有趣的时候。我很高兴我不必使用 C ;)

标签: c++ c function pointers parameters


【解决方案1】:

int **aint a[][] 在 C 和 C++ 中作为函数参数的确切区别是什么?

int *a。这是一个指向 int 的指针。

int **a。这是一个指向 int 的指针。

int a[]在所有其他上下文中是一个未指定数量的 int 数组,但作为函数参数声明符,它被调整为指向 int 的指针,即在这种情况下它是就像你写了int *a一样。

int a[][]是一个未指定数量的数组,包含未指定数量的整数,但这种类型是错误的,因为数组元素不能是未指定大小的数组。 p>

int *a[]在所有其他上下文中是一个未指定数量的指向 int 的指针数组,但作为函数参数声明符,它被调整为指向 int 的指针的指针,即 in这种情况就像你写了int **a一样。

int (*a)[N] 这是一个指向 N 个整数数组的指针。

int a[][N]在所有其他上下文中是一个未指定数量的 N 个 int 数组的数组,但作为函数参数声明符,它被调整为指向 N 个数组的指针int ie 在这种情况下,它与您编写 int (*a)[N] 相同。


一些例子:

void fun_1D(int*);        // argument is pointer to int
void fun_1D(int[]);       // same as above
void fun_1D(int[10]);     // same as above; note that 10 is ignored

int arr_1D[20];           // array of int
fun_1D(arr_1D);           // implicit conversion
fun_1D(&arr_1D[0]);       // same as above

void fun_2D(int (*)[20]); // note that 20 is not ignored
void fun_2D(int[][20]);   // same as above
void fun_2D(int[10][20]); // same as above; note that 10 is ignored

int arr_2D[20][20];       // array of array of int
fun_2D(arr_2D);           // implicit conversion
fun_2D(&arr_2D[0]);       // same as above

fun_1D(arr_2D[i]);        // implicit conversion
fun_1D(&arr_2D[i][0]);    // same as above

void fun_ptrs(int**);     // argument is pointer to pointer to int
void fun_ptrs(int*[]);    // same as above
void fun_ptrs(int*[10]);  // same as above; note that 10 is ignored

int *arr_ptr[20];         // array of pointers
fun_ptrs(arr_ptr);        // implicit conversion
fun_ptrs(&arr_ptr[0]);    // same as above

fun_1D(arr_ptr[i]);       // no conversion needed


// broken examples
fun_2D(arr_ptr);          // int*[20] is not int(*)[20]
fun_ptrs(arr_2D);         // int[20][20] is not int**

注意声明为数组的函数参数如何调整为与数组在左值到右值转换时衰减到的相同指针类型。

要记住的一些简单的经验法则:

  • 数组不是指针。
  • 指针不是数组。
  • 作为数组的函数参数实际上不是数组。它实际上被调整为指向此类数组元素的指针。在此调整之后,函数参数永远不会是数组。这不适用于任何其他上下文,函数参数除外。
  • 并非每种类型都可以是数组的元素。未指定长度的数组就是这样的类型。
  • 没有“数组未指定长度”类型的对象。它们只能用在引用在别处定义的数组的外部变量声明中,或者在从数组的初始化器推导出实际大小的定义中,或者在数组被调整为指向的指针的函数参数声明中使用元素。

如果我在 main 中声明 int a[6][6] 并调用一个需要 int** a 的函数,它会起作用吗?

不,因为int[6][6] 不是int**,也不会衰减为一个。 int[6][6] 衰减为 int(*)[6],正如我上面解释的那样。 int(*)[6]int** 不能相互转换。一个是指向数组的指针,另一个是指向指针的指针。

反之亦然

否,因为 int[6][6] 参数已调整为 int(*)[6]。请参阅上一段了解这些不兼容的原因。

似乎int a[][] 不被接受

正确。正如我在第四段中解释的那样(不包括引用)。

如果我有函数 f1(int *a) 和 f2(int a[]) 和 f2(int a[6]) 在这些情况下 sizeof (a) 会返回什么?

正如我上面解释的,所有这些都声明了一个int* 类型的参数。 sizeof asizeof(int*) 相同,因为那是类型。

【讨论】:

  • 我知道它们各自的意思,我要求以某种方式声明数组并在函数参数中以另一种方式使用它的上下文。如果我在 main 中声明 int a[6][6] 并调用一个需要 int** a 的函数,它会起作用吗?反之, int ** m in main 并且函数需要 int a[][6],似乎 int a[][] 在当前的 C/C++ 标准中不被接受,或者?
  • 另一个问题:for int* a;在 main sizeof(a) 中,指针的大小是整数值的大小。当我应用 sizeof 时 int a[20] 将返回 4 * 20 字节。如果我有函数 f1(int *a) 和 f2(int a[]) 和 f2(int a[6]) 在这些情况下 sizeof (a) 会返回什么?
  • 谢谢。最后一个问题:编写一个在一个或两个矩阵上执行操作的函数的最佳通用方法是什么?如果我使用 f(int** m, int n, int m) 并且有人使用 int a[6][6] 创建了一个矩阵,他将无法使用我的函数还是?没有解决方法?他必须创建一个动态分配的中间矩阵,将数据复制到其中并使用该矩阵进行函数调用?
  • @Mihnea what is the best general way to write a function that does stuff on one or 2 matrices 写一个代表矩阵的类。将数组分配为平面一维,其中行是按顺序排列的(std::vector 对此很有用)。更好的是,使用现成的矩阵库。
  • C 风格不带类?
【解决方案2】:
int **a

这是指向 int 的指针。

int a[][]

这是 int 数组的数组,但这是无效的,因为在声明时必须知道第二个数组的维度,即第二个数组必须是完整的,因为它不能在之后完成,就像那样

int a[][DIM]

【讨论】:

  • 是的,只有最左边的维度可以留空。
【解决方案3】:

C++ 继承了 C 的数组衰减为指针的行为。

这种行为真的很有帮助,直到一个人碰到锋利的边缘并意识到发生了一些奇怪的事情,然后试图弄清楚它做了什么并测试了这种行为并意识到它有点疯狂。

但请保持冷静,一旦你意识到数组衰减为指针意味着什么,一切都会重新变得有意义。

这里有一个例子来说明差异。

static void func1(int** p) {
    (void)p;
}

static void func2(int (&a)[2][2]) {
    (void)a;
}

int main() {
    // x is a 2-dimensional array of int objects.
    int x[2][2] = {{10, 20}, {30, 40}};
    // x[0][0] is 10, and is at 0x1000 (say, for example)
    // x[0][1] is 20, and is at 0x1004
    // x[1][0] is 30, and is at 0x1008
    // x[1][1] is 40, and is at 0x100C

    // y is a 1-dimensional array of pointers.
    int* y[2] = { &x[0][0], &x[1][0] };
    // y[0] is 0x1000, and is at 0x1010 (say, for example)
    // y[1] is 0x1008, and is at 0x1018

    // x cannot decay into an int**, because it is not an array of pointers.
    // y can decay into an int**, because it is an array of pointers.
    func1(y);

    // x can be used for an int[2][2] reference parameter.
    // y cannot be used for an int[2][2] reference parameter.
    func2(x);
}

【讨论】:

  • 好吧,当我声明 x[2][2] 时,我不能用它来调用需要 int ** 矩阵的函数,因为我可以执行矩阵 ++ 之类的操作或修改指针。 ..当我无法在声明/函数调用中交换它们时,最后一个答案指出了我想要的。但是当我编写函数对矩阵进行操作时,最好使用 f(int **matrix, int m, int n) 而不是静态声明矩阵?由于 f(int a[][m], int n) 意味着对其中一个维度进行硬编码,而不是使我的函数通常不再可用,对吧?
【解决方案4】:

当您在 C++ 中声明数组时,您正在分配一个连续的内存块来保存数组的成员。

 int  data[6];    // Block of 6 integers.

即使对于维度数组也是如此。

 int  threeD[2][3][4];    // Allocates a Block of 24 integers.
                          // The compiler keeps track of the number of
                          // of dimensions and does the maths for you 
                          // to calculate the correct offset.

现在,当将数组传递给函数时,数组将衰减为指向第一个元素的指针。

现在,如果您要动态分配多维数组。您倾向于将其作为数组的数组来执行。这些数组在连续内存中。但是您需要这样做才能像平常一样使用方括号运算符[]

int***  dynamicThreeD = new int**[2];
for(int l1 = 0 ; l1 < 2 ; ++l1) {
    dynamicThreeD[l1] = new int*[3];
    for(int l2 = 0 ; l2 < 3 ; ++l2) {
        dynamicThreeD[l1][l2] = new int[4];
    }
}

int threedD[2][3][4];

尽管这两种类型在访问方式上看起来相同:

dynamicThreeD[1][2][3] = 8;
threeD[1][2][3]        = 8;

这些不一样。 dynamicThreeD at ache [] 正在访问数组并检索下一个指针以引用下一个。而threeD 对象会产生索引计算(3) + (4 * 2) + (4*3 * 1),然后将其用作与第一个元素的偏移量。

dynamicThreeD[1][2][3] = 8;
// Equivalent to 
int** tmp1 = dynamicThreeD[1];
int*  tmp2 = tmp1[2];
tmp2[3] = 8;



threeD[1][2][3]        = 8;
// Equivalent to 
int index = (3) + (4 * 2) + (4*3 * 1)

// OK The compiler does not take the address like this
//    But I needed to do some type gymnastics to convert the type
//    at the language level.
//
//    But you get the same effect like this.
//    As the value is just offset by an index from the beginning of
//    the array.
(&threed[0][0][0])[index] = 8;

这样做的副作用是多维数组可以更有效,因为 1)我们只需要进行一次内存访问(不是 3 次)获取/设置数据。 2) 由于数据局部性和缓存,您可以获得更好的命中率。

另一方面,如果您有多个写入器,并行系统中的多维数组可能会很麻烦,因为如果所有成员都在同一缓存行中,缓存一致性就会成为问题。在这里,数组的数组有一个优势,因为每一行都可以位于内存的不同部分并独立工作(注意:我过于简化了一个非常复杂的问题)。

【讨论】:

    【解决方案5】:

    在其他很好的答案中,我想重点关注您的最后一个问题。


    “对于int[][],它几乎总是要求只让第一个参数无效,就像对于int array[][][][],我需要在函数参数中放入int数组a[][n1][n2][n3],对吧?”

    是的,只有第一个维度可以不指定。但是如果你想让其他维度在运行时可变/确定,至少在 C 中你可以使用:

    int foo (int n1, int n2, int a[][n1][n2]) { ...
    

    【讨论】:

      【解决方案6】:

      使用额外的间接级别,您可以传递固定大小的维度,因为 sizeof(*x)、sizeof(x[0][0]) 等知道间接类型的维度。 但通常在 C 中,数组 [] 只是一个指针 * 并且大小,尤其是在动态时,需要作为第二个参数、全局、MACRO 或在结构中传递或知道,直到类出现。当然,在 char* 和 char**argv 中经常使用 null 术语。对于指针,您需要支付 8 个字节的开销。

      【讨论】:

        猜你喜欢
        • 2011-02-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-06-16
        • 2020-05-05
        • 2021-05-04
        • 2020-12-27
        • 2021-09-17
        相关资源
        最近更新 更多