什么是类型安全?

343

"类型安全"是什么意思?


https://en.wikipedia.org/wiki/Type_safety - Basil Bourque
13个回答

297

类型安全意味着编译器将在编译期间验证类型,如果您尝试将错误类型分配给变量,则会抛出错误。

一些简单的例子:

// Fails, Trying to put an integer in a string
String one = 1;
// Also fails.
int foo = "bar";

这也适用于方法参数,因为您向它们传递了显式类型:

int AddTwoNumbers(int a, int b)
{
    return a + b;
}

如果我尝试使用以下代码进行调用:

int Sum = AddTwoNumbers(5, "5");

编译器会抛出错误,因为我传递了一个字符串("5"),而它期望一个整数。

在松散类型的语言中,比如JavaScript,我可以这样做:

function AddTwoNumbers(a, b)
{
    return a + b;
}

如果我这样调用它:

Sum = AddTwoNumbers(5, "5");

JavaScript会自动将数字5转换为字符串"5",并返回"55"。这是因为JavaScript使用加号进行字符串连接。如果要使其类型感知,则需要执行类似以下操作:

function AddTwoNumbers(a, b)
{
    return Number(a) + Number(b);
}

或者,可能是:

function AddOnlyTwoNumbers(a, b)
{
    if (isNaN(a) || isNaN(b))
        return false;
    return Number(a) + Number(b);
}

如果我这样调用它:

Sum = AddTwoNumbers(5, " dogs");

Javascript会自动将5转换成字符串,并将它们连接起来,返回"5 dogs"。

并非所有的动态语言都像javascript一样宽容(实际上一个动态语言不隐含松散类型语言(参见Python)),有些语言在类型转换无效时会给出运行时错误。

虽然这很方便,但也容易出现错误,只能通过测试运行程序才能识别。个人而言,我更喜欢让编译器告诉我是否犯了这种错误。

现在,回到C#...

C#支持一种语言特性叫做 协变性,这基本上意味着你可以用子类型替换基类型而不会引发错误,例如:

 public class Foo : Bar
 {
 }

在这里,我创建了一个新类(Foo),它是 Bar 的子类。现在我可以创建一个方法:

 void DoSomething(Bar myBar)

你可以使用Foo或者Bar作为参数调用它,两者都能正常工作而不会引发错误。这是因为C#知道Bar的任何子类都将实现Bar接口。

然而,你不能做相反的操作:

void DoSomething(Foo myFoo)

在这种情况下,我无法将Bar传递给此方法,因为编译器不知道Bar实现了Foo的接口。这是因为子类可以(并且通常会)与父类非常不同。

当然,现在我已经走得太远,超出了原始问题的范围,但这些都是要了解的好东西 :)


36
我认为这个答案是错误的:类型安全并不一定在编译时强制执行。例如,我了解到Scheme被认为是类型安全的,但它是动态检查的(类型安全在运行时强制执行)。这基本上是对本杰明·C·皮尔斯所著《类型与编程语言》介绍的改述。 - Nicolas Rinaudo
17
你所描述的是多态性(polymorphism),而不是协变性(covariance)。协变性通常用于泛型中。 - IS4
@NicolasRinaudo请注意,动态语言和静态语言之间的差距正在被动态编译和“解释”语言的预编译所缩小,并且通过“编译”语言中的反射来实现。反射允许运行时鸭子类型,例如,编译语言可以说“嘿,这有一个Quack()方法,我会调用它并看看会发生什么”。类似Pascal的语言也经常具有(可选的)运行时溢出检查,导致那些在运行时发生的“编译器”错误“无法将整数放入8位目标{core dump}” - Code Abominator
3
你的例子提到了一个叫做“强类型”的概念,它与类型安全不同。类型安全是指一种语言能够在执行或编译时检测类型错误。例如,Python 是弱类型但类型安全的。这个答案应该被标记,因为它非常具有误导性。 - dantebarba

95

类型安全不应与静态/动态类型或强/弱类型混淆。

一个类型安全的语言是指只有符合数据类型规范的操作才能对数据进行执行。也就是说,如果您的数据属于类型X,并且X不支持操作y,那么该语言将不允许您执行y(X)

这个定义并没有规定检查时间。它可以在编译时(静态类型)或运行时(动态类型)进行检查,通常通过异常来实现。它也可能是两者结合:某些静态类型语言允许您将数据从一种类型转换为另一种类型,并且必须在运行时检查类型转换的有效性(假设您正在尝试将Object转换为Consumer - 编译器无法知道它是否可接受)。

类型安全并不一定意味着强类型语言,一些语言被公认为是弱类型语言,但仍然可以被认为是类型安全的。例如JavaScript:它的类型系统非常弱,但仍然是严格定义的。它允许自动转换数据(比如将字符串转换为整数),但必须遵循明确定义的规则。我所知道的没有一个JavaScript程序会表现出未定义的行为,如果您够聪明(我不是),那么在阅读JavaScript代码时应该能够预测会发生什么。

类型不安全的编程语言的一个例子是C:在数组边界之外读取/写入数组值的行为在规范中是未定义的。无法预测会发生什么。C是一种具有类型系统但不安全的语言。


