一眨眼,入职已经三个月了。感谢同事们的帮助与指导,有幸顺利转正。接下来,就是要好好吃饭,好好码代码,好好写文章了。
今天在阅读 ESP-IDF 的源码时,碰到了一个不太熟悉的特性 restrict
。我只知道它是 C 中的一个关键字(啥?你连看都没看过?哈哈~),它的特性便一无所知了。于是研究一二,记录于此。
碰到一个不熟悉的关键字应该怎么办?当然是查一下 Primer Plus 啦!可是正好手边没有这本书,所以我就百度了一下,下面是百度的信息:
restrict
,C语言中的一种类型限定符(Type Qualifiers),用于告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。
字面的意思是:该对象只能通过某一指针对其进行操作,除此之外不会对其进行修改。可是,这有什么用呢?再读一遍,我就注意到了一个关键词编译器,看到这三个字我下意识地就想到了编译器优化。那么最直接的方法就是做对比实验了。
首先,写一段常见的测试代码:
#include <stdio.h>
int add(int *a, int *b)
{
*a = 1;
*b = 2;
return *a + *b;
}
int main(void)
{
int sum;
int num_1;
int num_2;
sum = add(&num_1, num_2);
printf("The sum is %d.\n", sum);
return 0;
}
不用编译运行也知道,输出的结果是 3 。
那么再来看一下 restrict
版本吧:
#include <stdio.h>
int add(int *restrict a, int *restrict b) // THIS LINE CHANGED
{
*a = 1;
*b = 2;
return *a + *b;
}
int main(void)
{
int sum;
int num_1;
int num_2;
sum = add(&num_1, num_2);
printf("The sum is %d.\n", sum);
return 0;
}
接下来,我们编译运行一下,观察输出结果:
% gcc -o test test.c
% ./test
The sum is 3.
%
什么?加不加 restrict
没区别?哦!!!这可能与编译器优化有关,我是不是应该打开编译器优化选项啊?!
于是,我便添加了编译器优化选项 -O1
。但是,两个程序段的输出并没有变化。
正当我一筹莫展的时候,我突然想到了汇编。一个程序的执行效率如何,不能光看执行结果,还需要落实到汇编代码上。于是,我使用 gcc -O1 -S test.c
执行两个程序段的汇编,将得到的部分汇编代码(add()
部分)比较如下。
/* WITHOUT restrict */
add
mov w8, #1
str w8, [x0]
mov w8, #2
str w8, [x1]
ldr w8, [x0]
add w0, w8, #2
ret
/* WITH restrict */
add
mov w8, #1
str w8, [x0]
mov w8, #2
str w8, [x1]
mov w0, #3
ret
可以看到,加了 restrict
后,虽然运行结果不变,但汇编指令减少了 1 条。接下来,我们将逐条分析该程序段的汇编指令。
首先,两个程序段的前四条汇编指令都是相同,具体分析见注释:
add
mov w8, #1 // *a = 1
str w8, [x0]
mov w8, #2 // *b = 2
str w8, [x1]
在处理返回值时,不加 restrict
的处理为:
ldr w8, [x0] /* [1] */
add w0, w8, #2 /* [2] */
[1]
:取 *a
的值;
[2]
:加上 2
后返回。
再来看一下我们的返回语句:return *a + *b;
那么问题出现了:为什么取
*a
的值是去相应的地址取值,而取*b
的值却直接用数字2
代替呢?
这个问题暂时留在这,我们接着看加了 restrict
的处理:
mov w0, #3 /* [1] */
[1]
:直接返回 3
。
看到这里,聪明的你一定大概理解了 restrict
的作用了!
原来:
加了
restrict
后,表明指针变量a
与b
是修改其指向对象的唯一途径。那么在对a
和b
指向的对象赋值后,没有其他对其操作的语句了,所以编译器很自信地认为它们的值就是自己刚才写进去的值,不可能会变。而刚才写的值编译器自己太清楚了,当编译器看到最后一条返回语句时,心想:咋还要我加一遍呢?不就是一个1
和一个2
嘛,和不就是3
嘛~
当没加
restrict
时,在对a
和b
指向的对象赋值后,编译器看到返回语句,心想:我刚刚写入的b
对象是2
没错,但a
对象还是1
吗?有可能a
和b
存在着某种联系呢,那我写入b
时岂不是有可能对a
产生影响?算了算了,还是别偷懒了,再去a
地址看看到底是多少吧…
看到这,你应该完全理解了吧!
在加了 restrict
以后,编译器直到别的地方不会对其产生影响,所以自己写入的值是多少,它就一定不会变。但没加 restrict
时,编译器不能确定别的地方是不是会修改改值,所以只能老老实实地去相应的地址查看了。
好的,就此落笔。希望对你有一点点帮助,这是我最大的心愿。
如果方便的话,可以关注一下我的微信公众号:
编程笔记本
谢谢。