关于C语言中的restrict关键字 c语言关键字
http://www.chinaunix.net/ 作者:phoneix 发表于:2007-06-17 09:18:45
此篇文章摘取于即将登载于《Dr.Dobb's 软件研发》第三期(2003年10月)的《The New C:一切源于FORTRAN》,文章主要是介绍了C99的新特性受限指针,在得到作者Randy Meyers以及《Dr.Dobb's 软件研发》杂志负责人刘江先生的应允下,把全文的前面的一部分作为文档发表,希望能对大家有所帮助。
新的C语言:一切都源于FORTRAN
译注:本文是作者Randy Meyers在 CUJ杂志开的一个专题系列The New C的第二篇文章,主要是叙说C99中的新关键字restrict以及受限指针(restricted pointers)的历史渊源和使用方式。受限指针作为一种编译器优化代码的方式,是由编译器厂商提供特定的实现,因此这篇文章所谈论的并非在一切实现中都能得到支持,至于如何使用restrict关键字,这篇文章做了很好的说明,期望本文能给关心C语言和使用C语言的用户带来帮助。在翻译上,所有译者在翻译过程中有疑惑的术语或者其他一切都以括号形式把原文直接给出,诚心不想给读者半点误导,但是否如愿还需读者的评判,关于本文的一切可以用[email]amstrongest@hotmail.com[/email]与译者联系和讨论。
有时候改进一种语言的最好方式就是让它和三十年前的古老样子更相似
一切都源于FORTRAN(It all began with FORTRAN)。
谈起上面的话,我并不是想说FORTRAN是第一个程序设计语言,但是在上个世纪六十年代(1960s)的一场关于如何在FORTRAN中实现参数传递的争论,却意外的使FORTRAN在七十年代(1970s)的超级计算机上面的性能有了巨大的提升,并且导致了九十年代(1990s)一个关于C语言的新特征被C99所接受,这就是受限指针(restricted pointers)。而理解受限指针的原始动机的最好方式就是回顾历史,重温发生在FORTRAN中的那个由争论所导致的意外。
和C不一样的是,在FORTRAN中如果一个函数被分配了一个新值作为参数,传递给函数的实参值将会改变,并且在函数返回时,调用者将会得到新的参数值。考虑下面Example 1所例举的代码,如果你以Y作为参数调用F,在F返回时,Y值将会是6。[译注:下面的程序没有出现变量Y,文中意思是Y是实参数,而下面程序出现的X是形参数,只是属于函数F的内部变量,但是当把Y复制给X后,并且改变X同时将改变外面调用的Y的值]
Example 1:
SUBROUTINE F(X)
INTEGER X
X = 6
END
这样的参数传递方式就使争论随之而来。不同的FORTRAN编译器可以选择两种实现方式中的一种来获得FORTRAN中的参数传递语义。第一种方式是引用参数传递(by reference),也就是典型的C程序员所使用的:写一个函数,并且在它的调用者中修改变量。(write a function that modifies a variable in its caller)。传递给函数的是一个参数的地址,并且在需要的时候任何地方都可以间接访问这个参数。如果FORTRAN编译器产生C代码的话,就会和下面Example 2.的C代码类似。
Example 2:
void f(int *x)
{
*x = 6;
}
然而,对于一些类型的计算机来说,间接访问局部变量比直接引用访问所带来的运行时开销要大的多。这也就导致了FORTRAN中参数传递的第二种实现方式。实参的地址依然会被传递给函数,但是函数一旦被调用,就将生成一个实参数的局部拷贝[译注:传递的是地址,但是函数内部拷贝的却是参数值],在函数生存期中将一直使用这个拷贝的局部变量,当函数返回时,将把拷贝变量赋值给调用函数的参数变量。这样的FORTRAN编译器如果产生C代码将会和下面的Example 3相类似。进/出拷贝(copy in/copy out)参数传递方式增加了函数进入和返回时的负担,但是如果一个参数被多次引用,而间接引用(在一些机器上代价十分昂贵)却不再使用的话,导致的结果就是性能的提升(在一些机器上面而言)。[译注:就是说第一种方式的主要的调用开销是间接引用,第二种方式的主要调用开销是拷贝变量,其中哪种更好,需要根据真实代码的情况衡量决定]。
Example 3:
void f(int *x)
{
int xcopy = *x;
xcopy = 6;
*x = xcopy;
}
大多数时候,编译器如何实现语言特征通常都被认为不过只是“实现细节”。它们不会影响程序员编写程序的方式,而语言的标准委员会允许语言的实现者自由选择和改变实现方式。然而,根据使用的参数传递机制,FORTRAN程序会产生不同的结果。考虑下面Example 4中的FORTRAN代码,以及以两种方式转换成的C代码:
Example 4:
SUBROUTINE G(X, Y)
INTEGER X, Y
X = 1 + X
Y = 5 + Y
END
// Translation using "by reference"
void g(int *x, int *y)
{
*x = 1 + *x;
*y = 5 + *y;
}
// Translation using
// "copy in/copy out"
void g(int *x, int *y)
{
int xcopy = *x;
int ycopy = *y;
xcopy = 1 + xcopy;
ycopy = 5 + ycopy;
*x = xcopy;
*y = ycopy;
}
G函数给它的参数加上了不同的常量,如果你把参数A和B传递给函数G,并且在调用前A的值是1,B的值是10。不用怀疑,无论使用FORTRAN中的那种函数参数传递机制,当函数返回时A的值将变成2而B的值将变成15。但是请考虑,如果你传递参数都是的A(并且被初始化为1),将会是什么情况?如果是使用引用调用(by reference)的参数传递机制,在函数返回时A将的值将变成7。A的值在赋值给*x的过程中时候被更新,因为x和y都指向A,所以在随后的对*y赋值的过程中A的值将再次被改写。相反,如果是使用进/出拷贝(copy in/copy out)的参数传递机制,在函数返回时,A的值将是6。调用发生后,在函数G中将不同的拷贝变量,并且每一个都将在函数返回时赋值给A,但最后的一个拷贝变量的返回值才会成为A的终值。
这两种不同的参数传递机制是任何程序设计语言定义者所必须面对的不一致性的代表。语言需要特殊的实现实现技术吗?也许这将会以付出效率为代价?语言的特性是否应该为了避免争议而改变?FORTRAN的定义者因为效率而允许同时存在两种参数传递机制。而一旦这样的决定做了出来,某种类型的程序就变的不一致了,并将导致无法定义的结果(outlawed)。
FORTRAN 66 标准包含了一系列可能会误导程序员的规则。在函数参数列表中,对于任何变量你都只能传递一次。如果你传递了一个变量作为函数参数,那么这个函数就不能再在全局上引用这个变量(FORTRAN COMMON)。如果你传递给一个变量给函数,你就不能再传递任何东西,并且这个函数也不能再引用任何东西,that overlaps it in storage (FORTRAN EQUIVALENCE)。在这样的规则下,没有什么程序可以确定应该采用何种参数传递机制。
大约十年以后[译注:意指1970s],为了实现超级计算机Cray 1的高性能,超级计算机需要高优化的编译器来使传统的程序能够使用机器的向量寄存器(vector registers)。考虑Example 5中的程序。其中对于函数来说最有效率的代码就是先后把数组指针x,y载入到向量寄存器中然后执行向量加指令来把两个向量寄存器中的变量加在一起。如果编译器以产生向量指令的方式来取代传统的使用循环来访问数组中的每一个元素的方式,那么代码的运行效率将得到巨大的提升。
Example 5:
void
vector_add(float *x, float *y,
float *result)
{
int i;
for (i = 0; i < 64; ++i)
result = x + y;
}
编译器中的优化器肯定会把循环转化成一系列的向量指令,但是问题在于那些向量指令是否真的whether the sequence of vector instructions is really equivalent to the original loop。你能在处理result数组的存储工作之前就把x,y数组载入到向量寄存器中,只因为你清楚result数组和x,y数组都是不同的个体。考虑如果result指向x[1],将会发生什么?在这种情况下result[0]其实就是x[1],同样result[I]其实就是x[I+1],每一次循环迭代过程中都会存储下一次的迭代中会被引用的变量。如果在做result的存储工作之前就把x载入到向量寄存器中去,变量值将会改变calculated change。正是在这一点上,FORTRAN的定义就带来了冲突。为了避免在传递机制中需要引入一个特殊的参数,FORTRAN标准定义了一系列精确的规则用来允许向量化编译器(vectorizing compiler)假设x,y和result都是互不相关的,non-overlapping arrays。就这样偶然的,FORTRAN在向量机上就有了巨大的性能优势。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/enjoyo/archive/2007/07/31/1719423.aspx
http://hanlin379.blog.163.com/blog/static/163239519201011290658673/
二、The restrict Type Qualifier
C The restrict type qualifier may only be applied to a pointer. A pointer declaration that uses this type qualifier establishes a special association between the pointer and the object it accesses, making that pointer and expressions based on that pointer, the only ways to directly or indirectly access the value of that object.
A pointer is the address of a location in memory. More than one pointer can access the same chunk of memory and modify it during the course of a program. The restrict type qualifier is an indication to the compiler that, if the memory addressed by the restrict-qualified pointer is modified, no other pointer will access that same memory. The compiler may choose to optimize code involving restrict-qualified pointers in a way that might otherwise result in incorrect behavior. It is the responsibility of the programmer to ensure that restrict-qualified pointers are used as they were intended to be used. Otherwise, undefined behavior may result.
If a particular chunk of memory is not modified, it can be aliased through more than one restricted pointer.
The following example shows restricted pointers as parameters of foo(), and how an unmodified object can be aliased through two restricted pointers.
void foo(int n, int * restrict a, int * restrict b, int * restrict c)
{
int i;
for (i = 0; i < n; i++)
a = b + c;
}
Assignments between restricted pointers are limited, and no distinction is made between a function call and an equivalent nested block.
{
int * restrict x;
int * restrict y;
x = y; // undefined
{
int * restrict x1 = x; // okay
int * restrict y1 = y; // okay
x = y1;// undefined
}
}
In nested blocks containing restricted pointers, only assignments of restricted pointers from outer to inner blocks are allowed. The exception is when the block in which the restricted pointer is declared finishes execution. At that point in the program, the value of the restricted pointer can be carried out of the block in which it was declared.
C++ C++ supports the restrict keyword as a non-orthogonal language extension for compatibility with C99. The compiler consumes and ignores the keyword, but issues a diagnostic message for incorrect usage. It is non-orthogonal because an existing C++ program can use restrict as a variable name.
三、C语言 restrict
2010-05-27 14:22 来自http://hi.baidu.com/vv1133/blog/item/08c7de349e35e1375ab5f5ce.html
我觉得这个解释比较好
从它来看restrict应该是为了解决代码1中出现的问题
使用restrict限制一个地址只能通过一个指针访问(就像是代码1增加了restrict限制后编译时会报错)
另外要注意的是这个关键字只是c99功能
不是标准c++中的
'Restrict' Pointers
One of the new features in the recently approved C standard C99, is the restrict pointer qualifier. This qualifier can be applied to a data pointer to indicate that, during the scope of that pointer declaration, all data accessed through it will be accessed only through that pointer but not through any other pointer. The 'restrict' keyword thus enables the compiler to perform certain optimizations based on the premise that a given object cannot be changed through another pointer. Now you're probably asking yourself, "doesn't const already guarantee that?" No, it doesn't. The qualifier const ensures that a variable cannot be changed through a particular pointer. However, it's still possible to change the variable through a different pointer. For example:
void f (const int* pci, int *pi;); // is *pci immutable?
{
(*pi)+=1; // not necessarily: n is incremented by 1
*pi = (*pci) + 2; // n is incremented by 2
}
int n;
f( &n, &n);//增加restrict关键字是因为这里会出问题,
//如果对两个参数都使用了restrict关键字,那么这里编译时会报错,因为一
//个地址可以通过两个指针访问了
In this example, both pci and pi point to the same variable, n. You can't change n's value through pci but you can change it using pi. Therefore, the compiler isn't allowed to optimize memory access for *pci by preloading n's value. In this example, the compiler indeed shouldn't preload n because its value changes three times during the execution of f(). However, there are situations in which a variable is accessed only through a single pointer. For example:
FILE *fopen(const char * filename, const char * mode);
The name of the file and its open mode are accessed through unique pointers in fopen(). Therefore, it's possible to preload the values to which the pointers are bound. Indeed, the C99 standard revised the prototype of the function fopen() to the following:
/* new declaration of fopen() in <stdio.h>; */
FILE *fopen(const char * restrict filename,
const char * restrict mode);
Similar changes were applied to the entire standard C library: printf(), strcpy() and many other functions now take restrict pointers:
int printf(const char * restrict format, ...);
char *strcpy(char * restrict s1, const char * restrict s2);
C++ doesn't support restrict yet. However, since many C++ compilers are also C compilers, it's likely that this feature will be added to most C++ compilers too.
Danny Kalev
Ashdod, Israel
--------------------------------------------------------------------------------
If you have a hot tip and we publish it, we'll pay you $10. Be sure to include a clear explanation of what the technique does and why it's useful. If it includes code, limit it to 20 lines if possible. Submit a tip here.
void *memcpy(void * restrict s1,const void * restrict s2,size_t n);
如果拷贝发生在两个重叠的对象之间,行为是不确定的。
void *memmove(void *s1, constvoid *s2, size_t n);
即使两个指针指向的区域互相重叠,拷贝也不会受影响。
值得注意的是,一旦你决定使用restrict来修饰指针,你必须得保证它们之间不会互相重叠,编译器不会替你检查。
更多阅读
各国语言中的12个月的拼写1 日耳曼语族 各国语言
Old EnglishSeæfterra ȜēolaSolmōnaþHreþmōnaþĒastermō
C语言在K叉哈夫曼编码教学中的应用 c语言哈夫曼编码译码
摘 要:字符编码与信息压缩是计算机应用的重要研究课题,许多学者对此作了很多非常有价值的研究。文章简单分析了二叉哈夫曼树的构造及编码,通过比较三种构造三叉哈夫曼树的算法,提出了构造任意K叉哈夫曼树及K进制的最优前缀编码的算法,并
线程池技术个人理解以及c语言的简单实现 线程池实现原理
转载:http://blog.csdn.net/mxgsgtc/article/details/11694901这几天闲来无事,网上无意中看到了关于线程池的东西,发现挺有意思的,找了挺多资料,研究一下,线程池技术,个人理解,线程池是个集合(概念上的,当然是线程的集合),假设这个集合中有3个
C语言中scanf函数输入回车符的问题 c语言scanf连续输入
在用c语言编写输入语句的时候常用到scanf函数,初学者在刚用scanf函数输入时,经常会遇到各种各样的输入错误,最重要的是一定要记住scanf函数的输入格式,scanf函数里包含了哪些东西,输入的时候就必须有哪些东西,比如:scanf("%c%c%c"),那么输入
C语言最重要的知识点复习资料
总体上必须清楚的:1)程序结构是三种:顺序结构、选择结构(分支结构)、循环结构。2)读程序都要从main()入口,然后从最上面顺序往下读(碰到循环做循环,碰到选择做选择),有且只有一个main函数。3)计算机的数据在电脑中保存是以二进制