1
其他不安全类型的语言有哪些例子?你所说的“在数组边界之外写入数组值是由规范定义的未定义行为。无法预测会发生什么”是什么意思?就像JavaScript一样,它会返回undefined吗?还是真的可能发生任何事情?你能举个例子吗? - ARK
1
@AkshayrajKore 确定。数组是内存指针,因此如果越界写入,可能会覆盖其他程序的数据 - 这可能不会产生任何影响,导致程序崩溃,或者擦除硬盘上的数据 - 这是未定义的,并取决于谁正在读取那一部分内存以及它对其做出的反应。 - Nicolas Rinaudo
@Nicolas Rinaudo 这不正确。你应该了解虚拟内存。每个进程都有自己的虚拟地址空间,因此一个进程不能以这种方式“覆盖另一个程序的数据”。 - ilstam
你是正确的 - 这应该是“您可能正在覆盖程序内存的另一部分”,包括程序本身在内,我相信是这样的。 - Nicolas Rinaudo
1
@NicolasRinaudo 程序的代码段在虚拟地址空间中被映射为只读。因此,如果您尝试写入它,那么会导致分段错误并使程序崩溃。同样,如果您尝试写入未映射的内存,那么会导致页面错误并再次崩溃。然而,如果您运气不好,可能会覆盖进程的堆栈或堆(如其他变量或其他内容)的数据。在这种情况下,您可能不会立即崩溃,这甚至更糟糕,因为您直到(希望)以后才会注意到该错误! - ilstam
非常好的回答。只是补充一下,Python是另一个广为人知的类型安全语言,它是动态类型的。 - dantebarba

47

类型安全不仅是编译时的约束,也是运行时的约束。即使经过这么长时间,我觉得我们仍然可以进一步阐明。

与类型安全相关的有两个主要问题。内存**和数据类型(以及其对应的操作)。

内存**

char通常需要每个字符1字节,或8位(取决于语言,Java和C#存储Unicode字符需要16位)。 int需要4个字节,或32位(通常)。

视觉上:

char: |-|-|-|-|-|-|-|-|

int : |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-|

类型安全语言不允许在运行时将int插入到char中(这应该会引发某种类别转换或内存不足异常)。但在类型不安全的语言中,你会覆盖3个相邻字节的现有数据。

int >> char:

|-|-|-|-|-|-|-|-| |?|?|?|?|?|?|?|?| |?|?|?|?|?|?|?|?| |?|?|?|?|?|?|?|?|

在上述情况下,右侧的3个字节将被覆盖,因此任何指向该内存的指针(例如3个连续的char)预期获得可预测的char值,现在都变成了垃圾。这会导致程序中出现undefined行为(或更糟糕的是,在其他程序中可能会出现问题,具体取决于操作系统如何分配内存-这在当今时代非常不可能发生)。

** 虽然第一个问题技术上与数据类型无关,但类型安全语言本质上解决了它,并将其可视化地描述给那些不知道内存分配“外观”的人。

数据类型

更微妙和直�的类�问题是两个数�类�使用相�的内存分�。以 int � unsigned int 为例,它们都是32�。(也�以是 char[4] 和 int,但常�的问题是 uint vs. int)。 |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| 类��安全的语言�许程�员引用正确分�的32�间隔,但当 unsigned int 的值被读入 int 空间(或�之亦然)时,我们�次出�了未定义的行为。想象一下,在银行程�中这�能会引起什么问题:
“哥们�我�支了30�元,�在�剩65506�元���
当然,银行程�使用更大的数�类�。😉 LOL�
正如其他人已�指出的那样,下一个问题是类�的计算�作。这已�足够涵盖了。
速度�安全性
今天大多数程�员�必担心这些问题,除�他们使用�C或C++这样的东西。这两�语言都�许程�员在�行时轻���类�安全(直�内存引用),尽管编译器尽最大努力将�险最�化。然而,这并�全是�事。
这些语言之所以如此计算速度快,�因之一在�它们�需�在�行时�作期间验�类�的兼容性,例如Java。它们�定开�人员是善良�智的人,�会将字符串和 int 相加,为此,开�人员�得了速度/效�的奖励。

确保类型安全性的确会对速度产生限制,但是考虑到 C/C++ 代码更容易受到缓冲区溢出攻击和其他相关攻击的影响,确保类型安全性非常重要。通过确保类型安全性可以减少此类攻击的威胁。 - Prateek93a
1
哇..讲解得非常好。:-) ..谢谢 Gr3go - Mohit Tomar

29
许多答案在这里混淆了类型安全和静态类型和动态类型。动态类型语言(如Smalltalk)也可以是类型安全的。
简而言之,如果没有操作导致未定义的行为,则认为语言是类型安全的。许多人认为需要显式类型转换的要求是语言被严格类型化的必要条件,因为自动转换有时会导致定义良好但意外/不直观的行为。

1
等等,你对类型安全的定义中居然没有提到“类型”这个词 :D 如果没有任何操作会导致未定义的行为。 - VasiliNovikov
1
此外,我不同意这样的定义。我认为类型安全确切地意味着:1. 存在类型 2. 编译器知道它们,并进行适当的检查。 - VasiliNovikov

