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修饰过的指针,它所指向的内容只能经由该指针修改)的更多相关文章

  1. ”危险“的restrict与GCC的编译优化

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

  2. iOS开发小技巧-修改SliderBar指针的样式(牢记这个方法,只能通过代码来修改)

    代码: // 修改进度条的指针图片 [self.progressSlider setThumbImage:[UIImage imageNamed:@"player_slider_playba ...

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

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

  4. linux应用程序设计--GCC程序编译

    GCC程序编译 linux系统下的GCC(GNU C Compiler)是GNU推出的功能强大.性能优越的多平台编译器,是GNU的代表作之一.GCC可以在多种硬件平台上编译出可执行程序,其执行效率与一 ...

  5. JVM性能优化系列-(6) 晚期编译优化

    6. 晚期编译优化 晚期编译优化主要是在运行时做的一些优化手段. 6.1 JIT编译器 在部分的商用虚拟机中,java程序最初是通过解释器(Interpreter) 进行解释执行的,当虚拟机发现某个方 ...

  6. restrict关键字(暗示编译器,某个指针指向的空间,只能从该指针访问)

    我们希望某个对象(内存空间)不被修改的通常做法是什么?声明该空间的const类型,但是这样真的可以吗?是不是的,由于const空间对象的指针是可以付给一个非const值指针的.所以这仍然无法不让该空间 ...

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

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

  8. GCC 编译优化指南

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

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

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

随机推荐

  1. jQuery不熟点总结

     jQuery 事件 1 .trigger() 方法触发被选元素的指定事件类型. 2 .delegate() 事件委派  1.不占内存2.可以给未来元素(后期动态添加的元素)添加事件. 2.  添加元 ...

  2. shp系列(二)——利用C++进行shp文件的读(打开)

    1.各数据类型及其字节数 BYTE 1;       char 1;    short 2;      int 4;    double 8; 2.位序big和little及其转换 对于位序是big的 ...

  3. 【Linux】YUM Repositories for CentOS, RHEL & Fedora Systems

    这里是官方wiki:https://wiki.centos.org/AdditionalResources/Repositories 一.简介 YUM(Yellowdog Updater Modifi ...

  4. 测试数据准备中用到到csv写文件知识点

    对于大数据测试中,有时需要自己去准备一些数据,用csvreader来写一个比较大的文件就比较方便,下面我就直接贴示例代码了: package com.acxm.amysu.test;import co ...

  5. 【JavaScript从入门到放弃】JS基础-01

    作为一个前端开发人员,JS是我们行走江湖吃饭的家伙.基本上一个前端人员能值多少大洋,就看JS了.虽然各种框架层出不穷,但是归根结底学好原生JS才是硬道理. 学习任何新东西其实都遵循 10000 小时成 ...

  6. 【Oracle】查询当前SCN

    介绍两种方式: 一.sys用户下: select current_scn from v$database; select dbms_flashback.get_system_change_number ...

  7. Kafka学习笔记(1)----Kafka的简介和Linux下单机安装

    1. Kafka简介 Kafka is a distributed,partitioned,replicated commit logservice.它提供了类似于JMS的特性,但是在设计实现上完全不 ...

  8. Java中数组的定义方式

    数组定义方式一 动态方式(指定数组的长度) 格式: 数组存储的数据类型[]数组名字 = new 数组存储的数据类型[长度]; [] : 表示数组. 数组名字:为定义的数组起个变量名,满足标识符规范,可 ...

  9. sass揭秘之@mixin,%,@function(转载)

    因为文章内含有很多sass代码,如需自己动手查看编译结果,推荐使用sassmeister这款在线编译工具,方便你阅读学习. 在阅读本文章之前,请先确认你已经阅读了上篇文章sass揭秘之变量,不然会给你 ...

  10. iframe嵌入的子页面如何刷新父窗口

    iframe中刷新父页面方法及一些按钮刷新代码集合[原创+转]2009-07-23 11:12a页面里iframe了个b页面,我想实现在b页面里一个按钮,一按就刷新a页面,也就是父页面,不是只刷新if ...