浅析 restrict 关键字

一眨眼,入职已经三个月了。感谢同事们的帮助与指导,有幸顺利转正。接下来,就是要好好吃饭,好好码代码,好好写文章了。

今天在阅读 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 后,表明指针变量 ab 是修改其指向对象的唯一途径。那么在对 ab 指向的对象赋值后,没有其他对其操作的语句了,所以编译器很自信地认为它们的值就是自己刚才写进去的值,不可能会变。而刚才写的值编译器自己太清楚了,当编译器看到最后一条返回语句时,心想:咋还要我加一遍呢?不就是一个 1 和一个 2 嘛,和不就是 3 嘛~

当没加 restrict 时,在对 ab 指向的对象赋值后,编译器看到返回语句,心想:我刚刚写入的 b 对象是 2 没错,但 a 对象还是 1 吗?有可能 ab 存在着某种联系呢,那我写入 b 时岂不是有可能对 a 产生影响?算了算了,还是别偷懒了,再去 a 地址看看到底是多少吧…

看到这,你应该完全理解了吧!
在加了 restrict 以后,编译器直到别的地方不会对其产生影响,所以自己写入的值是多少,它就一定不会变。但没加 restrict 时,编译器不能确定别的地方是不是会修改改值,所以只能老老实实地去相应的地址查看了。

好的,就此落笔。希望对你有一点点帮助,这是我最大的心愿。
如果方便的话,可以关注一下我的微信公众号:

编程笔记本

谢谢。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值