”危险“的RESTRICT与GCC的编译优化(编程者对编译器所做的一个“承诺”:使用restrict修饰过的指针,它所指向的内容只能经由该指针修改)
restrict是C99标准中新添加的关键字,对于从C89标准开始起步学习C语言的同学来说(包括我),第一次看到restrict还是相当陌生的。Wikipedia给出的解释如下:
In the C programming language, as of the C99 standard, restrict is a keyword that can be used in pointer declarations. The restrict keyword is a declaration of intent given by the programmer to the compiler. It says that for the lifetime of the pointer, only it or a value directly derived from it (such as pointer + 1) will be used to access the object to which it points. This limits the effects of pointer aliasing, aiding caching optimizations. If the declaration of intent is not followed and the object is accessed by an independent pointer, this will result in undefined behavior.
简单说来,restrict关键字是编程者对编译器所做的一个“承诺”:使用restrict修饰过的指针,它所指向的内容只能经由该指针(或从该指针继承而来的指针,如通过该指针赋值或做指针运算而得到的其他指针)修改,而不会被其他不相干的指针所修改。
那么这个承诺有什么用呢?有了编程者的承诺,编译器便可以对一些通过指针的运算进行大胆的优化了。
观察编译器优化的最好办法当然是查看编译后的汇编代码。Wikipedia上有一个很好的例子,我移植如下,特地在自己的机器上测试了一下,结论很有趣。
测试环境:Ubuntu 11.04 (x86-64) + Linux 2.6.38 + gcc 4.5.2
测试代码如下:


1 #include <stdio.h>
3
4 #ifdef RES
5 void multi_add(int* restrict p1, int* restrict p2, int* restrict pi)
6 #else
7 void multi_add(int* p1, int* p2, int* pi)
8 #endif
9 {
10 *p1 += *pi;
11 *p2 += *pi;
12 }
13
14 int main()
15 {
16 int a = 1, b = 2;
17 int inc = 1;
18
19 // increase both a and b by 1
20 multi_add(&a, &b, &inc);
21
22 // print the result
23 printf("a = %d, b = %d\n", a, b);
24 }


multi_add函数的功能很简单,将p1和p2指针所指向的内容都加上pi指针的内容。为了测试方便,使用了条件编译指令:如果定义RES宏,则使用带restrict的函数声明。(对于gcc,要开启默认关闭的c99支持:--std=c99)
分别编译出两个版本的程序:
gcc restrict.c -o without_restrict
gcc restrict.c -o with_restrict -DRES --std=c99
使用objdump查看目标文件的汇编代码(-d选项表示disassemble):
objdump -d without_restrict
PS:gcc默认使用的是AT&T汇编,与很多同学在初次学习汇编时接触的Intel x86汇编有些不同
除了表示上的细微符号差别,最大的区别是src/dest的顺序,两者恰好相反:
Intel : mov eax 2 (先dest后src)
AT&T : mov %2 %eax (先src后dest)
然而这次的结果让人失望:两个版本的程序拥有一模一样的multi_add函数,汇编代码如下:


push %rbp
mov %rsp,%rbp
mov %rdi,-0x8(%rbp)
mov %rsi,-0x10(%rbp)
mov %rdx,-0x18(%rbp)
mov -0x8(%rbp),%rax
mov (%rax),%edx
mov -0x18(%rbp),%rax
mov (%rax),%eax
add %eax,%edx
mov -0x8(%rbp),%rax
mov %edx,(%rax)
mov -0x10(%rbp),%rax
mov (%rax),%edx
mov -0x18(%rbp),%rax
mov (%rax),%eax
add %eax,%edx
mov -0x10(%rbp),%rax
mov %edx,(%rax)
leaveq
retq


