指针的安全性

指针和引用几乎是每个语言的必要元素。即使是ocaml这样的函数式语言,也不得不提供这样的东西。
先考虑,一个语言中为什么要有引用。
假如该语言中的所有函数的参数都是按值传递,而且变量只有值类型,没有指针和引用。那么我们根本无法实现最、最基本的,swap这样的函数。
而对于函数式语言,引用,有2个作用
1。为变量添加别名
2。提供一种修改变量值的方式。
例如
# let x=10;;
val x : int = 10
# let y= ref x;;
val y : int ref = {contents = 10}
# y := 5;;
- : unit = ()
# !y;
;;
- : int = 5
# x;;
- : int = 10
y的值通过赋值运算符号被改变,而x的值不受影响。
下面演示 mutable的单元
# let r=ref 13;;
val r : int ref = {contents = 13}
# let s=r;;
val s : int ref = {contents = 13}
# s :=82;;
- : unit = ()
# !r;;
- : int = 82
可以看到,r的值,悄悄的通过s改变了。
我们简单的将其来看作一个指针,那么对于一个指针,它有最简单的2种运算,引用与解引用
比较 引用 解引用
C p=&x; *p;
ml p=ref x; !p;
先看引用。
首先,当一个指针被创建的时候。
1。根据所指向的类型,可以推导出该指针的类型。
2。它的类型就此确定,且不应该再改变。
如果我们不是在它被赋值的时候就把它的类型记录下来,而是到了后面根据它所出现的位置而动态推导它的类型。那么将会出现非常尴尬的,循环依赖的状况。例
如:
(l →
λx:Nat.(!m)x,
m → λx:Nat.(!l)x).
再看解引用。
首先,如果要对一个指针解引用,那么,我们必须要能确定该指针指向的是什么类型。否则我们就无法知道解引用返回的是什么类型。那么就无法对它所在的表达式
进行类型推导,这是违反基本的类型安全规则的。
大多数情况下,我们很容易得知一个指针的类型。
但是,如果我们对该指针进行了某种运算的话。
例如
char* p=&x;
p++;
现在p指向的是什么类型呢?
如果我们再写
*p;
那么它的执行状况将是未知的。
(回想STL,它将iterator明确的分为5种类型。其中最基础的三种是input iterator,output
iterator,forward iterator。)
并不是所有的指针都可以进行++运算的,除非我们知道它后面一块内存存储的是什么类型。而这一点,是无法静态决定的。
例如。
char c;
char* p2=&c;
char* p="hello";
...
p++;
p2++;
此处p与p2具有相同的类型信息,但是一个可以执行++操作符,一个不可以。
而前文已经说过,我们引入类型信息,最最基本的一个用途,就是静态判定一个操作是否合法。即,某些类型不能进行某些运算(例如bool类型不支持++),
于是,我们将可执行不同的操作的变量划归为不同的集合,称之为类型。
而我们此处面临的,就是,类型信息不够丰富,或者说,类型划分不够细致。
或者我们可以这么说,要对一个指针执行++操作,当且进当它指向的是一篇存储着相同类型变量的连续存储区。
但是,再看这个问题。
char a[100]="hi,I'm Charming Sun";
char* p=a;
char* p1=&a[1];
char* s=new (p1) std::string("hello");
p++;
*p; ///??????
此时*p指向的是什么?
如果我们仅仅根据一个指针在初始化时候的上下文来判定一个指针的类型,那么,多少是有些不够的。
再者,当执行p++操作的时候,我们怎么才能知道一个指针是否已经越界了?编译器是无法从无法上做这样的检查的,一切都靠程序员自己。
所以,指针是一个非常棒的,必不可少的语言元素,但是对指针执行任何算术运算,都是危险的。
如何安全化的使用指针呢?
首先。就C/C++而论,指针解引用的时候无非会遇到三种问题。
1.空指针。
2.野指针。
3.越界。
空指针的问题很好办,每次解引用之前检查一下就是了。
(其实如果再度去思索,指针什么情况下会等于NULL?多半是程序员自己给自己下的陷阱)
野指针的问题,出自于垃圾的自动收集(GC).如果该指针指向的位置已经被GC回收了,那么它就是一个野指针。
对于C/C++而言,一个好处就是我们可以明确的知道一个对象在什么时候会被GC收回。但是我们需要手动的检查哪些指针应该被废弃不再使用。
对于C#/java而言,一个好处是永远不必担心一个引用指向的是一个无效的位置,因为GC是非常智能的。但是即便是非常熟练的程序员,除非他就是中间语
言解释器的编写者,否则没有人可以回答出变量或一块内存究竟是在什么地方被GC回收。
由指针,而引发的GC回收的问题,是一个非常复杂的问题,有待进一步研究。
最后说越界。
唯一能防止越界的方式就是,为指针提供更高层的类型。并,每次执行解引用的时候就检查其是否有效。
例如,使用std::vector代替数组,并且,不要用v[10]的方式访问,而要用v.at(10)的方式访问数据成员。
优点是安全性得到了保障,缺点是效率的大大降低。
再看,对于C程序员,常常不可避免的就是使用strcpy族的运算符,由此而引发的以指针越界而引发的缓存区溢出,几乎是近10年最主要的安全漏洞的来
源。幸好人们在今天已经普遍认识到这一点,简单的让编译器告诉newbie,不要使用strcpy!

此博客中的热门博文

少写代码,多读别人写的代码

在windows下使用llvm+clang

tensorflow distributed runtime初窥