什么是类型安全

转自:http://hi.baidu.com/chenfalei/blog/item/f33ac0133500ac21dd540186.html

编程语言的最终梦想:静态类型安全

常听人说“强类型”。但个人对强类型都有不同的理解。

有的认为C++就是强类型,有的认为C也是强类型。因为它们都有类型检查。

可见,如果没有一个明确的定义,谈“强类型”不免是自说自话。

那么,可以给“强类型”下一个什么样的定义呢?

最原始的定义是这样: 静态类型系统将检查所有的错误。只要通过了静态类型检查,程序将不会有bug.

但是,明显这是不现实的,因为有些bug是纯粹逻辑上的。比如说:

print "hell world"

少打了一个o, 是bug, 但除非把自然语言识别加进来,计算机对此无能为力。

于是,我们把强类型定义为:

静态类型系统检查所有类型匹配的错误,只要通过了类型检查,就保证不会有把苹果当成汽油的事发生。

至于类型匹配错误,我们把它定义成:

当你试图把一个变量当作类型A来处理时,却意外发现它不是类型A的。它可能是类型B的,也可能什么都不是。

再谨慎一点,我们可以排除掉使用downcast的情况。如果你非要

(汽油)new 苹果(), 自己找死,我们也管不着了。

这可以说是所有支持类型的语言所追求的最高境界。想想吧,一旦你的程序通过编译,不管它有几百万行,你都可以自信地说:我的程序里面最多只有逻辑错误了。

多好啊!!!

那么,有什么语言达到了这个要求呢?C吗?C++吗? Java吗?

不幸的是,它们都不是。

先说C,

union大家都熟悉吧?你把两种不同的类型混杂于一块内存空间。然后用一个变量来标识它的真正类型。

但是,如果你的程序一旦错误地把类型A当成类型B, 编译器不会警告你的。

还有,char* p = "hello world";

这句话,也不是类型安全的。你如果做p[0]='x'; 编译器不会抱错,因为p的类型是char*, 而对char* 做下标操作是完全合法的。

C还有好多其它不严格的地方。

C++呢?上面提到的两个C的问题它同样有。

还有一些不那么明显的类型漏洞。

1。 placement new. 看这个代码:

X* px = new X();

new (px) Y();

px->m();

这里,编译器不会报错,但px->m()的结果却是未知的。

有人解释说:这是因为px指向的对象已经不存在了。但是,我们不是研究它为什么失败,你可以有一千个理由解释你的程序为什么崩溃,但是,事实很简单:它崩溃了。因为你想把一个不是X的东东当成X来使用。

2。delete p;

很惊讶是吗?

X* px = new X();

delete px;

px->m();

简简单单地就绕过了编译器,得到了一个类型匹配错误。你也可以较它悬挂指针错误,但是,根据我们对类型匹配错误的定义,我们想把px当成X*, 但实际上它并不是我们期望的类型,所以,它也是类型错误的一种。否则,不免对其他的类型漏洞不够公平,

你这个px->m()并不必例子一里的px->m()安全一丁点。 为什么我是类型错误,你就不是?

再看Java, 相比于C/C++, Java在类型安全上有了长足的进步。上面提到的问题,在Java里全都不存在了。Java彻底地扔掉了union, 扔掉了指针,这些设计在效率上可能值的探讨。但是,一个明显的事实是,它的类型系统更安全了。

Java采用了垃圾收集机制。这对很多习惯于又程序管理内存的C/C++程序员来说是有争议的。但是,如果不考虑效率等问题,只从类型安全的角度去看,它免除了delete带来的类型安全漏洞,朝真正意义上的静态类型安全又近了一步。

不过,Java也不是完整的静态类型安全。

缺乏泛形的支持,只是程序员要频繁地做类似(苹果)obj;这样的downcast. 大大地加大了程序的隐患。好在java也认识到这一点,各种不同的努力都在试图往java中加入generics. 不过,这不是本文要讨论的目的,而且,毕竟,我们的前提是不考虑downcast.