15

“类型安全”的编程语言指以下几点:

  1. 您不能从未初始化的变量中读取
  2. 您不能超出数组边界进行索引
  3. 您不能执行未经检查的类型转换

7

一个文科专业人士的解释,而不是计算机科学专业人士:

当人们说某种语言或语言特性是类型安全的时候,他们指的是这种语言将帮助你防止,例如,将不是整数的东西传递给一些期望整数的逻辑。

例如,在C#中,我定义一个函数如下:

 void foo(int arg)

编译器将会阻止我这样做:
  // call foo
  foo("hello world")

在其他语言中,编译器不会阻止我(或者没有编译器...),所以字符串将被传递给逻辑,然后可能会发生一些糟糕的事情。
类型安全的语言试图在“编译时”捕获更多错误。
不过,使用类型安全的语言,当你有一个像“123”这样的字符串,并且想要像操作整数一样操作它时,你需要编写更多的代码将字符串转换为整数;或者当你有一个整数像123,想要在消息中使用它,比如,“答案是123”,你需要编写更多的代码来转换/强制转换为字符串。

5
文科专业的人可能会说“一个”解释 :) 你也混淆了静态类型和动态类型。 - ididak
1
文科专业不是“主修”,而是“专业”。 - Corey Trager

6
为了更好地理解,请观看下面的视频,演示了类型安全语言(C#)和非类型安全语言(JavaScript)中的代码。

http://www.youtube.com/watch?v=Rlw_njQhkxw

现在是一段较长的文本。

类型安全意味着防止类型错误。当不知情地将一个数据类型分配给另一个数据类型时,会发生类型错误,从而得到不良结果。

例如,JavaScript 不是一种类型安全的语言。在下面的代码中,“num”是数字变量,“str”是字符串。JavaScript 允许我执行“num + str”,现在猜测它会进行算术运算还是连接操作。

现在对于下面的代码,结果是“55”,但重要的是所创建的混淆是什么样的操作它会执行。

这是因为 JavaScript 不是一种类型安全的语言。它允许设置一种类型的数据到其他类型而没有限制。

<script>
var num = 5; // numeric
var str = "5"; // string
var z = num + str; // arthimetic or concat ????
alert(z); // displays  “55</script>

C#是一种类型安全的语言。它不允许将一个数据类型分配给其他数据类型。下面的代码不允许在不同的数据类型上使用“+”运算符。

enter image description here


5

概念:

Type Safe(类型安全)的含义非常简单,它确保变量的类型是安全的,比如:

  1. 不允许错误的数据类型,例如不能使用整数类型来保存或初始化字符串类型的变量
  2. 无法访问超出边界的索引
  3. 只允许特定的内存位置

因此,这一切都关乎变量储存的类型安全。


4

类型安全意味着变量、返回值或参数的数据类型必须符合特定的标准。

实际上,这意味着7(整型)和“7”(字符串类型中的引号字符)是不同的。

PHP、Javascript和其他动态脚本语言通常是弱类型的,在尝试将“7”+3相加时会将(字符串)“7”转换为(整数)7,尽管有时需要显式执行此操作(而Javascript使用“+”字符进行连接)。

C/C++/Java将无法理解它,或将结果串联成“73”。类型安全通过使类型要求明确来防止代码中出现此类错误。

类型安全非常有用。解决上述“7”+3的问题是将(int) “7”+3进行类型强制转换,结果为10。


3

请尝试理解以下内容...

TypeSafe 意味着在编译时对变量进行静态检查以适当地赋值。例如,考虑字符串或整数。这两种不同的数据类型不能相互赋值(即,您不能将整数分配给字符串,也不能将字符串分配给整数)。

对于非类型安全行为,请考虑以下内容:

object x = 89;
int y;

如果你尝试这样做:

y = x;

编译器会抛出一个错误,指出无法将System.Object转换为整数。您需要显式地进行转换。一种方法是:
y = Convert.ToInt32( x );

上面的赋值操作不是类型安全的。类型安全的赋值是指可以直接将类型分配给彼此。
在ASP.NET中,非类型安全的集合随处可见(例如应用程序、会话和视图状态集合)。这些集合的好消息是(最小化多个服务器状态管理考虑),你可以将任何数据类型放入这三个集合中。坏消息是:由于这些集合不是类型安全的,当你取回值时需要适当地进行类型转换。
例如:
Session[ "x" ] = 34;

代码可以正常工作。但要将整数值重新赋回,您需要执行以下操作:

int i = Convert.ToInt32( Session[ "x" ] );

阅读有关泛型的文章,这种工具可以帮助您轻松实现类型安全的集合。

C#是一种类型安全的语言,但请注意C# 4.0的文章;有趣的动态可能性正在出现(C#基本上获得了Option Strict:Off,这是一件好事吗...我们会看到)。


就我个人而言,我讨厌 Convert.To 这种写法,为什么不直接使用安全转换呢?这样还能减少调用栈上的函数调用次数。 - FlySwat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接