通过反汇编来理解restrict关键字

一次难忘的面试经历

多年前,一次互联网某厂实习生的面试题,题目的代码片段很简单,如下:

 1  #include <stdlb.h>
  
 2  int main()
 3 {
 4      int *restrict pInt = (int*)malloc(4);
 5      int *pNewInt = pInt;
 6      return 0;
 7 }

 面试官问我运行结果是什么,我居然当时不加思索的回答,编译器会报错。当时个人理解是这样的,首先用malloc函数分配了一个4Bytes大小的内存,并让pInt指针指向它。同时pInt指针,也被restrict关键字修饰。而restrict就是用来表明是访问一个数据对象的唯一且初始的方式。因此在第5行中,将pInt的值赋值给了另一个指针pNewInt就是错误的。但是遗憾的是,这种错误编译器是不会报错的。下面我们就这个问题来聊一聊restrict关键字。

不使用restrict关键字

 restrict关键字实际上在C99标准出来以前,编译器就已经开始支持类似restrict的语句了,如编译器定义了__restrict。restrict关键字,实际上是用来指示编译器对代码进行优化的。在分析具体含义以前,我们先来看一个没有用restrict的例子。
在这里插入图片描述

 在main函数中定义了三个参数p、q、r。这之后调用了f函数,并把p和q的地址传递进去。在f中根据这两个地址,将p和q分别设置为2和3,最后把p的值2当做函数返回值返回,并赋值给整形变量r。这个代码很简单,不过重点不是看它,而是它的反汇编代码,下面我们通过objdump工具执行objdump -j .text -l -C -S test来生成汇编代码。
 首先来看下main函数的汇编代码。如下图所示
在这里插入图片描述
 在反汇编代码中的可以看到,分别将p和q的地址分别赋值给了rdi和 rsi寄存器。即rdi保存了p的地址,rsi保存了q的地址。之后调用了f函数。
而在f函数的反汇编代码中,通过如下图所示能确认rdi确实保存着p的地址
在这里插入图片描述
在这里插入图片描述
 接着,如下图所示,可以看到通过调用了puts函数向屏幕打印信息
在这里插入图片描述
最后,f函数最后用rax所指向区域的值,赋给了eax寄存器,即将p的值放入eax作为返回值返回。
在这里插入图片描述
 好了,现在有一个疑问,代码其对应的反汇编代码干嘛不直接把立即数2直接赋值给eax,反而多此一举从rax所指向的内存中把2取出来给eax呢?因为编译器之所以不直接把2赋值给eax,而非要从内存中获取,就是担心代码中会通过其他指针访问p所占的区域。在test.c的代码中我们通过指针a访问了p所占的区域,但是编译器不知道指针b是否也是指向p所占的区域,通过指针b是否也对该区域做了修改。因此编译器只能根据return *a,从内存中将2取出来赋值给eax。

使用restrict关键字

 好了,怎么样才能让编译器变得聪明一点呢?这个时候restrict就派上用场了。修改上面代码
在这里插入图片描述
 运行编译命令gcc -g -O2 -std=c99 -o test test.c进行编译,使用反汇编工具objdump查看发现
在这里插入图片描述
 可以看到直接把2赋给了eax寄存器,作为f的返回值返回了。看明白了吧,restrict关键字起作用了,编译器检测到指针a和b都使用了restrict关键字,也就是说,它们所指向的空间都只能通过指针a和b访问,不会有其他途径。这个时候,编译器就可以放心大胆地进行优化了,直接把2赋给了eax,而不用再从内存中获取了。这样,f的执行效率也提高了。这里还有一点需要注意,C++程序并不支持restrict关键字,但是可以使用“__restrict”关键字。

猜你喜欢

转载自blog.csdn.net/songguangfan/article/details/108309181
今日推荐