元芳你怎么看

本网站主要用于记录我个人学习的内容,希望对你有所帮助

0%

函数声明

正确理解函数声明

看书的时候看到一个很抽象的语句:
(*(void(*)())0)();
想必每个C程序员看到这条语句都会觉得难绷。这个表达式实际上是在尝试调用位于地址0处的函数指针。在大多数操作系统中,访问地址0通常是非法的,因为它可能导致程序崩溃或产生未定义的行为。这种操作在一些特殊的底层编程场景中可能有用,但在通常的应用程序中应该避免使用,因为它不安全且不可移植。

请各位牢记一条十分简单的规则:按照使用的方式来声明。

声明符与表达式

声明符(Declaration Specifier)和表达式(Expression)是编程中的两个不同概念,它们有以下异同点:

相同点

  1. 语法元素:声明符和表达式都是编程语言的语法元素,它们用于构建和操作程序中的数据和操作。

  2. 用途:声明符用于声明(或定义)变量、函数、类型或其他标识符,以便程序正确识别和使用这些标识符。表达式用于表示和计算值,它可以包括操作数和操作符,用于执行特定的计算操作。

不同点

  1. 含义和作用

    • 声明符 用于描述标识符的性质和类型,但不执行计算或操作。它告诉编译器如何分配内存和如何解释标识符的内容。
    • 表达式 用于表示和计算值,执行特定的计算操作,返回一个结果。表达式可以包括常量、变量、运算符和函数调用等,用于生成新的值。
  2. 例子

    • 声明符示例:int x;(声明一个整数变量x)、void myFunction();(声明一个没有返回值的函数myFunction)。
    • 表达式示例:x + 5(计算x与5的和,返回一个值)、myFunction()(调用函数myFunction,返回其结果)。
  3. 上下文

    • 声明符 通常出现在变量、函数、类型或其他标识符的声明语句中,例如在变量声明、函数原型、结构体定义等地方。
    • 表达式 通常出现在赋值语句、算术运算、逻辑运算、函数调用等地方,用于执行操作和计算值。

总之,声明符用于描述标识符的性质和类型,而表达式用于表示和计算值。它们在编程中扮演不同的角色,但都是构建程序逻辑的重要组成部分。

声明

任何的C变量的声明都由两部分组成:类型,声明符。最简单的声明符就是单个变量,比如:

int tmp;

这个声明的含义是:当对其求值时,tmp的类型是int
当然也可以这样写:

int ((tmp));

这个声明的含义是:当对其求值时,((tmp))的类型是int型,因此也可以推出来tmp的类型是int
同理的逻辑也适用于函数和指针的声明:

double func();

这个声明的含义是:func()求值的结果是double类型。也就是说,func是一个返回值为double类型的函数。

int* p;

这个声明的含义是:*p是一个int型数据。也就是说,p是一个指向int型数据的指针。

将上面的声明组合一下:

double *func2(),(*func3)();

表示*func2()(*func3())都是浮点类型的声明符。因为()优先级高于*,可以得出:
func2是一个函数,该函数的返回值是指向double类型数据的指针。
func3是一个函数指针,其指向的函数的返回值类型是double

类型转换符

类型转换符是一种在编程语言中用于改变一个值的数据类型的操作符或关键字。类型转换符允许程序员显式地指定将一个数据类型转换为另一个数据类型的方式。这种操作通常称为类型转换或强制类型转换。在理解了上面的知识点后,如果得到一个声明好的、给定类型的变量,得到它的类型转换符也就不是一件难事。例如:

float (*tmp)();

表示tmp是一个指向返回值为float类型的函数的指针,那么:

float (*)()

也就表示为一个“一个指向返回值为float类型的函数的指针”的类型转换符。

可以看下面代码再练习练习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

int myFunction(char *str, int num) {
// 函数实现
return num * 2;
}

int main() {
// 声明一个函数指针,并初始化为指向myFunction的指针
int (*h)(char *, int);
// 这里的myFunction的类型就是 int (*)(char *, int),为了便于理解我就全部写出来了
h = (int (*)(char *, int))myFunction;

// 现在可以使用h来调用函数
int result = h("Hello", 1);
printf("%d\n", result);

return 0;
}

注意:

(*fp)();

fp是一个函数指针,那么*fp就是该指针指向的函数,所以(*fp)()就是调用这个函数的方式。但是,ANSI C 标准允许程序员将上式子简写成fp(),这只是一种简写

看的仔细的读者就会发现,我上面代码调用函数也使用了简写。因此我上面的代码中:int result = h("Hello", 1);实际上是int result = (*h)("Hello", 1);

解答

(*(void(*)())0)();被设计的目的是为了在计算机启动的时候,硬件将调用首地址为0位置的子例程。

你可能会想,为什么不直接:

(* 0)()

可惜编译器是笨重的。因为运算符*必须要一个指针来进行解引用。而且这个指针还应该是一个函数指针,这样经过运算符*作用后的结果才能作为函数被调用。因此,上式子对0要进行强制类型转换为一个“指向返回值为void类型的函数的指针”。因此就有了下式:

(*(void(*)())0)();,让我解释一下这个表达式的各个部分:

  1. void(*)():这部分表示一个函数指针类型,它指向一个没有参数并且没有返回值的函数。这是一个函数指针类型的定义,但没有指向任何有效函数。

  2. 0:这是一个空指针,它不指向任何内存地址。

  3. (*(void(*)())0):这部分将空指针转换为一个函数指针,然后尝试通过解引用这个函数指针来调用一个函数。

  4. 最后的 (); 尝试调用解引用的函数指针,但由于函数指针不指向有效函数,这会导致未定义行为,通常会导致程序崩溃或其他不可预测的结果。