两段诡异代码
(*(void(*)())0)();
void (*signal(int , void(*)(int)))(int);
代码的出处《C陷阱和缺陷》
其实这些代码对于老司机来说肯定是见怪不怪,有点炒冷饭了。对于萌新来说简直是“我嘞个豆”。尤其是自己在学校上课老师讲得可能不太清楚,或者网课一笔带过导致似懂非懂。
所以我打算深度地去解析一下它!如果有讲的不好的地方,欢迎批评指点,谢谢!
代码1深度解析
(*(void(*)())0)();
这段代码是C语言中的一个复杂的表达式,它尝试调用一个函数,但这个函数的地址是0,也就是空指针。下面是对这段代码的分析:
0
这是一个整数常量,表示数字0。
(void(*)())
这是一个类型转换。它将整数0转换为一个函数指针的类型。这个函数指针的类型是指向一个返回类型为void且没有参数的函数。
(*(void(*)())0)
这是对转换后的函数指针进行解引用。解引用后得到的是一个函数,这个函数没有参数,返回类型为void。
(*(void(*)())0)()
最后,通过在解引用的函数后面加上一对括号()来调用这个函数。
在一些操作系统中,地址0是不可访问的,因为它通常被保留用于表示空指针错误(null pointer dereference)。尝试执行这段代码将导致程序崩溃,抛出一个访问违规(segmentation fault)或类似的错误。
#include <stdio.h>
void my_fc() {
printf("Hello, World!\n");
}
int main() {
// 正确版本
void (*fc_ptr)() = my_fc;
fc_ptr(); //调用
printf("调用空指针函数之前\n");
// 二号版本
(*(void(*)())0)(); //导致崩溃发生
// 如果程序能安全到达这里,说明没崩溃
printf("博主是个靓仔(应该都知道什么是靓仔吧)\n");
return 0;
}
Ok,我相信试了的同学,都没有看到博主是个靓仔
事后一想发现我为什么还要去试一下。总的来说,地址0又不包含有效的函数代码。或许是空指针保护,或许是无效的函数指针…
来个题目
int (*func_ptr)(int) = (int(*)(int))0x12345678;
func_ptr(10);
我嘞个豆x2
深度解析:
int (*func_ptr)(int)
这是一个函数指针的声明。func_ptr 是一个指针,它指向一个函数,这个函数接受一个 int 类型的参数并返回一个 int 类型的值。
(int(*)(int))0x12345678
这是一个类型转换。它将十六进制常量 0x12345678 转换为一个函数指针类型,这个类型与 func_ptr 的类型相匹配。这意味着它指向一个接受一个 int 参数并返回一个 int 的函数。
int (*func_ptr)(int) = (int(*)(int))0x12345678;
这行代码将转换后的地址赋值给 func_ptr 函数指针。现在,func_ptr 指向内存地址 0x12345678 处的函数。
func_ptr(10);
通过函数指针 func_ptr 调用函数,并传递参数 10。
总而言之,和上面的肯定是很类似的。这段代码的意图是调用位于内存地址 0x12345678 的函数,并将 10 作为参数传递给它。然而,潜在问题还是存在的:很多现代操作系统不给随便去访问任意内存地址。如果地址 0x12345678 不是有效的函数地址,或者该地址不是程序可执行内存的一部分,尝试调用这个地址的函数会导致未定义行为,然后导致程序崩溃。
代码2深度解析
void (*signal(int , void(*)(int)))(int);
这个其实我第一次看到时候没有我嘞个豆,除了可读性比较史,还是挺好看出来的。我的同学曾和我说:漂亮到极致的代码看不懂,史一样的我也看不懂,xx不读了。
回归正题 :
void (*)(int)
这是一个函数指针类型,指向一个接受一个 int 参数并返回 void 的函数。这种类型的函数指针通常用于信号处理函数,因为信号处理函数接受一个信号作为参数,并且不返回值。
signal(int, void(*)(int))
这是 signal 函数的声明。signal 是啥?signal函数接受两个参数:第一个参数是一个 int 类型,表示信号的编号;第二个参数是一个函数指针,指向信号处理函数,也就是上面提到的 void (*)(int) 类型。
void (*signal(int , void(*)(int)))(int)
这个整体声明表示 signal 函数返回一个函数指针,这个函数指针的类型与第二个参数的类型是一样的哦,指向一个接受一个 int 参数并返回 void 的函数。
其实第一段代码吃懂了,这个真的很好懂,当然了,对于老司机而言,啥也不是,对于我这种萌新而言,我啥也不是。
还没结束,这可读性也太差了,能改造一下么,可以!
void (*)(int)signal(int , void(*)(int))(int)
一下子就好看了,好看个xx,这是错的。
还是用 typedef 改造吧。
typedef改造行动
关于typedef,先来个例子:
typedef unsigned int uint;
接下来开始改造:
typedef void(*pfun_t)(int);
我对typedef的理解是*要吸住改造的成品,所以位置放的是不一样的。然后你再尝试用上面的改造方案来改造刚刚的代码。
pfun_t signal(int, pfun_t);
好看了!