其中寄存器rdi存放p1的地址,rsi存放p2的地址,rdx存放的是pi的地址。大段的汇编代码,无非是将寄存器中的内容mov到栈上的临时变量上,再把临时变量的值mov进寄存器进行加法运算。
难道restrict关键字没有任何作用?我怀疑很可能是编辑器优化程度不够。这次,使用-O1重新编译源代码并反汇编,终于观察到差别:
未使用restrict的版本:
mov (%rdx), %eax
add %eax, (%rdi)
mov (%rdx), %eax
add %eax, (%rsi)
使用了restrict的版本:
mov (%rdx), %eax
add %eax, (%rdi)
add %eax, (%rsi)
可以看出,-O1的编译优化还是很给力的,所有运算直接在寄存器中进行,不再蛋疼地先mov进栈变量,再mov进寄存器进行add运算(在这个简单的例子中,确实没有必要)。
最大的区别在于将rdx寄存器间接引用的值mov进eax的语句只在一开始执行了1次。可以理解,当程序员“承诺”这些指针都是相互独立不再干扰时,pi指针的内容在函数范围内可以视之为常量,只需要load进寄存器一次。
而没有restrict关键字时,即使程序中没有对pi的内容进行操作,编译器仍然不能保证pi的内容在函数范围内是常量:因为有pointer aliasing的可能,即p1和p2指向的内容和pi相关(简单情况:p1和pi实际是同一个指针)。
需要注意的是,restrict是程序员给出的“承诺“,编译器没有指针的合法使用进行检查的职责,也没有这样的能力。
事实上,打开restrict关键字,如果这样调用:
multi_add(&a, &b, &a);
编译器不会报错。(事实上编译期完全有能力检查出简单alias的pointer)
而使用不同的编译优化级别(不优化,-O1, -O2),则产生了相当不同的结果。
不优化 : a = 2, b = 4
-O1 : a = 2, b = 3
-O2以上: a = 2, b = 4
前面已经提到,没有开启-O选项时,gcc没有对restrict关键字进行优化(至少在这个例子中),所以应当是正确的行为(尽管此行为可能与编写multi_add函数的初衷不符合)
在O1下,restrict被优化,pi的值一开始即被缓存,所以产生了a和b都增加了1的结果
那么为什么O2以上,行为又开始变得正确了呢?
继续反汇编代码,发现-O2以上时,multi_add函数本身代码保持不变(确实在O1已经优化的相当简洁了),但main函数已经面目全非了:调用multi_add的代码已经改变,准确地说:
multi_add函数已经不再被main调用了
这里不再列出相关的汇编代码,因为这里的优化策略是相当复杂的。在这个例子中,由于a和b都是常量,a和b的值直接在编译期被算了出来,并放入寄存器中进行后续printf的调用。
可以看出,restrict确实是优化的利器。但是如果不仔细使用,它还是相当危险的,甚至能够导致在不同的优化级别下,出现完全不同的程序行为。
转自: https://www.cnblogs.com/promise6522/archive/2012/01/08/c99_restrict_and_gcc_optimization.html
https://www.cnblogs.com/dylancao/p/9555800.html
”危险“的RESTRICT与GCC的编译优化(编程者对编译器所做的一个“承诺”:使用restrict修饰过的指针,它所指向的内容只能经由该指针修改)的更多相关文章
- ”危险“的restrict与GCC的编译优化
restrict是C99标准中新添加的关键字,对于从C89标准开始起步学习C语言的同学来说(包括我),第一次看到restrict还是相当陌生的.Wikipedia给出的解释如下: In the C p ...
- iOS开发小技巧-修改SliderBar指针的样式(牢记这个方法,只能通过代码来修改)
代码: // 修改进度条的指针图片 [self.progressSlider setThumbImage:[UIImage imageNamed:@"player_slider_playba ...
- GCC的编译和安装 很好的资料
http://blog.csdn.net/yrj/article/details/492404 1.GCC的编译和安装2.预处理 #define 可以支持不定数量的参数. 例子如下: ...
- linux应用程序设计--GCC程序编译
GCC程序编译 linux系统下的GCC(GNU C Compiler)是GNU推出的功能强大.性能优越的多平台编译器,是GNU的代表作之一.GCC可以在多种硬件平台上编译出可执行程序,其执行效率与一 ...
- JVM性能优化系列-(6) 晚期编译优化
6. 晚期编译优化 晚期编译优化主要是在运行时做的一些优化手段. 6.1 JIT编译器 在部分的商用虚拟机中,java程序最初是通过解释器(Interpreter) 进行解释执行的,当虚拟机发现某个方 ...
- restrict关键字(暗示编译器,某个指针指向的空间,只能从该指针访问)
我们希望某个对象(内存空间)不被修改的通常做法是什么?声明该空间的const类型,但是这样真的可以吗?是不是的,由于const空间对象的指针是可以付给一个非const值指针的.所以这仍然无法不让该空间 ...
- GCC 编译优化指南(转)
GCC 编译优化指南(转) http://www.jinbuguo.com/linux/optimize_guide.html 作者:金步国 版权声明 本文作者是一位开源理念的坚定支持者,所以本文虽然 ...
- GCC 编译优化指南
转自: http://www.jinbuguo.com/linux/optimize_guide.html GCC 编译优化指南 作者:金步国[www.jinbuguo.com] 版权声明 本文作者是 ...
- GCC编译优化指南【作者:金步国】
GCC编译优化指南[作者:金步国] GCC编译优化指南 作者:金步国 版权声明 本文作者是一位自由软件爱好者,所以本文虽然不是软件,但是本着 GPL 的精神发布.任何人都可以自由使用.转载.复制和再分 ...
随机推荐
- mysql 操作提示 1366 Incorrect string value
一.报错说明 数据库此字段的字符集与整理字符集是否与SQL语句传递数据的字符集相同:不相同则会引发MySQL1366错误. 二.产生原因 windows 安装MySql 的时候选择的是默认的编码,创建 ...
- 关于java1.8中LocalDateTime实现日期,字符串互转小坑。
今天无聊,来看了下1.8的时间类型LocalDateTime,当想把字符串转成LocalDateTime的时候报错!! java.time.format.DateTimeParseException: ...
- 11g Rac PSU20180116手动补丁升级步骤
手动升级:软件包解压在新建的/home/grid/update 目录下ORACLE_HOME=/u01/app/oracle/product/11.2.0/dbhome_1GRID_HOME=/u01 ...
- Liunx搜索命令行
1.grep grep(General Regular Expression Parser,通用规则表达式分析程序)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来. 它的使 ...
- vue项目中阻止浏览器返回上一页
vue项目中在某个页面阻止浏览器返回上一页,适用移动端.PC端. 使用场景例如: 首页 与 A页面 来回跳转,那样点击浏览器返回时也会来回跳转,本想当页面在首页的时候就不再返回了,所以这个时候 ...
- Super超级ERP系统---(8)订单管理--订单创建
订单管理是ERP系统中一个重要模块,客户下订单,ERP通过订单来为客户进行配送.订单模块主要包括订单创建,订单修改,订单审核,订单取消,订单分配,订单打印,订单拣货,订单出库.在随后的几节里我们看看这 ...
- BS程序性能调优
首先想到的是优化算法.改进技术.扩展设备去做优化.其实在讨论性能的时候,绕不开对业务的理解,不同的业务系统对性能的要求不同,优化方式也不一样.优化性能的前提是保证业务的正确性.我们平时关注的性能主要是 ...
- hdu2236 无题II 最大匹配 + 二分搜索
中文题目,题意大家都明白. 看到“不同的行和列”就觉得要用二分匹配来做.要求最大值与最小值的差值最小,是通过枚举边的下限和上限来完成. 枚举过程是这样的,在输入的过程可以记录下边权的最大值MAX和最小 ...
- MongoDB 学习笔记(七):主从复制与副本集
一.主从复制 1.主从复制是一个简单的数据库同步备份的集群技术,如下图:要明确的知道主服务器与从服务器,且从服务器要明确的知道主服务器的存在. 2.在MongoDB中在启动数据库服务时,可以用mast ...
- 团体程序设计天梯赛-练习集-L1-024. 后天
L1-024. 后天 如果今天是星期三,后天就是星期五:如果今天是星期六,后天就是星期一.我们用数字1到7对应星期一到星期日.给定某一天,请你输出那天的“后天”是星期几. 输入格式: 输入第一行给出一 ...