难以理解高级指针算术语法
trouble understanding advanced pointer arithmatic syntax
假设我们得到下一个设置:
int (*p)[9];
它是一个常规指针,还是某种指向 9*sizeof(int)
大内存块的特殊指针?
如何引用这样的语法?
假设我有一个给定的矩阵:
int mat[200][9];
int (*p)[9] = mat;
指针算法如何使用它,例如,如果我们要增加 p
如何转换为这种类型?
下一个代码的输出是 2 5
,我认为它对上面显示的特殊语法有一个 link。有人可以向我解释为什么不是输出 2 1
?
int main()
{
int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
printf("%d %d", *(a+1), *(ptr-1));
return 0;
}
你应该阅读
int (*p)[9];
作为指向 int [9]
(9 个整数)数组的指针。它是一个常规指针,类型为 int [9]
.
指针运算与其他指针的情况一样,基于指针的大小,即 sizeof(int[9])
。
也就是说,对于另一个问题,如果有疑问,请检查数据类型!
int main()
{
int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
printf("%d %d", *(a+1), *(ptr-1));
return 0;
}
&a
是 if 类型 int (*)[5]
,因此指针算法将遵守这一点。 &a+1
指向整个数组之后的第一个元素。
因此,自然地,ptr
指向最后一个元素地址之后的一个。所以,通过说 *(ptr-1)
会得到最后一个元素的值,即 5
.
认为 &a 是指向具有 sizeof(int)*5 的“项目”的指针,所以当您执行加 1 时,它会将您发送到数组之后的下一个元素,该元素将以 20 的偏移量前进bytes 所以你做 -1 然后将它转换为 int 以获得数组的最后一个变量。
- is it a regular pointer, or some kind of a special pointer to a block of memory that is
9*sizeof(int)
big?
它是一个指向类型为int [9]
的数组的指针。这确实是 9*sizeof(int)
字节大。
- how do I refer to such syntax?
它们被称为数组指针或指向数组的指针。您可以将其视为“指向整个数组的指针”,不要与“指向数组中第一个元素的指针”混淆。
- lets say I have a given matrix:
int mat[200][9];
int (*p)[9] = mat;
how would pointer arithmetic work with it, for example, if we were to increase p
就像任何指针类型一样,p
被指定为指向 int [200][9]
数组中的第一个元素。第一个元素是一个数组 int [9]
并且指向这样一个数组的指针是 int(*)[9]
.
- how do I cast to such type?
与任何类型一样,尽管它们与指向不同大小或类型的数组的指针不兼容,也不与 int*
.
兼容
- the next code's output is
2 5
and I think that it has a link to the special syntax I've shown above. can someone explain to me why isn't the output 2 1
?
通常,无论何时在表达式中使用数组都会衰减为指向第一个元素的指针。这就是这里发生的事情 *(a+1)
,a
衰减为 int*
并且指针算术 + 1 让我们指向第二个元素。
此数组衰减规则的极少数例外之一是 &
地址运算符的操作数。所以在表达式 &a
中,数组 a
不会 衰减。相反,我们以指向数组 int (*)[5]
.
的指针的形式获取它的地址
&a + 1
然后在指向数组的指针上给出指针算术。与任何指针算法一样,1
表示一个指向项的大小,在本例中为 sizeof(int [5])
。所以我们最终指向原始数组之一的越界 - 这很好,只要我们不取消引用指针,我们就可以指向那里。
(int*)
转换是非常可疑和不好的做法,因为 (int*)
与 int(*)[5]
不兼容。但是,只要我们不通过错误的类型取消引用数据,C 就允许我们进行野蛮和可疑的转换。但是 *(ptr-1)
然后转到数组的最后一项并取消引用它。这滥用了 C pointer/type 系统中的一条特殊规则,该规则允许我们通过不同的指针类型访问(“左值访问”)实际对象,只要存储在该内存位置的实际上是用于的类型访问权限。由于实际存储在物理内存地址上的 5
是一个 int
,它可以工作。
指针声明的基本规则如下:
T *p; // p is a pointer to T
T *ap[N]; // ap is an array of pointers to T
T *fp(); // fp is a function returning a pointer to T
T (*pa)[N]; // pa is a pointer to an array of T
T (*pf)(); // pf is a function returning a pointer to T
T * const p; // p is a const pointer to T - *p is writable, but p is not
const T *p; // p is a non-const pointer to const T - p is writable, but *p is not
T const *p; // same as above
const T * const p; // p is a const pointer to const T - neither p nor *p are writable
T const * const p; // same as above
要阅读复杂的声明,请找到最左边的标识符并按照上述规则找出路,将它们递归地应用于任何函数参数。例如,C 标准库中 signal
函数的声明是这样分解的:
signal -- signal
signal( ) -- is a function taking
signal( sig ) -- parameter sig
signal(int sig ) -- is an int
signal(int sig, func ) -- parameter func
signal(int sig, (*func) ) -- is a pointer to
signal(int sig, (*func)( )) -- a function taking
signal(int sig, (*func)( )) -- unnamed parameter
signal(int sig, (*func)(int)) -- is an int
signal(int sig, void (*func)(int)) -- returning void
(*signal(int sig, void (*func)(int))) -- returning a pointer to
(*signal(int sig, void (*func)(int)))( ) -- a function taking
(*signal(int sig, void (*func)(int)))( ) -- unnamed parameter
(*signal(int sig, void (*func)(int)))(int) -- is an int
void (*signal(int sig, void (*func)(int)))(int); -- returning void
您可以通过替换来构建更复杂的指针类型。例如,如果你想声明一个 return 指向数组的指针的函数,你可以将它构建为
T a [N]; // a is an array of T
|
+---+----+
| |
T (* pa )[N]; // pa is a pointer to an array of T
|
++--+
| |
T (* fpa() )[N]; // fpa is a function returning a pointer to an array of T
如果你想要一个指向return指向T
的函数的指针数组,那么你可以将它构建为
T * p ; // p is a pointer to T
|
+----------+
| |
T * fp (); // fp is a function returning pointer to T
|
+---+-----+
| |
T * (* pf ) (); // pf is a pointer to a function returning pointer to T
|
++---+
| |
T * (* apf[N] ) (); // apf is an array of pointers to functions returning pointer to T
指针运算是根据对象而不是字节来完成的。如果 p
存储类型为 T
的对象的地址,则 p+1
生成该类型的下一个对象的地址。如果 pa
存储 T
的 N 元素数组的地址,则 pa+1
产生 T
的下一个 N 元素数组的地址。
在你的代码中
int main()
{
int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
printf("%d %d", *(a+1), *(ptr-1));
return 0;
}
表达式 &a + 1
产生 a
之后 int
的下一个 5 元素数组的地址。此地址值被强制转换为 int *
,因此它被视为 a
的最后一个元素之后的第一个 int
的地址。图表可能会有所帮助:
int[5] int int *
------ --- -----
+---+ +---+ +---+
a: | | a[0]: | 1 | ptr: | |
+ - + +---+ +---+
| | a[1]: | 2 | |
+ - + +---+ |
| | a[2]: | 3 | |
+ - + +---+ |
| | a[3]: | 4 | |
+ - + +---+ |
| | a[4]: | 5 | |
+---+ +---+ |
a + 1: | | | ? | <--------+
+ - + +---+
| | | ? |
+ - + +---+
... ...
表达式a
和a+1
的类型为int [5]
,每个a[i]
的类型为int
, ptr
的类型为 int *
.
因此,ptr-1
产生 a[4]
的地址。
请记住,C 声明语法反映了表达式语法 - 如果您有一个指向名为 pa
的数组的指针,并且您想要访问指向数组的第 i
个元素,您必须取消引用 pa
然后下标结果(假设 T
在这个例子中是 int
):
printf( "indexed value = %d\n", (*pa)[i] );
在表达式和声明中,后缀运算符 []
和 ()
的优先级高于一元运算符 *
,因此 *pa[i]
将被解析为 *(pa[i])
,在这种情况下这不是我们想要的。我们需要明确地将 *
运算符与 pa
分组,以便我们取消引用正确的东西。
表达式(*pa)[i]
的类型是int
,所以pa
的声明写成
int (*pa)[N];
因此,声明的形状告诉您代码中表达式的形状。从那里它只是记住各种子表达式的类型:
Expression Type
---------- ----
pa int (*)[N];
*pa int [N];
(*pa)[i] int
数组下标操作a[i]
定义为*(a + i)
——给定一个起始地址a
,偏移量i
个元素(不是字节——记住指针算法的讨论上面)从该地址并取消引用结果。这意味着如果 p
是一个指针,那么
*p == *(p + 0) == p[0]
意味着您可以将指针表达式作为数组下标1.
鉴于您的声明
int mat[200][9];
int (*p)[9] = mat;
然后你可以索引到 p
就像你索引到 mat
:
(*p)[j] == (*(p + 0))[j] == p[0][j]
因此 p[i][j]
产生与 mat[i][j]
相同的值。
- 数组不是指针,但数组表达式会根据需要转换为指针表达式。
假设我们得到下一个设置:
int (*p)[9];
它是一个常规指针,还是某种指向
9*sizeof(int)
大内存块的特殊指针?如何引用这样的语法?
假设我有一个给定的矩阵:
int mat[200][9];
int (*p)[9] = mat;
指针算法如何使用它,例如,如果我们要增加 p
如何转换为这种类型?
下一个代码的输出是
2 5
,我认为它对上面显示的特殊语法有一个 link。有人可以向我解释为什么不是输出2 1
?
int main()
{
int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
printf("%d %d", *(a+1), *(ptr-1));
return 0;
}
你应该阅读
int (*p)[9];
作为指向 int [9]
(9 个整数)数组的指针。它是一个常规指针,类型为 int [9]
.
指针运算与其他指针的情况一样,基于指针的大小,即 sizeof(int[9])
。
也就是说,对于另一个问题,如果有疑问,请检查数据类型!
int main()
{
int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
printf("%d %d", *(a+1), *(ptr-1));
return 0;
}
&a
是 if 类型 int (*)[5]
,因此指针算法将遵守这一点。 &a+1
指向整个数组之后的第一个元素。
因此,自然地,ptr
指向最后一个元素地址之后的一个。所以,通过说 *(ptr-1)
会得到最后一个元素的值,即 5
.
认为 &a 是指向具有 sizeof(int)*5 的“项目”的指针,所以当您执行加 1 时,它会将您发送到数组之后的下一个元素,该元素将以 20 的偏移量前进bytes 所以你做 -1 然后将它转换为 int 以获得数组的最后一个变量。
- is it a regular pointer, or some kind of a special pointer to a block of memory that is
9*sizeof(int)
big?
它是一个指向类型为int [9]
的数组的指针。这确实是 9*sizeof(int)
字节大。
- how do I refer to such syntax?
它们被称为数组指针或指向数组的指针。您可以将其视为“指向整个数组的指针”,不要与“指向数组中第一个元素的指针”混淆。
- lets say I have a given matrix:
int mat[200][9]; int (*p)[9] = mat;
how would pointer arithmetic work with it, for example, if we were to increase p
就像任何指针类型一样,p
被指定为指向 int [200][9]
数组中的第一个元素。第一个元素是一个数组 int [9]
并且指向这样一个数组的指针是 int(*)[9]
.
- how do I cast to such type?
与任何类型一样,尽管它们与指向不同大小或类型的数组的指针不兼容,也不与 int*
.
- the next code's output is
2 5
and I think that it has a link to the special syntax I've shown above. can someone explain to me why isn't the output2 1
?
通常,无论何时在表达式中使用数组都会衰减为指向第一个元素的指针。这就是这里发生的事情 *(a+1)
,a
衰减为 int*
并且指针算术 + 1 让我们指向第二个元素。
此数组衰减规则的极少数例外之一是 &
地址运算符的操作数。所以在表达式 &a
中,数组 a
不会 衰减。相反,我们以指向数组 int (*)[5]
.
&a + 1
然后在指向数组的指针上给出指针算术。与任何指针算法一样,1
表示一个指向项的大小,在本例中为 sizeof(int [5])
。所以我们最终指向原始数组之一的越界 - 这很好,只要我们不取消引用指针,我们就可以指向那里。
(int*)
转换是非常可疑和不好的做法,因为 (int*)
与 int(*)[5]
不兼容。但是,只要我们不通过错误的类型取消引用数据,C 就允许我们进行野蛮和可疑的转换。但是 *(ptr-1)
然后转到数组的最后一项并取消引用它。这滥用了 C pointer/type 系统中的一条特殊规则,该规则允许我们通过不同的指针类型访问(“左值访问”)实际对象,只要存储在该内存位置的实际上是用于的类型访问权限。由于实际存储在物理内存地址上的 5
是一个 int
,它可以工作。
指针声明的基本规则如下:
T *p; // p is a pointer to T
T *ap[N]; // ap is an array of pointers to T
T *fp(); // fp is a function returning a pointer to T
T (*pa)[N]; // pa is a pointer to an array of T
T (*pf)(); // pf is a function returning a pointer to T
T * const p; // p is a const pointer to T - *p is writable, but p is not
const T *p; // p is a non-const pointer to const T - p is writable, but *p is not
T const *p; // same as above
const T * const p; // p is a const pointer to const T - neither p nor *p are writable
T const * const p; // same as above
要阅读复杂的声明,请找到最左边的标识符并按照上述规则找出路,将它们递归地应用于任何函数参数。例如,C 标准库中 signal
函数的声明是这样分解的:
signal -- signal
signal( ) -- is a function taking
signal( sig ) -- parameter sig
signal(int sig ) -- is an int
signal(int sig, func ) -- parameter func
signal(int sig, (*func) ) -- is a pointer to
signal(int sig, (*func)( )) -- a function taking
signal(int sig, (*func)( )) -- unnamed parameter
signal(int sig, (*func)(int)) -- is an int
signal(int sig, void (*func)(int)) -- returning void
(*signal(int sig, void (*func)(int))) -- returning a pointer to
(*signal(int sig, void (*func)(int)))( ) -- a function taking
(*signal(int sig, void (*func)(int)))( ) -- unnamed parameter
(*signal(int sig, void (*func)(int)))(int) -- is an int
void (*signal(int sig, void (*func)(int)))(int); -- returning void
您可以通过替换来构建更复杂的指针类型。例如,如果你想声明一个 return 指向数组的指针的函数,你可以将它构建为
T a [N]; // a is an array of T
|
+---+----+
| |
T (* pa )[N]; // pa is a pointer to an array of T
|
++--+
| |
T (* fpa() )[N]; // fpa is a function returning a pointer to an array of T
如果你想要一个指向return指向T
的函数的指针数组,那么你可以将它构建为
T * p ; // p is a pointer to T
|
+----------+
| |
T * fp (); // fp is a function returning pointer to T
|
+---+-----+
| |
T * (* pf ) (); // pf is a pointer to a function returning pointer to T
|
++---+
| |
T * (* apf[N] ) (); // apf is an array of pointers to functions returning pointer to T
指针运算是根据对象而不是字节来完成的。如果 p
存储类型为 T
的对象的地址,则 p+1
生成该类型的下一个对象的地址。如果 pa
存储 T
的 N 元素数组的地址,则 pa+1
产生 T
的下一个 N 元素数组的地址。
在你的代码中
int main()
{
int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
printf("%d %d", *(a+1), *(ptr-1));
return 0;
}
表达式 &a + 1
产生 a
之后 int
的下一个 5 元素数组的地址。此地址值被强制转换为 int *
,因此它被视为 a
的最后一个元素之后的第一个 int
的地址。图表可能会有所帮助:
int[5] int int *
------ --- -----
+---+ +---+ +---+
a: | | a[0]: | 1 | ptr: | |
+ - + +---+ +---+
| | a[1]: | 2 | |
+ - + +---+ |
| | a[2]: | 3 | |
+ - + +---+ |
| | a[3]: | 4 | |
+ - + +---+ |
| | a[4]: | 5 | |
+---+ +---+ |
a + 1: | | | ? | <--------+
+ - + +---+
| | | ? |
+ - + +---+
... ...
表达式a
和a+1
的类型为int [5]
,每个a[i]
的类型为int
, ptr
的类型为 int *
.
因此,ptr-1
产生 a[4]
的地址。
请记住,C 声明语法反映了表达式语法 - 如果您有一个指向名为 pa
的数组的指针,并且您想要访问指向数组的第 i
个元素,您必须取消引用 pa
然后下标结果(假设 T
在这个例子中是 int
):
printf( "indexed value = %d\n", (*pa)[i] );
在表达式和声明中,后缀运算符 []
和 ()
的优先级高于一元运算符 *
,因此 *pa[i]
将被解析为 *(pa[i])
,在这种情况下这不是我们想要的。我们需要明确地将 *
运算符与 pa
分组,以便我们取消引用正确的东西。
表达式(*pa)[i]
的类型是int
,所以pa
的声明写成
int (*pa)[N];
因此,声明的形状告诉您代码中表达式的形状。从那里它只是记住各种子表达式的类型:
Expression Type
---------- ----
pa int (*)[N];
*pa int [N];
(*pa)[i] int
数组下标操作a[i]
定义为*(a + i)
——给定一个起始地址a
,偏移量i
个元素(不是字节——记住指针算法的讨论上面)从该地址并取消引用结果。这意味着如果 p
是一个指针,那么
*p == *(p + 0) == p[0]
意味着您可以将指针表达式作为数组下标1.
鉴于您的声明
int mat[200][9];
int (*p)[9] = mat;
然后你可以索引到 p
就像你索引到 mat
:
(*p)[j] == (*(p + 0))[j] == p[0][j]
因此 p[i][j]
产生与 mat[i][j]
相同的值。
- 数组不是指针,但数组表达式会根据需要转换为指针表达式。