我应该如何理解这些 C 语法?
How should I understand theese C syntax?
如下代码,函数指针y
、z
、b
的行为相同。即使 *x
也会被理解为指针 x
的数据,而 &x
也会被理解为指向 x
.
的指针
typedef void (*func)(void);
void x(void){
printf("asdsag\n");
return;
}
int main(int argc, char const *argv[])
{
func y = *x;
func z = x;
func b = &x;
y();
z();
b();
return 0;
}
现在看这段代码。
void test(int **p){
printf("%p\n",p);
return;
}
int main(int argc, char const *argv[])
{
int * p[5];
test(p);
test(&p);
return 0;
}
此代码为所有 3 种情况打印相同的指针。
这是编译以上代码后的汇编
main:
@ args = 0, pretend = 0, frame = 32
@ frame_needed = 1, uses_anonymous_args = 0
push {r7, lr}
sub sp, sp, #32
add r7, sp, #0
str r0, [r7, #4]
str r1, [r7]
ldr r3, [r7, #12]
mov r0, r3
bl test
add r3, r7, #12
mov r0, r3
bl test
add r3, r7, #12
mov r0, r3
bl test
movs r3, #0
mov r0, r3
adds r7, r7, #32
mov sp, r7
@ sp needed
pop {r7, pc}
.size main, .-main
这三种情况似乎都采用相同的 r7+#12
。
[问题] 对于上面给出的所有信息,我应该如何理解 C99 中的 p
、*p
、&p
?
[信息] 这是用 gcc-linaro-7.2.1-2017 构建的。11-x86_64_arm-linux-gnueabihf
[更新] @tadman 关于不兼容指针的更新
#include <stdio.h>
void test1(void **p){
printf("%p\n",p);
return;
}
void test2(void *p){
printf("%p\n",p);
return;
}
int main(int argc, char const *argv[])
{
void * p[5] = {0};
test1(p);
test2(&p);
return 0;
}
即使 -Wall
也没有警告
这是输出:
0x7fff5615bb20
0x7fff5615bb20
P/S第一种情况我错了,删了
在第一种情况下,您有一个函数,如下所示:
typedef void (*f)(void);
void p(void) {
}
int main(int argc, char const *argv[]) {
func a = p;
func b = *p;
func c = &p;
a();
b();
c();
return 0;
}
所有这三个都起作用,因为 p
和 &p
是同一件事,函数上的 &
运算符是可选的,基本上是一个“什么都不做”的运算符。在 clang 上至少 *
也是一个空操作,没有记录但似乎是这种情况。因此,您可以执行 &p
或 *p
或 &*p
或 ****&&**&*p
并且您会得到相同的结果。
在第二种情况下你有实际数据:
void test(int **p) {
}
int main(int argc, char const *argv[]) {
int *p[5];
test(p); // Valid: int** -> int**
test(&p); // Invalid: Incompatible pointer int*** -> int**
test(*p); // Invalid: incompatible pointer int* -> int**
return 0;
}
第一个是已定义的行为,您正在传递一个相同的指针类型,因此它可以工作。后两个只是垃圾代码。也许他们会编译,但行为是未定义的。无论他们做什么都是完全随意的。
不要混淆函数指针和常规指针的间接级别。这是关于 C 标准的一个特别古怪的事情。
§ 6.3.2.1
Lvalues, arrays, and function designators
...
- A function designator is an expression that has function type. Except when it is the operand of the
sizeof operator,67) or the unary & operator, a function designator with type "function returning
type" is converted to an expression that has type "pointer to function returning type".
强调-
function designator with type "function returning
type" is converted to an expression that has type "pointer to function returning type".
(提到标准的正确部分,感谢@John Bollinger)
这基本上意味着a
、b
和c
在这里都是一样的
func a = p;
func b = *p;
func c = &p;
所有这些都可以像常规函数一样处理,并用 (...)
调用。
虽然常规指针的行为非常非常不同 - 正如您的下一个示例。
int * p[5];
test(*p);
test(p);
test(&p);
所以int* p[5]
声明了一个包含5个指针的数组。所以p
的类型是int*[5]
,p
中每个元素的类型(比如p[0]
)是int*
.
因此,对于 test(*p)
,您只是传递了 p[0]
(因为 *(p + 0)
== p[0]
== *p
)- 这是输入 int*
。从 int*
到 int**
的转换是未定义的,因此这是一个很大的禁忌。
使用 test(&p)
,您传递的是 int*(*)[5]
类型的值。再一次,不同级别的间接寻址——而不是函数类型——真正的规则在这里适用。行为未定义。
test(p)
很好,p
,因为类型 int*[5]
会衰减到 int**
,这是 test
[=42 的有效参数=]
(经过编辑以反映指针类型的非衰减版本,感谢 @John Bollinger)
此外,由于我们经常谈论未定义的行为,以防您感到困惑。它基本上意味着该行为没有明确定义的结果 - 您可能会在您的 PC 和您的环境中观察到某些 明显定义的 - 但在全球范围内不能保证相同的行为。
3.4.3
undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which
this document imposes no requirements
如下代码,函数指针y
、z
、b
的行为相同。即使 *x
也会被理解为指针 x
的数据,而 &x
也会被理解为指向 x
.
typedef void (*func)(void);
void x(void){
printf("asdsag\n");
return;
}
int main(int argc, char const *argv[])
{
func y = *x;
func z = x;
func b = &x;
y();
z();
b();
return 0;
}
现在看这段代码。
void test(int **p){
printf("%p\n",p);
return;
}
int main(int argc, char const *argv[])
{
int * p[5];
test(p);
test(&p);
return 0;
}
此代码为所有 3 种情况打印相同的指针。 这是编译以上代码后的汇编
main:
@ args = 0, pretend = 0, frame = 32
@ frame_needed = 1, uses_anonymous_args = 0
push {r7, lr}
sub sp, sp, #32
add r7, sp, #0
str r0, [r7, #4]
str r1, [r7]
ldr r3, [r7, #12]
mov r0, r3
bl test
add r3, r7, #12
mov r0, r3
bl test
add r3, r7, #12
mov r0, r3
bl test
movs r3, #0
mov r0, r3
adds r7, r7, #32
mov sp, r7
@ sp needed
pop {r7, pc}
.size main, .-main
这三种情况似乎都采用相同的 r7+#12
。
[问题] 对于上面给出的所有信息,我应该如何理解 C99 中的 p
、*p
、&p
?
[信息] 这是用 gcc-linaro-7.2.1-2017 构建的。11-x86_64_arm-linux-gnueabihf
[更新] @tadman 关于不兼容指针的更新
#include <stdio.h>
void test1(void **p){
printf("%p\n",p);
return;
}
void test2(void *p){
printf("%p\n",p);
return;
}
int main(int argc, char const *argv[])
{
void * p[5] = {0};
test1(p);
test2(&p);
return 0;
}
即使 -Wall
也没有警告
这是输出:
0x7fff5615bb20
0x7fff5615bb20
P/S第一种情况我错了,删了
在第一种情况下,您有一个函数,如下所示:
typedef void (*f)(void);
void p(void) {
}
int main(int argc, char const *argv[]) {
func a = p;
func b = *p;
func c = &p;
a();
b();
c();
return 0;
}
所有这三个都起作用,因为 p
和 &p
是同一件事,函数上的 &
运算符是可选的,基本上是一个“什么都不做”的运算符。在 clang 上至少 *
也是一个空操作,没有记录但似乎是这种情况。因此,您可以执行 &p
或 *p
或 &*p
或 ****&&**&*p
并且您会得到相同的结果。
在第二种情况下你有实际数据:
void test(int **p) {
}
int main(int argc, char const *argv[]) {
int *p[5];
test(p); // Valid: int** -> int**
test(&p); // Invalid: Incompatible pointer int*** -> int**
test(*p); // Invalid: incompatible pointer int* -> int**
return 0;
}
第一个是已定义的行为,您正在传递一个相同的指针类型,因此它可以工作。后两个只是垃圾代码。也许他们会编译,但行为是未定义的。无论他们做什么都是完全随意的。
不要混淆函数指针和常规指针的间接级别。这是关于 C 标准的一个特别古怪的事情。
§ 6.3.2.1
Lvalues, arrays, and function designators
...
- A function designator is an expression that has function type. Except when it is the operand of the sizeof operator,67) or the unary & operator, a function designator with type "function returning type" is converted to an expression that has type "pointer to function returning type".
强调-
function designator with type "function returning type" is converted to an expression that has type "pointer to function returning type".
(提到标准的正确部分,感谢@John Bollinger)
这基本上意味着a
、b
和c
在这里都是一样的
func a = p;
func b = *p;
func c = &p;
所有这些都可以像常规函数一样处理,并用 (...)
调用。
虽然常规指针的行为非常非常不同 - 正如您的下一个示例。
int * p[5];
test(*p);
test(p);
test(&p);
所以int* p[5]
声明了一个包含5个指针的数组。所以p
的类型是int*[5]
,p
中每个元素的类型(比如p[0]
)是int*
.
因此,对于 test(*p)
,您只是传递了 p[0]
(因为 *(p + 0)
== p[0]
== *p
)- 这是输入 int*
。从 int*
到 int**
的转换是未定义的,因此这是一个很大的禁忌。
使用 test(&p)
,您传递的是 int*(*)[5]
类型的值。再一次,不同级别的间接寻址——而不是函数类型——真正的规则在这里适用。行为未定义。
test(p)
很好,p
,因为类型 int*[5]
会衰减到 int**
,这是 test
[=42 的有效参数=]
(经过编辑以反映指针类型的非衰减版本,感谢 @John Bollinger)
此外,由于我们经常谈论未定义的行为,以防您感到困惑。它基本上意味着该行为没有明确定义的结果 - 您可能会在您的 PC 和您的环境中观察到某些 明显定义的 - 但在全球范围内不能保证相同的行为。
3.4.3
undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this document imposes no requirements