那么,不用downcast, java程序就是静态类型安全的吗?

请看这段代码:

String[] sa = new String[100];

sa[0] = "hello world";

Object[] oa = sa;

oa[0] = new Integer(1);

System.out.println(sa[0]);

通!火药桶爆炸了!

究其原因,是在于Object[] oa = sa;这一句。

根据类型理论里的协变原理,只有只读的Object[] 才能是String[]的父类型。但Java里并没有只读数组这么个类型,悲剧就这样发生了。

同样的隐患也存在于一些泛形的语言之中。如果语言想提供协变的支持,如,想让MyTemplate< Object> 是MyTemplate<String>的父类型,那么,Object类型的引用在MyTemplate的定义中也不能是随意的。它必须符合 协变的规定,只处于协变的位置上。

还有一个静态类型系统无能为力的地方,如:

String s[] = new String[2];

s[2] = "hello world";

砰!悲剧再次发生了。.

它符合我们对类析匹配错误的定义:你试图把s[2]当作String来处理,但实际上它不是。

这个问题存在于几乎各种支持数组的语言。

因为数组的的长度可以是任意的,很难设计一个类型系统来静态保证数组的边界不会被越过。

Pascal试图这样做,它通过在类型上附加数组的长度来帮助静态类型系统工作。

但是,一个规定了大小的数组虽然可以保证类型安全,可因为不同大小的数组不能互相转换,大大牺牲了程序的灵活性。

Java的解决方案很现实:既然这样做不好做,那就算了吧。我在运行时加入边界检查,虽然不能静态地保证数组的边界 安全,至少可以保证不出大漏子。 其实,理论上说,Java的做法是通过改变数组的动态语义,把一个未定义的类型匹配错误转换成了一个相对安全的定义好的对象状态错误。

是啊,哪有十全十美的事情呢?毕竟,程序作为一个状态机,总是会有非法状态的。

其实,也还是有另一种方案的。

ML, 这个编程语言界的老前辈。几乎是带类型的functional language的开山祖师了。

它的方案是什么?

简单, 不能再简单了,那就是:干脆不要数组!

听起来吓人,但是,这是和functional language的方针一致的,因为数组是一个要求副作用的数据结构,而functional是要摒弃副作用,所以,functional弃用数组也是很 自然的。(具有讽刺意味的是,ML的一些扩展还是把数组加了进来,因为数组是实现象hash table之类的数据结构的必经之路。)

说了这么多,基本上把可能的类型系统漏洞都从阴沟里翻了出来, 晒晒太阳。

C#怎么样? 我只是对C#惊鸿一瞥,当时看看,现在许久不用,都忘掉了。不过,这个数组越界的问题,相信仍然存在。

如果我们现实一点,把数组越界这个不大可能根本解决的问题抛开,那么,唯一让我对C#担心的,就是这个数组的协变问题了

