目录
2.1 Strong, Static Typing 强语言、强静态类型语言
2.1.1 ADA is Strong, Static Typing
2.1.2 C is Weak, Static Typing
2.3.3 Cost of Runtime Checking
2.5 Semantics and Safety 语义和安全性
3. ADA: SOME INTERESTING HIGHLIGHTS
1. ADA的历史
mid 1970s:
US DoD has too many languages for myriad embedded devices; want: safe, modular, cross platform, high level
美国国防部有太多的语言用于众多的嵌入式设备;希望:安全、模块化、跨平台、高水平
1977:
solicit proposals for new language
征求对新语言的建议
1979:
Honeywell’s proposal chosen; christened Ada
Honeywell的提案被选中;被命名为 "Ada"。
10 Dec 1980:
MIL-STD-1815 approved
1983:
ANSI/MIL-STD-1815A (“Ada 83”)
1995:
ISO-8652:1995 (“Ada 95”) USAF funds GNAT Compiler, now in GCC
2012:
ISO/IEC 8652:2012 (“Ada 2012”)
2. ADA的特点
2.1 Strong, Static Typing 强语言、强静态类型语言
静态类型语言 VS 动态类型语言
在静态类型语言中,变量的类型必须先声明,即在创建的那一刻就已经确定好变量的类型,而后的使用中,你只能将这一指定类型的数据赋值给变量。如果强行将其他不相干类型的数据赋值给它,就会引发错误。例如C。类型检查是在编译器编译时执行的!
在动态类型语言中,没有像静态类型语言那样的限制,而是你将什么类型的数据赋值给变量,这个变量就是什么类型。如Python,JavaScript。类型检查是在编译器运行时检查的!
强类型 VS 弱类型
强弱之分,体现在对类型的检查严格程度上,弱类型语言对于变量类型的检查比较宽松,容忍隐式类型转换这种事情的发生。何为隐式类型转换,一般有两种形式:
- 相关类型之间隐式转换
- 不相关类型之隐式间转换
举例子来说,一个int类型的数据与一个float类型的数据相加,最终的结果是一个float类型的数据,这个过程就发生了隐式类型转换,int类型数据首先被转成float类型,然后与另一个float进行操作,这便是相相关类型之间隐式转换。
一个int类型数据与一个字符串类型数据相加,竟然没有发生错误,得到的结果是一个字符串,int类型数据隐式转换为字符串,可他们原本是两个不相干的数据类型,这种就是第二种隐式转换。
在弱类型语言中,变量可以隐式强制转换为不相关类型,而在强类型语言中则不可以。
2.1.1 ADA is Strong, Static Typing
2.1.2 C is Weak, Static Typing
2.2 Module System
Automatic dependency resolution 自动解决依赖关系
Information Hiding, Encapsulation 信息隐藏、封装
2.3 Portable
对于一个signed integer type,如果执行的操作因为它超出了该类型的基本范围,而不能提供正确的结果,就会引发异常Constraint_Error。
2.3.1 ADA
2.3.2 C
2.3.3 Cost of Runtime Checking
But stronger static checking can improve performance too.
2.4 Readability
2.4.1 Purposeful Verbosity
这指的是一种编程语言的设计特性,其中为了确保代码清晰明了,通常需要编写更多的代码。这种语言通常比较详细,需要明确指定许多细节,而不是依赖默认行为或简洁的语法。这样的设计可以提高代码的可读性和可维护性。例如,在Ada语言中,这种特性比较明显。
2.4.2 Intended Redundancy
这是指编程语言设计时故意引入的冗余元素,以提高代码的可读性、可理解性或错误检测能力。这种冗余可能表现为需要多次指定同一信息,或者需要对相同的概念提供多种描述。例如,Ada语言强调类型的明确性和可读性,会出现这种冗余。
2.4.3 Unambiguous Semantics
这是指编程语言的语义设计得非常明确,没有或极少有模棱两可的地方。这样的设计可以使得程序的行为更加预测和一致,有助于减少由于误解语言特性而引入的错误。这是Ada和C语言等许多编程语言的共有特性。
2.4.4 Ada Prefers Readability
Ada更偏向于代码的可读性:Ada是一种设计上注重明确性和可读性的编程语言。Ada的设计原则之一就是使得阅读程序比编写程序更容易。
为什么重视代码的可读性:一段代码可能会被阅读超过10次,但只被编写一次。因此,优化对整体成本贡献最大的任务是有意义的,也就是优化代码的可读性。这也包括了阅读你的代码的程序,如编译器和其他工具。
Ada与其他编程语言的对比:相比之下,Ruby、JavaScript、Perl这些语言更注重的是代码编写的便捷性和简洁性,而不是可读性。这种差异在语言的语义设计上有所体现。例如,JavaScript中,有些相等性比较会产生直觉上难以理解的结果,这是由于JavaScript的类型转换规则引起的。以下是一些JavaScript的例子:
JavaScript:
>>> false == 'false';
false
>>> false == '0';
true
>>> '' == '0';
false
>>> 0 == '';
true
>>> 0 == '0';
true
这些JavaScript的例子展示了一些让人困惑的语义设计,这种设计可能会影响代码的可读性和可理解性。与之相比,Ada语言会更倾向于避免此类问题,提高代码的可读性和明确性。
-
false == 'false';
在JavaScript中,这返回false
。虽然字符串'false'在逻辑上代表假,但JavaScript不会自动将字符串'false'转换为布尔值false
。 -
false == '0';
这返回true
。在JavaScript中,false
会被转换为数字0,字符串'0'也会被转换为数字0,所以二者相等。 -
'' == '0';
这返回false
。空字符串''
会被转换为0,但是字符串'0'被转换为数字时是1,所以二者不相等。 -
0 == '';
这返回true
。空字符串''
和数字0在JavaScript中被认为是相等的。 -
0 == '0';
这返回true
。因为字符串'0'在进行比较时会被转换为数字0。
2.5 Semantics and Safety 语义和安全性
-
运行时检查:Ada语言支持运行时检查,这包括有符号整数溢出、数组索引越界、访问未分配的内存、范围错误等。这种设计可以将难以追踪的bug(称为heisenbugs)转化为崩溃,从而更容易进行错误定位。
-
对实时嵌入式和关键系统的安全性的影响:这些运行时检查的设计有助于提高实时嵌入式和关键系统的安全性。在这些系统中,一旦出现错误,可能会导致严重的后果。因此,能够早期检测并处理错误是非常重要的。
2.6 Application Areas 应用领域
Ada语言主要应用于实时嵌入式系统领域,这包括 Avionics 航空电子设备、Railway 铁路、Defence 国防、Space 太空、Robotics 机器人、Crypto 加密技术等。此外,Ada也应用于Muen验证分离内核,这是一种通过SPARK进行形式验证的系统。SPARK是Ada的一个子集,专注于安全和可靠性,能够支持形式化的程序验证。
2.7 与MISRA C的比较
MISRA C是一种用于汽车领域的“安全”C语言子集,但并非所有规则都是可强制执行的。相比之下,Ada本身就比C更“安全”,有更好的封装性。而SPARK则是一个比Ada更“安全”的子集,具有相同的运行时语义。
Ada拥有更强的类型系统,可以提供更多的静态保证。同时,Ada也提供了更多的运行时检查,例如数组索引越界、动态内存访问等。
因此,相比于C语言(包括其安全子集MISRA C),Ada语言(以及其子集SPARK)在设计上更注重安全性和可靠性,更适合应用于需要高度安全保障的领域。
3. ADA: SOME INTERESTING HIGHLIGHTS
3.1 Packages
Spec the interface the implementation says “what”
says "how' the implementation Body
3.1.1 Packages: with and use
3.2 Types
Type equality is by name
这段Ada代码尝试将SecondType
类型的变量J
赋值给FirstType
类型的变量I
。虽然两种类型的范围都是0到10,但在Ada语言中,这两种类型被视为不同的类型。即使它们的范围相同,它们也不能直接相互赋值。
Ada是一种静态、强类型的编程语言,这意味着你不能将一个类型的值直接赋给另一个类型的变量,除非这两个类型是相同的,或者已经定义了从一个类型到另一个类型的转换。
在这段代码中,如果你想将J
赋值给I
,你需要进行显式的类型转换,如下所示:
I := FirstType(J);
以上代码将J
显式地转换为FirstType
类型。需要注意的是,显式的类型转换可能会有潜在的风险,例如,如果J
的值不在FirstType
的范围内,这将会在运行时引发异常。但在这个特定的例子中,由于FirstType
和SecondType
的范围是相同的,所以这种情况不会发生。
3.2.1 Type Casts and Errors
这段Ada代码中的关键问题是在类型转换时可能会出现错误。这个问题的来源是BigType
和LittleType
类型的范围不同。
在这段代码中,BigType
类型的变量I
从用户那里获取一个整数输入,然后尝试将它转换为LittleType
类型并赋值给J
。
问题是,如果用户输入的值在BigType
的范围(0..20)内,但超出了LittleType
的范围(0..10),那么在类型转换时就会引发运行时异常。例如,如果用户输入了15,这个值对于BigType
类型是有效的,但对于LittleType
类型是无效的。
3.2.2 Subtypes
This compiles and executes with no errors.
Casting is effectively implicit between subtypes.
3.3 Arrays
3.4 Procedures and Functions
Nesting, static scope
Functions with side-effects (disallowed in SPARK)
3.5 Parameter Passing
names come from names of formal parameters (which is why they must match between package spec and body) 名称来自于正式参数的名称(这就是为什么它们必须在包的规格和主体之间匹配)。
3.6 Parameter Modes
in: read only
out: write only
in out: read/write
Checked statically by the type system
由类型系统静态检查
Allows e.g. all records to be passed by pointer if the compiler desires.
如果编译器需要,允许所有记录通过指针传递。
Note: Strong typing improving performance over e.g. C
3.7 Implementation Hiding
Vector’s fields can’t be accessed outside the package. Vector的字段不能在包之外被访问。
Why does the Vector record need to be defined in the spec?
在Ada中,有一个关键的设计原则是信息隐藏或者封装。这就意味着实现细节(比如变量的实际数据类型)应当尽可能地隐藏在包(package)的内部,而用户只能通过接口(也就是包的spec部分)来使用这个包。
在这个例子中,Vector
类型在规格(spec)部分被声明为私有(private),这意味着包的使用者不能直接访问其内部结构,例如他们不能直接操作Vector
中的X
、Y
和Z
字段。相反,他们必须通过在规格部分公开的过程(Set
、Print
和Normalise
)来操作Vector
。这种设计提供了良好的封装性,有助于保护数据的完整性和一致性。
然而,Vector
类型的实际定义(即其为一个包含X
、Y
和Z
的记录类型)需要在包的私有部分进行。这是因为包的实现部分需要知道Vector
的具体结构来实现Set
、Print
和Normalise
过程。但是,尽管Vector
类型在私有部分被定义,包的使用者依然不能直接访问其内部结构。
这里的两个问题实际上是相同的,都在询问为什么Vector
记录需要在规格(spec)中被定义。答案是,Vector
的定义实际上是在私有部分进行的,这种设计可以提供良好的封装性,防止数据的误操作。同时,这也是Ada语言的设计原则之一,即尽可能地隐藏实现细节,而只通过接口暴露出必要的操作和功能。
虽然Ada语言在设计上强调了信息隐藏和封装,但在编译阶段,编译器仍然需要知道数据类型的大小,以便为变量分配合适的内存空间。这是为什么在Ada的包规格(spec)部分,需要对Vector
类型进行私有(private)声明。
在这个例子中,当编译VectorTest
程序时,编译器需要知道Vector
类型的大小,以便为变量V
分配足够的内存空间。尽管Vector
的具体定义是在包的私有部分,但由于Vector
在规格部分被声明为私有类型,编译器可以根据私有部分的定义来确定其大小。
这种设计在保证封装性的同时,也确保了编译器可以正确地为类型分配内存。这是一种在保护数据完整性和一致性的同时,确保代码可执行和高效的方法。
3.8 Enumerations 枚举
枚举是一种包含有限个元素的数据类型。在Ada语言中,你可以通过列举所有可能的值来定义枚举类型。例如,以下代码定义了一个Day
枚举,包含了一周的七天:
type Day is (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
Ada还提供了一些内置的属性,可以用于访问枚举类型的第一个元素('First
)和最后一个元素('Last
),以及所有可能的元素范围('Range
)。
你也可以使用枚举类型作为数组的索引类型,如下所示:
Temperatures : array (Day) of Float;
上述代码定义了一个名为Temperatures
的数组,其索引类型为Day
,元素类型为Float
。你可以使用枚举类型的元素来访问和修改数组的元素。
Ada支持对枚举类型的值进行比较。比如,在下述代码中,Monday
(作为Day
类型的'First
)和Sunday
(作为Day
类型的'Last
)被比较:
if Mo < Su then
Ada.Text_IO.Put("Monday < Sunday");
end if;
3.9 标准类型的选择
有关标准类型,下面提供了三种定义方式,并讨论了哪种更好:
type SecondOfDay is range 0 .. 86_400; -- Option 1
type SecondOfDay is new Integer range 0 .. 86_400; -- Option 2
subtype SecondOfDay is Integer range 0 .. 86_400; -- Option 3
-
Option 1:这是最佳选项,因为它定义了一个新的类型,范围是0到86400。这种方式最具有可移植性,因为这个类型不依赖于
Integer
类型的具体实现。 -
Option 2:这个定义创建了一个新的类型,它是
Integer
类型的子类型,范围是0到86400。但是这种定义的可移植性不如Option 1,因为这个类型的具体实现取决于Integer
类型的实现。 -
Option 3:这个定义创建了一个名为
SecondOfDay
的子类型,它是Integer
类型的一个子集,范围是0到86400。这种定义的静态检查能力不如Option 1和Option 2,因为子类型的值可以被隐式地转换为其基类型的值,这可能会引发类型范围外的错误。
因此,如果你需要定义一个在特定范围内的类型,使用Option 1的方式是最好的。