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

”危险“的restrict与GCC的编译优化的更多相关文章

  1. ”危险“的RESTRICT与GCC的编译优化(编程者对编译器所做的一个“承诺”:使用restrict修饰过的指针,它所指向的内容只能经由该指针修改)

    restrict是C99标准中新添加的关键字,对于从C89标准开始起步学习C语言的同学来说(包括我),第一次看到restrict还是相当陌生的.Wikipedia给出的解释如下: In the C p ...

  2. GCC 编译优化指南(转)

    GCC 编译优化指南(转) http://www.jinbuguo.com/linux/optimize_guide.html 作者:金步国 版权声明 本文作者是一位开源理念的坚定支持者,所以本文虽然 ...

  3. GCC 编译优化指南

    转自: http://www.jinbuguo.com/linux/optimize_guide.html GCC 编译优化指南 作者:金步国[www.jinbuguo.com] 版权声明 本文作者是 ...

  4. GCC编译优化指南【作者:金步国】

    GCC编译优化指南[作者:金步国] GCC编译优化指南 作者:金步国 版权声明 本文作者是一位自由软件爱好者,所以本文虽然不是软件,但是本着 GPL 的精神发布.任何人都可以自由使用.转载.复制和再分 ...

  5. GCC 编译优化指南【转】

    转自:http://www.jinbuguo.com/linux/optimize_guide.html 版权声明 本文作者是一位开源理念的坚定支持者,所以本文虽然不是软件,但是遵照开源的精神发布. ...

  6. gcc/g++ -O 优化选项说明

    查查gcc手册就知道了,每个编译选项都控制着不同的优化选项 下面从网络上copy过来的,真要用到这些还是推荐查阅手册 -O设置一共有五种:-O0.-O1.-O2.-O3和-Os. 除了-O0以外,每一 ...

  7. Gcc的编译流程分为了四个步骤:

    http://blog.csdn.net/xiaohouye/article/details/52084770(转) Gcc的编译流程分为了四个步骤: 1.预处理,生成预编译文件(.文件): Gcc ...

  8. GCC的编译和安装 很好的资料

    http://blog.csdn.net/yrj/article/details/492404 1.GCC的编译和安装2.预处理    #define 可以支持不定数量的参数.    例子如下:    ...

  9. 学习总结:gcc/g++ 编译与链接

    gcc/g++ 编译与链接 编译与链接的过程可以分解为四个步骤:预处理.编译.汇编.链接 预处理:源代码文件和相关的头文件,被预处理器cpp预处理成一个后缀为 .i 的文件(选项:-E) 编译:把预处 ...

随机推荐

  1. python 字符串 转 bit, bitarray 异或加密

    Python中异或加密要将str 转为 bitarray, 提示: int类型和纯数字的字符串也可以异或处理, 可能更方便 from bitarray import bitarray def str2 ...

  2. Winform 利用 Oracle.ManagedDataAccess访问Oracle数据库

    Winform 利用 Oracle.ManagedDataAccess访问Oracle数据库时出现以下错误: Message = "每个配置文件中只允许存在一个 <configSect ...

  3. 推荐一款Notepad++主题Dracula

    https://draculatheme.com/notepad-plus-plus/ Activating theme Go to %AppData%\Notepad++\themes Place  ...

  4. Python 3.6版本中实现 HTMLTestRunner输出时”fp=file(filename,'wb')“报错

    原错误代码: if __name__=='__main__': suite=unittest.makeSuite(WidgetTestCase) filename='D:\\myreport.html ...

  5. 【机器学习】支持向量机(SVM)的优化算法——序列最小优化算法(SMO)概述

    SMO算法是一一种启发式算法,它的基本思路是如果所有变量的解的条件都满足最优化问题的KKT条件,那么这个最优化问题的解就得到了.因为KKT条件是该优化问题的充分必要条件. 整个SMO算法包括两个部分: ...

  6. Springboot & Mybatis 构建restful 服务五

    Springboot & Mybatis 构建restful 服务五 1 前置条件 成功执行完Springboot & Mybatis 构建restful 服务四 2 restful ...

  7. hw3

    1. 2.将MAXPRIMES的值设为4,t2会发生越界错误,t1不会. 3.令n = 1,此时不满足while语句的判断条件,不执行while循环. 4. 点覆盖:{1,2,3,4,5,6,7,5, ...

  8. typeof 和 instanceof apply与call简单用法以及判断数组的坑

    1 typeof 和 instanceof var array = [];平时如果判断一个对象是否为数组,可能你会用 typeof array,但是输出为“object”. typeof 一般只能返回 ...

  9. springBoot 使用拦截器 入坑

    近期使用SpringBoot 其中用到了拦截器 结果我的静态资源被全部拦截了,让我导致了好久才搞好: 看下图项目结构: 问题描述:上图划红框的资源都被拦截器给拦截了,搞得项目中不能访问:解决问题就是在 ...

  10. 受限filterbanks

    2.Related works to filterbank learning 虽然DNN-HMM模型的性能比GMM-HMM模型相比,具有很大的优势,但是训练集和测试集之间的失配问题,也使得DNN-HM ...