首页 > 解决方案 > C 和 C++ 中的二维数组差异

问题描述

我有这两行代码,我认为它们可以在 C 和 C++ 上编译。

int a[3][3] = {{10,20,30},{40,50,60},{70,80,90}};
int *p[3] = {a+0, a+1, a+2};

C编译器编译得很好。在 Visual Studio C++ 编译器上出现此错误: 错误 C2440: 'initializing': cannot convert from 'int (*)[3]' to 'int *'

我试图了解这两种情况之间的区别。

标签: c++c

解决方案


数组与指针可能是 C(以及从 C 继承的 C++)中较难的主题之一。一旦你理解了背后的概念,这实际上很容易,但这个概念可能会出乎初学者的意料——我从未在其他编程语言中看到过类似的东西。

Borgleader 在他的评论中说:int a[3][3]衰变到int*但那是错误的!(如果是真的,那么 OP 的问题就不存在了。)

真相是:

  1. a是类型int [3][3]
  2. a可能衰减到int (*)[3]指向 int 数组 3 的指针

因此,OP 的定义存在类型不匹配错误:

int *p[3] = {a+0, a+1, a+2};

的元素p有类型int*但是a+0(以及a+1, a+2)提供int (*)[3].

这正是Clang 在 Wandbox 上 Bob__ 的现场演示中所说的

prog.c:7:18: warning: incompatible pointer types initializing 'int *' with an expression of type 'int (*)[3]' [-Wincompatible-pointer-types]
    int *p[3] = {a+0, a+1, a+2};
                 ^~~

Bob__ 将 C 与-std=c11和一起使用-pedantic

我将其更改为 C++-std=c++17并且没有-pedantic. C++ 将此报告为错误,因为默认情况下它在类型兼容性方面更加严格。


实际上,我对 Quora 上有这个例子的评论感到困惑。

考虑到 C 曾经对不匹配的类型非常宽容,这个例子可能会奏效。为了说明这一点,我在 godbolt.org 上做了一个稍微扩展的例子:

#include <stdio.h>

int main()
{
  int a[3][3] = {{10,20,30},{40,50,60},{70,80,90}};
  int *p[3] = { a + 0, a + 1, a + 2 };
  int *p1[3] = { *(a + 0), *(a + 1), *(a + 2) };
  int *p2[3] = { a[0], a[1], a[2] };
  return 0;
}

int *p[3] = { a + 0, a + 1, a + 2 };它编译:

  mov rcx, qword ptr [rbp - 168] # load rcx with address of a
  mov qword ptr [rbp - 80], rcx  # store rcx to p[0] 
  mov rcx, qword ptr [rbp - 168] # load rcx with address of a
  add rcx, 12                    # add 12 to rcx (1 * 3 * sizeof (int))
  mov qword ptr [rbp - 72], rcx  # store rcx to p[1]
  mov rcx, qword ptr [rbp - 168] # load rcx with address of a
  add rcx, 24                    # add 24 to rcx (2 * 3 * sizeof (int))
  mov qword ptr [rbp - 64], rcx  # store rcx to p[2]

对于int *p1[3] = { *(a + 0), *(a + 1), *(a + 2) };

  mov rcx, qword ptr [rbp - 168] # load rcx with address of a
  mov qword ptr [rbp - 112], rcx # store rcx to p1[0]  
  add rcx, 12                    # add 12 to rcx (1 * 3 * sizeof (int))
  mov qword ptr [rbp - 104], rcx # store rcx to p1[1] 
  mov rcx, qword ptr [rbp - 168] # load rcx with address of a
  add rcx, 24                    # add 24 to rcx (2 * 3 * sizeof (int))
  mov qword ptr [rbp - 96], rcx  # store rcx to p1[2]

对于int *p2[3] = { a[0], a[1], a[2] };

  mov rcx, qword ptr [rbp - 168] # load rcx with address of a
  mov qword ptr [rbp - 144], rcx # store rcx to p2[0]  
  add rcx, 12                    # add 12 to rcx (1 * 3 * sizeof (int))
  mov qword ptr [rbp - 136], rcx # store rcx to p2[1] 
  mov rcx, qword ptr [rbp - 168] # load rcx with address of a
  add rcx, 24                    # add 24 to rcx (2 * 3 * sizeof (int))
  mov qword ptr [rbp - 128], rcx # store rcx to p2[2]

Live Demo on godbolt

无需过多深入,为所有三行生成了几乎相同的代码。(唯一的区别是mov qword ptr [rbp -......之后的地址,因为初始化存储在堆栈上具有不同地址的变量中,当然。)

这并不奇怪,*(a + 0)并且会a[0]产生等效代码,因为根据cppreference: Subscript

根据定义,下标运算符E1[E2]*((E1)+(E2)).

但即使使用错误类型的指针进行初始化也没有什么不同。

恕我直言,这对两节课有好处:

  1. 通过引入必要的取消引用运算符来使用正确的类型可以防止警告(在 C 中)和错误(在 C++ 中)。

  2. 优化取消引用运算符(以警告为代价)不会改进生成的二进制代码。


在另一条评论中,OP 表示

据我了解,“a”是指向整个数组的指针......

那是错误的。a是一个数组。如果需要,它可能会衰减为指针。

这是一个区别,很容易通过一个例子来说明:

#include <stdio.h>

void printSizes(int a[3][3], int (*p)[3])
{
  puts("when a and p passed to a function:");
  printf("sizeof a: %u\n", (unsigned)sizeof a);
  printf("sizeof p: %u\n", (unsigned)sizeof p);
}

int main()
{
  int a[3][3] = {{10,20,30},{40,50,60},{70,80,90}};
  int (*p)[3] = { a + 0, a + 1, a + 2 };
  printf("sizeof a: %u\n", (unsigned)sizeof a);
  printf("sizeof p: %u\n", (unsigned)sizeof p);
  return 0;
}

输出:

sizeof a: 36
sizeof p: 8
when a and p passed to a function:
sizeof a: 8
sizeof p: 8

Live Demo on ideone

关于数组和指针的混淆可能来自于数组在大多数情况下衰减为指针的事实。甚至下标运算符 ( operator[]) 是为指针定义的,但不是为数组定义的。sizeof运算符是少数例外之一,并显示了差异。

由于数组可能不能用作参数,因此在 function 中不再存在这种差异printSize()。即使给出数组类型,编译器也会使用数组衰减产生的指针类型。


推荐阅读