C/C++编译器如何区分普通二维数组和数组指针数组?
How C/C++ compiler distinguish regular two dimensional array and array of pointers to arrays?
常规静态分配数组如下所示,可以使用以下公式访问:
const int N = 3;
const int M = 3;
int a1[N][M] = { {0,1,2}, {3,4,5}, {6,7,8} };
int x = a1[1][2]; // x = 5
int y = *(a1+2+N*1); // y = 5, this is what [] operator is doing in the background
数组是连续的内存区域。在动态数组分配的情况下看起来不同,而是有指向数组的指针数组:
int** a2 = new int*[N];
for (int i = 0; i < N; i++)
a2[i] = new int[M];
//Assignment of values as in previous example
int x = a2[1][2];
int y = *(*(a2+1))+2); // This is what [] operator is doing in the background, it needs to dereference pointers twice
正如我们所看到的,[]运算符完成的操作在典型的连续数组和动态分配数组的情况下是完全不同的。
我的问题如下:
- 我对[]操作的理解是否正确?
- C/C++ 编译器如何区分它应该执行哪个 [] 操作,以及它在何处实现?我可以想象自己在 C++ 中通过重载 [] 运算符实现它,但是 C/C++ 如何处理这个?
- 使用 malloc 而不是 new 在 C 语言中是否可以正常工作?我看不出有什么理由不实际。
Is my understanding of [] operations correct?
int y = *(a1+2+N*1); // y = 5, this is what [] operator is doing in the background
根据定义,将下标运算符转换为相应的间接寻址和指针运算的方式是:
int y = *(*(a1+1)+2)
这与 int**
.
的情况完全相同
How C/C++ compiler can distinguish which [] operation it should perform
编译器使用类型系统。它知道表达式的类型,并且知道下标操作对每种类型意味着什么。
Will it work correctly in C language using malloc instead of new? I don't see any reasons why not actually.
数组的创建方式并不重要。下标运算符对所有指针的工作方式相同。
a1
和 a2
是不同的类型,因此,operator []
的行为将取决于该类型如何定义运算符。在这种情况下,您正在处理符合 C++ 规范的内在编译器行为,但它也可以是 std::unique_ptr<>
或 MyClass
重载 operator[]
对于这个数组的声明
int a1[N][M] = { {0,1,2}, {3,4,5}, {6,7,8} };
这些记录
int x = a1[1][2];
int y = *(a1+2+N*1);
不等价。
第二个不正确。表达式 *(a1+2+N*1)
的类型 int[3]
被隐式转换为用作初始值设定项的 int *
类型的对象。所以整型变量y
是由指针初始化的
运算符 a1[1] 的计算方式类似于 *( a1 + 1 )
。结果是 int[3]
.
类型的 one-dimensional 数组
所以应用第二个下标运算符你会得到 *( *( a1 + 1 ) + 2 )
.
使用two-dimensional数组和动态分配数组的表达式的区别在于two-dimensional数组的指示符在这个表达式(a1 + 1)
中被隐式转换为指针到它的第一个类型 int ( * )[3]
的元素,而指向动态分配的指针数组的指针仍然具有相同的类型 int **
.
在第一种情况下取消对表达式 *(a1 + 1 )
的引用,您将获得类型 int[3]
的左值,该左值又在表达式 *( a1 + 1) + 2
中再次隐式转换为输入 int *
.
在第二种情况下,表达式 *(a1 + 1)
生成类型为 int *
的对象。
在这两种情况下都使用了指针算法。不同之处在于,当您在下标运算符中使用数组时,它们会隐式转换为指向其第一个元素的指针。
当您已经在处理指向数组第一个元素的指针时动态分配数组。
例如,而不是这些分配
int** a2 = new int*[N];
for (int i = 0; i < N; i++)
a2[i] = new int[M];
你可以写
int ( *a2 )[M] = new int[N][M];
每个操作都会导致某种特定类型的结果。每种类型都定义了它可以使用的操作类型。
请注意,数组具有 decay to pointer 到数组元素的能力。所以 some_array + int_value
导致 pointer to element
.
这是公开每个步骤类型的代码:https://godbolt.org/z/jeKWh5WWW
#include <type_traits>
const int N = 3;
const int M = 4;
int a1[N][M] = { {0,1,2,0}, {3,4,5,0}, {6,7,8,0} };
int** a2 = new int*[N];
static_assert(
std::is_same_v<decltype(a1[0][0]), int&>,
"value type is reference to int");
static_assert(
std::is_same_v<decltype(a1[0]), int(&)[M]>,
"row type is reference to int aray");
static_assert(
std::is_same_v<decltype(a1 + 1), int(*)[M]>,
"advanced pointer is pointer to array of ints");
static_assert(
!std::is_same_v<decltype(a1[0]), int*&>,
"row type is reference to int pointer");
static_assert(
std::is_same_v<decltype(a2[0][0]), int&>,
"value type is reference to int");
static_assert(
!std::is_same_v<decltype(a2[0]), int(&)[M]>,
"row type is not reference to int aray");
static_assert(
std::is_same_v<decltype(a2 + 1), int**>,
"advanced pointer is pointer to pointer to int");
static_assert(
std::is_same_v<decltype(a2[0]), int*&>,
"row type is reference to int pointer");
我认为这是其他答案的很好的附录。
How C/C++ compiler can distinguish which [] operation it should perform, and where it's implemented?
built-in []
运算符(即不是 user-defined 重载)总是做一件事:它添加它的两个操作数并取消引用结果。 <i>E1</i>[<i>E2</i>]
定义为 (*((<i>E1</i>)+(<i>E2</i>)))
。这是它的工作原理:
- 如果
<i>E1</i>
或<i>E2</i>
是一个数组,它会自动转换为指向其第一个元素的指针。这不是 []
运算符 本身 的一部分;它是 C 和 C++ 语言的 built-in 部分。在 C 中,具体规则是,每当数组在表达式中用作 sizeof
的操作数、一元 &
的操作数或用于初始化数组的字符串文字时,它被转换为指向其第一个元素的指针。
- 因此,无论代码是用指针还是数组编写,
[]
总是有一个指针操作数。你可以写一个数组,但是 []
总是接收到一个指针。
+
运算符通过将指针调整给定元素数来将整数添加到指针:给定一个指向数组元素 j 的指针和一个整数 k 添加到它,它会生成指向数组元素 j+k 的指针。
- 根据指向元素的指针,
*
运算符为引用的元素生成左值。
自动数组转换 +
和 *
的组合意味着 A[i]
为数组 A
的元素 i
生成一个左值.
下面是表达式 A[i][j]
的工作原理,其中 A
是声明为 SomeType A[m][n]
:
的数组
- 在
A[i][j]
中,A
是一个包含n
个元素的m
个数组的数组。它会自动转换为指向其第一个元素(索引为 0 的元素)的指针。
- 然后
A[i]
为该数组的元素 i
生成一个左值。也就是说,A[i]
的结果是一个数组;它是 n
SomeType
个对象的数组。
- 由于
A[i]
的结果是一个数组,它会自动转换为指向其第一个元素的指针。
- 然后
A[i][j]
为该数组的元素 j
生成左值。
由于指针算法以 pointed-to-type 为单位进行运算,因此它包括元素大小的缩放。这就是 A[i]
的计算按 n
元素的子数组的大小缩放的原因。
Will it work correctly in C language using malloc instead of new? I don't see any reasons why not actually.
当然可以,如果操作正确的话。
常规静态分配数组如下所示,可以使用以下公式访问:
const int N = 3;
const int M = 3;
int a1[N][M] = { {0,1,2}, {3,4,5}, {6,7,8} };
int x = a1[1][2]; // x = 5
int y = *(a1+2+N*1); // y = 5, this is what [] operator is doing in the background
数组是连续的内存区域。在动态数组分配的情况下看起来不同,而是有指向数组的指针数组:
int** a2 = new int*[N];
for (int i = 0; i < N; i++)
a2[i] = new int[M];
//Assignment of values as in previous example
int x = a2[1][2];
int y = *(*(a2+1))+2); // This is what [] operator is doing in the background, it needs to dereference pointers twice
正如我们所看到的,[]运算符完成的操作在典型的连续数组和动态分配数组的情况下是完全不同的。 我的问题如下:
- 我对[]操作的理解是否正确?
- C/C++ 编译器如何区分它应该执行哪个 [] 操作,以及它在何处实现?我可以想象自己在 C++ 中通过重载 [] 运算符实现它,但是 C/C++ 如何处理这个?
- 使用 malloc 而不是 new 在 C 语言中是否可以正常工作?我看不出有什么理由不实际。
Is my understanding of [] operations correct?
int y = *(a1+2+N*1); // y = 5, this is what [] operator is doing in the background
根据定义,将下标运算符转换为相应的间接寻址和指针运算的方式是:
int y = *(*(a1+1)+2)
这与 int**
.
How C/C++ compiler can distinguish which [] operation it should perform
编译器使用类型系统。它知道表达式的类型,并且知道下标操作对每种类型意味着什么。
Will it work correctly in C language using malloc instead of new? I don't see any reasons why not actually.
数组的创建方式并不重要。下标运算符对所有指针的工作方式相同。
a1
和 a2
是不同的类型,因此,operator []
的行为将取决于该类型如何定义运算符。在这种情况下,您正在处理符合 C++ 规范的内在编译器行为,但它也可以是 std::unique_ptr<>
或 MyClass
重载 operator[]
对于这个数组的声明
int a1[N][M] = { {0,1,2}, {3,4,5}, {6,7,8} };
这些记录
int x = a1[1][2];
int y = *(a1+2+N*1);
不等价。
第二个不正确。表达式 *(a1+2+N*1)
的类型 int[3]
被隐式转换为用作初始值设定项的 int *
类型的对象。所以整型变量y
是由指针初始化的
运算符 a1[1] 的计算方式类似于 *( a1 + 1 )
。结果是 int[3]
.
所以应用第二个下标运算符你会得到 *( *( a1 + 1 ) + 2 )
.
使用two-dimensional数组和动态分配数组的表达式的区别在于two-dimensional数组的指示符在这个表达式(a1 + 1)
中被隐式转换为指针到它的第一个类型 int ( * )[3]
的元素,而指向动态分配的指针数组的指针仍然具有相同的类型 int **
.
在第一种情况下取消对表达式 *(a1 + 1 )
的引用,您将获得类型 int[3]
的左值,该左值又在表达式 *( a1 + 1) + 2
中再次隐式转换为输入 int *
.
在第二种情况下,表达式 *(a1 + 1)
生成类型为 int *
的对象。
在这两种情况下都使用了指针算法。不同之处在于,当您在下标运算符中使用数组时,它们会隐式转换为指向其第一个元素的指针。
当您已经在处理指向数组第一个元素的指针时动态分配数组。
例如,而不是这些分配
int** a2 = new int*[N];
for (int i = 0; i < N; i++)
a2[i] = new int[M];
你可以写
int ( *a2 )[M] = new int[N][M];
每个操作都会导致某种特定类型的结果。每种类型都定义了它可以使用的操作类型。
请注意,数组具有 decay to pointer 到数组元素的能力。所以 some_array + int_value
导致 pointer to element
.
这是公开每个步骤类型的代码:https://godbolt.org/z/jeKWh5WWW
#include <type_traits>
const int N = 3;
const int M = 4;
int a1[N][M] = { {0,1,2,0}, {3,4,5,0}, {6,7,8,0} };
int** a2 = new int*[N];
static_assert(
std::is_same_v<decltype(a1[0][0]), int&>,
"value type is reference to int");
static_assert(
std::is_same_v<decltype(a1[0]), int(&)[M]>,
"row type is reference to int aray");
static_assert(
std::is_same_v<decltype(a1 + 1), int(*)[M]>,
"advanced pointer is pointer to array of ints");
static_assert(
!std::is_same_v<decltype(a1[0]), int*&>,
"row type is reference to int pointer");
static_assert(
std::is_same_v<decltype(a2[0][0]), int&>,
"value type is reference to int");
static_assert(
!std::is_same_v<decltype(a2[0]), int(&)[M]>,
"row type is not reference to int aray");
static_assert(
std::is_same_v<decltype(a2 + 1), int**>,
"advanced pointer is pointer to pointer to int");
static_assert(
std::is_same_v<decltype(a2[0]), int*&>,
"row type is reference to int pointer");
我认为这是其他答案的很好的附录。
How C/C++ compiler can distinguish which [] operation it should perform, and where it's implemented?
built-in []
运算符(即不是 user-defined 重载)总是做一件事:它添加它的两个操作数并取消引用结果。 <i>E1</i>[<i>E2</i>]
定义为 (*((<i>E1</i>)+(<i>E2</i>)))
。这是它的工作原理:
- 如果
<i>E1</i>
或<i>E2</i>
是一个数组,它会自动转换为指向其第一个元素的指针。这不是[]
运算符 本身 的一部分;它是 C 和 C++ 语言的 built-in 部分。在 C 中,具体规则是,每当数组在表达式中用作sizeof
的操作数、一元&
的操作数或用于初始化数组的字符串文字时,它被转换为指向其第一个元素的指针。 - 因此,无论代码是用指针还是数组编写,
[]
总是有一个指针操作数。你可以写一个数组,但是[]
总是接收到一个指针。 +
运算符通过将指针调整给定元素数来将整数添加到指针:给定一个指向数组元素 j 的指针和一个整数 k 添加到它,它会生成指向数组元素 j+k 的指针。- 根据指向元素的指针,
*
运算符为引用的元素生成左值。
自动数组转换 +
和 *
的组合意味着 A[i]
为数组 A
的元素 i
生成一个左值.
下面是表达式 A[i][j]
的工作原理,其中 A
是声明为 SomeType A[m][n]
:
- 在
A[i][j]
中,A
是一个包含n
个元素的m
个数组的数组。它会自动转换为指向其第一个元素(索引为 0 的元素)的指针。 - 然后
A[i]
为该数组的元素i
生成一个左值。也就是说,A[i]
的结果是一个数组;它是n
SomeType
个对象的数组。 - 由于
A[i]
的结果是一个数组,它会自动转换为指向其第一个元素的指针。 - 然后
A[i][j]
为该数组的元素j
生成左值。
由于指针算法以 pointed-to-type 为单位进行运算,因此它包括元素大小的缩放。这就是 A[i]
的计算按 n
元素的子数组的大小缩放的原因。
Will it work correctly in C language using malloc instead of new? I don't see any reasons why not actually.
当然可以,如果操作正确的话。