【C/C++】什么是类型安全的更多相关文章

  1. 04_Swift2基础之类型安全和类型推测+字面量+类型别名

    1. 类型安全和类型推测 1> 类型安全 Swift 是一个 _类型安全(type safe)_ 的语言.类型安全的语言可以让你清楚地知道代码要处理的值的类型.如果你的代码需要一个`String ...

  2. c++ 类型安全

    类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图访问自己没被授权的内存区域.“类型安全”常被用来形容编程语言,其根据在于该门编程语言是否提供保障类型安全的机制:有的时候也用“类型安全”形容 ...

  3. 类型安全且自动管理内存的返回 std::string 的 sprintf 实现

    在这篇博文里,我提到了一个例子,说的是使用C++实现类型安全的printf.这个例子很惊艳,但是在我写程序的时候,并非那么"迫切"地需要它出现在我的工具箱中,因为它并不比普通的pr ...

  4. Qt信号槽机制的实现(面试的感悟,猜测每一个类保存的一个信号和槽的二维表,实际使用函数指针 元对象 还有类型安全的检查设定等等)

    因为面试时问了我这道题,导致我想去了解信号槽到底是如何实现的,于是贴着顺序看了下源码,大致了解了整个框架.网上关于信号槽的文章也很多,但是大部分都是将如何应用的,这里我就写一下我所理解的如何实现吧, ...

  5. Swift编程语言学习1.3——类型安全和投机型

    Swift 是类型安全(type safe )语言.类型安全的语言可以让你清楚地知道代码被处理值类型.假设你需要一个代码String.你绝对不能进去一个不小心传球Int. 因为 Swift 它是类型安 ...

  6. Matlab与.NET基于类型安全的接口混合编程入门

    原文:[原创]Matlab与.NET基于类型安全的接口混合编程入门 如果这些文章对你有用,有帮助,期待更多开源组件介绍,请不要吝啬手中的鼠标. [原创分享]Matlab.NET混编调用Figure窗体 ...

  7. Swift语言指南(四)--类型安全和类型推断

    原文:Swift语言指南(四)--类型安全和类型推断 Swift是一门类型安全语言,类型安全语言需要代码里值的类型非常明确.如果你的代码中有部分值需要String类型,你就不能错误地传递Int. 鉴于 ...

  8. 自动类型安全的.NET标准REST库refit

    在SCOTT HANSELMAN 博客上看到一个好东西<Exploring refit, an automatic type-safe REST library for .NET Standar ...

  9. Effective Java 第三版——33. 优先考虑类型安全的异构容器

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  10. Springboot的常规属性配置和类型安全配置

    1.类型常规配置: 通过在properties文件中注入键值对的方式,在java代码中通过@Value注解注入值 2.类型安全配置 将要注入的属性包装成一个类,给类加上注解:ConfigrationP ...

随机推荐

  1. shell编程初探

    #! /bin/sh #这是神圣丁的第一个shell脚本 name="陈培昌" echo "我就喜欢\"$name\"" echo '我就喜 ...

  2. Acwing-198-反素数(约数, 数学)

    链接: https://www.acwing.com/problem/content/200/ 题意: 对于任何正整数x,其约数的个数记作g(x),例如g(1)=1.g(6)=4. 如果某个正整数x满 ...

  3. Springboot项目报错【java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader】

    1.发生问题: 升级了JDK9,发现原先的springboot项目起不来了,以为是maven中jdk配置有问题. 于是在pom中添加了 <plugin> <groupId>or ...

  4. JS转换/Date(-28800000)/格式

    去除/Date() if (value.includes('/Date')) { var re = /-?\d+/; value = re.exec(value); value = new Date( ...

  5. 【Python之路】特别篇--Python内置函数

    abs() 求绝对值 i = abs(-100) print(i) # 100 all() 循环里面的参数 如果每个元素都为真,那么all返回值为真  假: 0 False None "&q ...

  6. python Print 输出

    print 默认输出是换行的,如果要实现不换行需要在变量末尾加上逗号 , #!/usr/bin/python # -*- coding: UTF-8 -*- x="a" y=&qu ...

  7. springboot错误1 Failed to execute goal org.springframework.boot:spring-boot-maven-plugin

    关于Springboot打包错误的问题 | Failed to execute goal org.springframework.boot:spring-boot-maven-plugin https ...

  8. codeforces412A

    Poster CodeForces - 412A The R1 company has recently bought a high rise building in the centre of Mo ...

  9. Centos系统下载

    在学习或者工作当中,难免会用到不同版本的CentOS镜像,下面就简单的介绍一下如何正确并快速的下载所需要的CentOS镜像. 一.官网下载 官网地址:https://www.centos.org/此种 ...

  10. 邻居子系统输出 之 neigh_output、neigh_hh_output

    概述 ip层在构造好ip头,检查完分片之后,会调用邻居子系统的输出函数neigh_output进行输出,输出分为有二层头缓存和没有两种情况,有缓存时调用neigh_hh_output进行快速输出,没有 ...