p 是一个指向结构的指针,所有这些代码片段都做了什么?
p is a pointer to a structure, what do all these code snippets do?
++p->i
p++->i
*p->i
*p->i++
(*p->i)++
*p++->i
上面这些说法我看不懂,我写了一个小测试程序来理解它们。
#include <stdio.h>
struct my_structure {
int i;
};
void main() {
struct my_structure variable = {20};
struct my_structure *p = &variable;
printf("NAME: %d\n", ++p->i);
printf("NUMBER: %d\n", p++->i);
printf("RANK: %d", *p->i++);
printf("name: %d\n", *p->i++);
printf("number: %d\n", (*p->i)++);
printf("rank: %d", *p++->i);
}
这是我评论最后四个 print
语句后得到的输出:
NAME: 21
NUMBER: 21
取消注释代码并编译后我得到:
test.c: In function 'main':
test.c:14:24: error: invalid type argument of unary '*' (have 'int')
printf("RANK: %d", *p->i++);
^~~~~~~
test.c:15:26: error: invalid type argument of unary '*' (have 'int')
printf("name: %d\n", *p->i++);
^~~~~~~
test.c:16:29: error: invalid type argument of unary '*' (have 'int')
printf("number: %d\n", (*p->i)++);
^~~~~
test.c:17:24: error: invalid type argument of unary '*' (have 'int')
printf("rank: %d", *p++->i);
查看在线评论:
struct my_structure {
int i;
};
void main() {
struct my_structure variable = {20};
struct my_structure *p = &variable;
printf("NAME: %d\n", ++p->i); //pre-increments i by 1 (prints 21)
printf("NUMBER: %d\n", (p++)->i); //changes location pointed to. (meaningless, ub, printed 3 for me)
//printf("RANK: %d", *p->i++); // error, (will not be included in build)
//printf("name: %d\n", *p->i++); // error, (will not be included in build)
//printf("number: %d\n", (*p->i)++);// error, (will not be included in build)
//printf("rank: %d", *p++->i); // error, (will not be included in build)
getchar();
}
虽然第一条和第二条语句在语法上都是正确的(即编译、构建和 运行 没有任何问题的迹象)只有第一条是有意义的,尽管我不知道是为了什么目的。鉴于声明:struct my_structure variable = {20};
,只定义了一个内存位置,指针的第二个增量将其位置超出您的定义,并指向一个未知值。在我的 运行 中,它指向 3
,但可能包含任何内容。并且,由于它不被拥有,调用 undefined behavior。这就是为什么 运行 在不同的 PC 上,甚至在不同时间在同一台 PC 上运行生成的可执行文件很可能会产生不同的结果。
让我们按运算符分解。
x->y
:这会取消引用指向结构的指针 (x
),然后访问指示的成员变量 (y
)。仅当 x
是指向结构的指针时才有意义。也等同于(*x).y
.
++x
:前增量x
。这会将 x
的值增加 1
,然后 returns x
的新值。它的优先级低于 ->
运算符,因此 ++p->i
将像上面那样获取 i
,然后递增它。
x++
:Post-增量 x
。这会将 x
的值增加 1
,然后 returns x
的旧值。但是,这一次,运算符具有相同的优先级并且从左到右执行。然后这将递增 p
,然后取消引用 p
曾经访问 i
的位置。这将为您提供 i
的值,但现在 p
指向 uninitialized/unknown 内存(除非 p
在数组中,在这种情况下它现在指向下一个该数组的成员)。
*x
:取消引用 x
就像我在上面 ->
下提到的,但在这个例子中我们现在做了两次,结果是等效的 ((**p).i)++)
。但是,由于 p
指向结构而不是指向结构的指针,因此这是一个编译器错误。这也适用于下一个表达式,因为它只是明确地说明了编译器已经符合的相同操作顺序。
综上所述,我们来到最后一个,顺序为:
- 取消引用
p
。 (到目前为止还不错)
- 增加那个结果。但是没有为结构定义增量,所以编译器错误。
- 取消引用该结果。同样,不再是指针,所以我们不能取消引用。编译器错误。
- 从该结果中访问成员
i
。
你可以看到我这里使用的运算符优先级规则:https://en.cppreference.com/w/c/language/operator_precedence
可以使代码正常工作(编译,运行 不会崩溃,并产生连贯、可解释的答案),但您需要与所选结构不同的结构。例如:
#include <stdio.h>
struct my_structure
{
char *i;
};
#define EXPR(x) #x, x
int main(void)
{
char strings[][10] = { { "Winter" }, { "Bash" }, { "Is" }, { "Here" } };
struct my_structure variables[] = { { strings[0] }, { strings[1] }, { strings[2] }, { strings[3] } };
struct my_structure *p = variables;
printf("%10s: %s\n", EXPR(++p->i));
printf("%10s: %s\n", EXPR(p++->i));
printf("%10s: %d\n", EXPR(*p->i++));
printf("%10s: %d\n", EXPR(*p->i++));
printf("%10s: %d\n", EXPR((*p->i)++));
printf("%10s: %d\n", EXPR(*p++->i));
return 0;
}
生成输出:
++p->i: inter
p++->i: inter
*p->i++: 66
*p->i++: 97
(*p->i)++: 115
*p++->i: 116
宏 EXPR
只是让我不重复代码中的表达式,而且还可以在调用 printf()
.
时获取字符串形式和值
当事情开始时,p->i
指向字符串 "Winter"
。
++p->i: inter
— 预先递增指针 p->i
,使其指向 Winter
的 i
。
p++->i: inter
— post-递增指针p
(指向"Bash"
),但结果与之前相同,因为递增在之后生效p->i
被使用。
*p->i++: 66
— post-增加指针 p->i
(因此它指向 Bash
中的 a
)并报告之前指向的值增量,即 B
(ASCII 中的 66)。
*p->i++: 97
— 相同的表达式,但指针指向递增前的 a
(97) 和递增后的 s
。
(*p->i)++: 115
- post-增加 p->i
指向的字母,报告 s
但将其更改为 t
.
*p++->i: 116
— post-增量 p
因此它指向字符串 "In",同时报告 t
(116).
这是一个具有更多工具的替代方案:
#include <stdio.h>
struct my_structure
{
char *i;
};
#define EXPR(x) #x, x
int main(void)
{
char strings[][10] = { { "Winter" }, { "Bash" }, { "Is" }, { "Here" } };
struct my_structure variables[] = { { strings[0] }, { strings[1] }, { strings[2] }, { strings[3] } };
struct my_structure *p = variables;
for (size_t i = 0; i < sizeof(strings)/sizeof(strings[0]); i++)
printf("strings[%zu] = [%s]\n", i, strings[i]);
for (size_t i = 0; i < sizeof(variables)/sizeof(variables[0]); i++)
printf("variables[%zu].i = [%s]\n", i, variables[i].i);
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %s\n", EXPR(++p->i));
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %s\n", EXPR(p++->i));
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %d\n", EXPR(*p->i++));
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %d\n", EXPR(*p->i++));
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %d\n", EXPR((*p->i)++));
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %d\n", EXPR(*p++->i));
printf("%10s: %s\n", EXPR(p->i));
for (size_t i = 0; i < sizeof(strings)/sizeof(strings[0]); i++)
printf("strings[%zu] = [%s]\n", i, strings[i]);
for (size_t i = 0; i < sizeof(variables)/sizeof(variables[0]); i++)
printf("variables[%zu].i = [%s]\n", i, variables[i].i);
return 0;
}
及其输出:
strings[0] = [Winter]
strings[1] = [Bash]
strings[2] = [Is]
strings[3] = [Here]
variables[0].i = [Winter]
variables[1].i = [Bash]
variables[2].i = [Is]
variables[3].i = [Here]
p->i: Winter
++p->i: inter
p->i: inter
p++->i: inter
p->i: Bash
*p->i++: 66
p->i: ash
*p->i++: 97
p->i: sh
(*p->i)++: 115
p->i: th
*p++->i: 116
p->i: Is
strings[0] = [Winter]
strings[1] = [Bath]
strings[2] = [Is]
strings[3] = [Here]
variables[0].i = [inter]
variables[1].i = [th]
variables[2].i = [Is]
variables[3].i = [Here]
尝试使用此方案的变体(例如,额外的括号)以确保您了解正在发生的事情。
首先,一些提醒:
p++
的计算结果为 p
的当前值,并且作为 副作用 将 p
加 1。如果 p
是一个指针,它被设置为指向序列中的下一个对象。
++p
的计算结果为 p + 1
的当前值,并且作为副作用将 p
加 1。同样,如果 p
是一个指针,它被设置为指向序列中的下一个对象。
++
的后缀形式和->
运算符的优先级相同,比++
和[=33=的一元(前缀)形式的优先级高].因此,像 ++p->i
这样的表达式被解析为 ++(p->i)
,p->i++
被解析为 (p->i)++
,*p->i
被解析为 *(p->i)
,等等
除此之外...
表达式
++p->i
被解析为
++(p->i)
并评估为 p->i
的当前值加 1,并且作为 副作用 更新 p->i
.
表达式
p++->i
被解析为
(p++)->i
并评估当前 p->i
,然后更新 p
以指向序列中的下一个 struct
对象。
表达式
*p->i
被解析为
*(p->i)
因为 ->
的优先级高于一元 *
。一元操作数 *
必须是指针类型,但是 p->i
是一个整数,所以编译器会在这个表达式上 yak。
表达式
*p->i++
被解析为
*((p->i)++)
同样,由于 *
的操作数不是指针类型,因此编译器将对该表达式进行检查。
表达式
(*p->i)++
被解析为
(*(p->i))++
同样,p->i
没有指针类型,所以编译器会出问题。
表达式
*p++->i
被解析为
*((p++)->i)
而且,再一次,更多的 yakkage。
++p->i
p++->i
*p->i
*p->i++
(*p->i)++
*p++->i
上面这些说法我看不懂,我写了一个小测试程序来理解它们。
#include <stdio.h>
struct my_structure {
int i;
};
void main() {
struct my_structure variable = {20};
struct my_structure *p = &variable;
printf("NAME: %d\n", ++p->i);
printf("NUMBER: %d\n", p++->i);
printf("RANK: %d", *p->i++);
printf("name: %d\n", *p->i++);
printf("number: %d\n", (*p->i)++);
printf("rank: %d", *p++->i);
}
这是我评论最后四个 print
语句后得到的输出:
NAME: 21
NUMBER: 21
取消注释代码并编译后我得到:
test.c: In function 'main':
test.c:14:24: error: invalid type argument of unary '*' (have 'int')
printf("RANK: %d", *p->i++);
^~~~~~~
test.c:15:26: error: invalid type argument of unary '*' (have 'int')
printf("name: %d\n", *p->i++);
^~~~~~~
test.c:16:29: error: invalid type argument of unary '*' (have 'int')
printf("number: %d\n", (*p->i)++);
^~~~~
test.c:17:24: error: invalid type argument of unary '*' (have 'int')
printf("rank: %d", *p++->i);
查看在线评论:
struct my_structure {
int i;
};
void main() {
struct my_structure variable = {20};
struct my_structure *p = &variable;
printf("NAME: %d\n", ++p->i); //pre-increments i by 1 (prints 21)
printf("NUMBER: %d\n", (p++)->i); //changes location pointed to. (meaningless, ub, printed 3 for me)
//printf("RANK: %d", *p->i++); // error, (will not be included in build)
//printf("name: %d\n", *p->i++); // error, (will not be included in build)
//printf("number: %d\n", (*p->i)++);// error, (will not be included in build)
//printf("rank: %d", *p++->i); // error, (will not be included in build)
getchar();
}
虽然第一条和第二条语句在语法上都是正确的(即编译、构建和 运行 没有任何问题的迹象)只有第一条是有意义的,尽管我不知道是为了什么目的。鉴于声明:struct my_structure variable = {20};
,只定义了一个内存位置,指针的第二个增量将其位置超出您的定义,并指向一个未知值。在我的 运行 中,它指向 3
,但可能包含任何内容。并且,由于它不被拥有,调用 undefined behavior。这就是为什么 运行 在不同的 PC 上,甚至在不同时间在同一台 PC 上运行生成的可执行文件很可能会产生不同的结果。
让我们按运算符分解。
x->y
:这会取消引用指向结构的指针 (x
),然后访问指示的成员变量 (y
)。仅当 x
是指向结构的指针时才有意义。也等同于(*x).y
.
++x
:前增量x
。这会将 x
的值增加 1
,然后 returns x
的新值。它的优先级低于 ->
运算符,因此 ++p->i
将像上面那样获取 i
,然后递增它。
x++
:Post-增量 x
。这会将 x
的值增加 1
,然后 returns x
的旧值。但是,这一次,运算符具有相同的优先级并且从左到右执行。然后这将递增 p
,然后取消引用 p
曾经访问 i
的位置。这将为您提供 i
的值,但现在 p
指向 uninitialized/unknown 内存(除非 p
在数组中,在这种情况下它现在指向下一个该数组的成员)。
*x
:取消引用 x
就像我在上面 ->
下提到的,但在这个例子中我们现在做了两次,结果是等效的 ((**p).i)++)
。但是,由于 p
指向结构而不是指向结构的指针,因此这是一个编译器错误。这也适用于下一个表达式,因为它只是明确地说明了编译器已经符合的相同操作顺序。
综上所述,我们来到最后一个,顺序为:
- 取消引用
p
。 (到目前为止还不错) - 增加那个结果。但是没有为结构定义增量,所以编译器错误。
- 取消引用该结果。同样,不再是指针,所以我们不能取消引用。编译器错误。
- 从该结果中访问成员
i
。
你可以看到我这里使用的运算符优先级规则:https://en.cppreference.com/w/c/language/operator_precedence
可以使代码正常工作(编译,运行 不会崩溃,并产生连贯、可解释的答案),但您需要与所选结构不同的结构。例如:
#include <stdio.h>
struct my_structure
{
char *i;
};
#define EXPR(x) #x, x
int main(void)
{
char strings[][10] = { { "Winter" }, { "Bash" }, { "Is" }, { "Here" } };
struct my_structure variables[] = { { strings[0] }, { strings[1] }, { strings[2] }, { strings[3] } };
struct my_structure *p = variables;
printf("%10s: %s\n", EXPR(++p->i));
printf("%10s: %s\n", EXPR(p++->i));
printf("%10s: %d\n", EXPR(*p->i++));
printf("%10s: %d\n", EXPR(*p->i++));
printf("%10s: %d\n", EXPR((*p->i)++));
printf("%10s: %d\n", EXPR(*p++->i));
return 0;
}
生成输出:
++p->i: inter
p++->i: inter
*p->i++: 66
*p->i++: 97
(*p->i)++: 115
*p++->i: 116
宏 EXPR
只是让我不重复代码中的表达式,而且还可以在调用 printf()
.
当事情开始时,p->i
指向字符串 "Winter"
。
++p->i: inter
— 预先递增指针p->i
,使其指向Winter
的i
。p++->i: inter
— post-递增指针p
(指向"Bash"
),但结果与之前相同,因为递增在之后生效p->i
被使用。*p->i++: 66
— post-增加指针p->i
(因此它指向Bash
中的a
)并报告之前指向的值增量,即B
(ASCII 中的 66)。*p->i++: 97
— 相同的表达式,但指针指向递增前的a
(97) 和递增后的s
。(*p->i)++: 115
- post-增加p->i
指向的字母,报告s
但将其更改为t
.*p++->i: 116
— post-增量p
因此它指向字符串 "In",同时报告t
(116).
这是一个具有更多工具的替代方案:
#include <stdio.h>
struct my_structure
{
char *i;
};
#define EXPR(x) #x, x
int main(void)
{
char strings[][10] = { { "Winter" }, { "Bash" }, { "Is" }, { "Here" } };
struct my_structure variables[] = { { strings[0] }, { strings[1] }, { strings[2] }, { strings[3] } };
struct my_structure *p = variables;
for (size_t i = 0; i < sizeof(strings)/sizeof(strings[0]); i++)
printf("strings[%zu] = [%s]\n", i, strings[i]);
for (size_t i = 0; i < sizeof(variables)/sizeof(variables[0]); i++)
printf("variables[%zu].i = [%s]\n", i, variables[i].i);
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %s\n", EXPR(++p->i));
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %s\n", EXPR(p++->i));
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %d\n", EXPR(*p->i++));
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %d\n", EXPR(*p->i++));
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %d\n", EXPR((*p->i)++));
printf("%10s: %s\n", EXPR(p->i));
printf("%10s: %d\n", EXPR(*p++->i));
printf("%10s: %s\n", EXPR(p->i));
for (size_t i = 0; i < sizeof(strings)/sizeof(strings[0]); i++)
printf("strings[%zu] = [%s]\n", i, strings[i]);
for (size_t i = 0; i < sizeof(variables)/sizeof(variables[0]); i++)
printf("variables[%zu].i = [%s]\n", i, variables[i].i);
return 0;
}
及其输出:
strings[0] = [Winter]
strings[1] = [Bash]
strings[2] = [Is]
strings[3] = [Here]
variables[0].i = [Winter]
variables[1].i = [Bash]
variables[2].i = [Is]
variables[3].i = [Here]
p->i: Winter
++p->i: inter
p->i: inter
p++->i: inter
p->i: Bash
*p->i++: 66
p->i: ash
*p->i++: 97
p->i: sh
(*p->i)++: 115
p->i: th
*p++->i: 116
p->i: Is
strings[0] = [Winter]
strings[1] = [Bath]
strings[2] = [Is]
strings[3] = [Here]
variables[0].i = [inter]
variables[1].i = [th]
variables[2].i = [Is]
variables[3].i = [Here]
尝试使用此方案的变体(例如,额外的括号)以确保您了解正在发生的事情。
首先,一些提醒:
p++
的计算结果为 p
的当前值,并且作为 副作用 将 p
加 1。如果 p
是一个指针,它被设置为指向序列中的下一个对象。
++p
的计算结果为 p + 1
的当前值,并且作为副作用将 p
加 1。同样,如果 p
是一个指针,它被设置为指向序列中的下一个对象。
++
的后缀形式和->
运算符的优先级相同,比++
和[=33=的一元(前缀)形式的优先级高].因此,像 ++p->i
这样的表达式被解析为 ++(p->i)
,p->i++
被解析为 (p->i)++
,*p->i
被解析为 *(p->i)
,等等
除此之外...
表达式
++p->i
被解析为
++(p->i)
并评估为 p->i
的当前值加 1,并且作为 副作用 更新 p->i
.
表达式
p++->i
被解析为
(p++)->i
并评估当前 p->i
,然后更新 p
以指向序列中的下一个 struct
对象。
表达式
*p->i
被解析为
*(p->i)
因为 ->
的优先级高于一元 *
。一元操作数 *
必须是指针类型,但是 p->i
是一个整数,所以编译器会在这个表达式上 yak。
表达式
*p->i++
被解析为
*((p->i)++)
同样,由于 *
的操作数不是指针类型,因此编译器将对该表达式进行检查。
表达式
(*p->i)++
被解析为
(*(p->i))++
同样,p->i
没有指针类型,所以编译器会出问题。
表达式
*p++->i
被解析为
*((p++)->i)
而且,再一次,更多的 yakkage。