在语言环境中进行可靠的编程。 第2部分-挑战者

具有功能要求的第一部分在这里

声称具有可靠性的编程语言。

按字母顺序-Active Oberon,Ada,BetterC,IEC 61131-3 ST,Safe-C。

免责声明(借口)绝不是一次“全力以赴”的竞选活动,而且它的审查相当学术性-该语言不仅可能具有积极支持的现代开发环境,甚至可能是您平台的编译器。

另一方面,对于有问题的语言,有开放源代码的编译器,并且在当前的软件开发水平下-有趣的是,不太复杂的语法允许您制作个性化的编译器并通过背光和解析器集成到某种Eclipse中。

为了说明语言的清晰度,我选择了Dijkstra关于餐饮哲学家的著名多线程任务的实现。 在语言和论坛的教科书中都有实施,这为我的工作提供了便利-它只是为了适应。 例如,最近有关现代C ++的habr文章包含一个C ++ 17实现供比较。

活跃欧伯龙(2004)


它是根据Pascal,Modula,1988年以来的Oberons,Java,C#,Ada的经验以及应用程序的实际经验而创建的。 它具有OS A2形式的实现,可以在* nix或Windows之上充当运行时。 源A2和链接的编译器

还有一个与Oberon环境无关的Oberon2到C编译器(OOC)项目。 这是一个略有不同的方言,差异如下所述。

Oberon的主要特点是该规范的简洁性。 这是基本Oberon-2上的16页,以及多线程Active扩展上的23页。

简单明了的语法,排除明显的错误。

标识符区分大小写。

使用垃圾回收器(GC)对堆中的对象进行OOP。

它与之前的版本不同,它以Instance.Method(以前是Method(实例))的形式更加熟悉的OOP语法,并支持带有同步原语的多线程。
OOP实现中没有动态调度,这很容易导致情况-他们忘记为新类型添加处理。

可以为流分配优先级和高/实时性,它们不会被GC中断。 以UTF-8数组形式的字符串。

Rantime(Oberon系统)为运行时错误(内存寻址或整数溢出)重新启动失败的过程/模块/线程提供了有趣的机会。

缺点是缺少RAII,并且错误处理方便-通过以下返回码均可实现,以下选项除外。

Oberon-2 OOC


由于Oberon不需要操作系统-它在ANSI C中进行编译,因此不存在任何互操作性问题,因此它对于实验更加方便。 与Active版本的不同-没有内置的多线程语言-而是有一个用于处理PThreads的模块,但是有UTF16,分层模块化和一个用于处理异常的系统模块。

模块3


还有一个来自稍微不同的开发分支的亲戚,其形式为Modula-3。 它是基于Oberon(而不是过度开发的Ada)创建的。 实现在这里

与Active Oberon相比,增加了泛型和异常,还有一些用于Unicode,GUI甚至Postgress的实际工作的库。 与C的简化集成。其他多线程语义。 RAII与WITH相似(类似于在C#中使用)。

但是,Modula 3的开发似乎在2010年停止了。

免责声明 启动WinAOS后,我突然遇到了TRAP(即中止/堆栈跟踪或运行时错误)-即使任务管理器无法正常工作,并且尽管系统/运行时没有崩溃-但只有应用程序,我对可靠性是否由语言确定存在一定疑问编程=(

而且,AOC的发展方式已经足够独立。

餐饮哲学家的资料
MODULE Philo; (* Dining Philosophers Example from Active Oberon Language Report by Patrik Reali *) (* Adapted for running in AOS by Siemargl *) IMPORT Semaphores := Example8, Out; CONST NofPhilo = 5; (* number of philosophers *) VAR fork: ARRAY NofPhilo OF Semaphores.Semaphore; i: LONGINT; TYPE Philosopher = OBJECT VAR first, second: LONGINT; (* forks used by this philosopher *) PROCEDURE & Init(id: LONGINT); BEGIN IF id # NofPhilo-1 THEN first := id; second := (id+1) ELSE first := 0; second := NofPhilo-1 END END Init; PROCEDURE Think; (* Need lock console output *) BEGIN {EXCLUSIVE} Out.Int(first); Out.String(".... Think...."); Out.Ln; END Think; PROCEDURE Eat; BEGIN {EXCLUSIVE} Out.Int(first); Out.String(".... Eat...."); Out.Ln; END Eat; BEGIN {ACTIVE} LOOP Think; fork[first].P; fork[second].P; Eat; fork[first].V; fork[second].V END END Philosopher; VAR philo: ARRAY NofPhilo OF Philosopher; BEGIN FOR i := 0 TO NofPhilo DO NEW(fork[i], INTEGER(i)); NEW(philo[i], i); END; END Philo. Philo.Philo1 ~ 

Ada(1980年,最新有效的2016年标准)


实际上,乍一看,我想要的一切。

甚至更多-有些数字具有精确的浮点计算。 例如,有一个实时线程调度程序,跨线程交换和SPARK语言的经过正式验证的子集。 还有更多。

我认为,如果Ada的可靠性需要一个该死的喇叭,它会附有在困难情况下打电话的说明=)

实施-GNUTaya Ada正在开发中,已通过ISO / IEC标准化。

该标准提供了使用GC的实现,但是对于编译选项,通常不实现。 需要手动进行内存管理-此处可能会出现编程器错误。 但是,该语言适合使用默认堆栈,并且存在带有析构函数的托管类型的概念。 您还可以为每种数据类型定义GC实现,自动释放或引用计数。

Ada参考手册2012包含950页。

除了复杂性外,Ada的缺点还在于其冗长的冗长性,不过,这种冗长性是出于可读性的考虑。 由于语言安全模型的特殊性,很难与外部库集成。

Ada-ru网站上有一篇很好的评论翻译文章-第一个链接。

餐饮哲学家的资料
 -- Code from https://rosettacode.org/wiki/Dining_philosophers#Ordered_mutexes -- ADA95 compatible so can run in ideone.com with Ada.Numerics.Float_Random; use Ada.Numerics.Float_Random; with Ada.Text_IO; use Ada.Text_IO; procedure Test_Dining_Philosophers is type Philosopher is (Aristotle, Kant, Spinoza, Marx, Russel); protected type Fork is entry Grab; procedure Put_Down; private Seized : Boolean := False; end Fork; protected body Fork is entry Grab when not Seized is begin Seized := True; end Grab; procedure Put_Down is begin Seized := False; end Put_Down; end Fork; Life_Span : constant := 20; -- In his life a philosopher eats 20 times task type Person (ID : Philosopher; First, Second : not null access Fork); task body Person is Dice : Generator; begin Reset (Dice); for Life_Cycle in 1..Life_Span loop Put_Line (Philosopher'Image (ID) & " is thinking"); delay Duration (Random (Dice) * 0.100); Put_Line (Philosopher'Image (ID) & " is hungry"); First.Grab; Second.Grab; Put_Line (Philosopher'Image (ID) & " is eating"); delay Duration (Random (Dice) * 0.100); Second.Put_Down; First.Put_Down; end loop; Put_Line (Philosopher'Image (ID) & " is leaving"); end Person; Forks : array (1..5) of aliased Fork; -- Forks for hungry philosophers -- Start philosophers Ph_1 : Person (Aristotle, Forks (1)'Access, Forks (2)'Access); Ph_2 : Person (Kant, Forks (2)'Access, Forks (3)'Access); Ph_3 : Person (Spinoza, Forks (3)'Access, Forks (4)'Access); Ph_4 : Person (Marx, Forks (4)'Access, Forks (5)'Access); Ph_5 : Person (Russel, Forks (1)'Access, Forks (5)'Access); begin null; -- Nothing to do in the main task, just sit and behold end Test_Dining_Philosophers; 


BetterC(2017年Dlang子集,原始D-2001,D 2.0-2007)


考虑了最现代的实现。 该语言的完整描述相当长,长达649页, 请参见原始站点

实际上,这是D语言,但受-betterC开关的限制。 为什么会这样呢?

由于标准库D是由Alexandrescu开发的Phobos,事实证明它非常狡猾,完全基于模板构建。 该主题的关键是Phobos在内存消耗方面是不可控制的。

在BetterC模式下丢失的最重要的事情是多线程,GC,字符串,类(结构仍然保留-它们在功能上很接近-仅在堆栈上)和异常(RAII和try-finally仍然保留)。

但是,可以用全D编写程序的一部分,而用D-BetterC编写关键的部分。 还有一个系统属性函数来控制不使用危险效果:纯安全 @nogc。

语言创造者为政权辩护

然后挤压 -什么被切断,什么仍然可用。

字符串包含在Phobos中-尝试在BetterC中使用它们会导致基本操作(如将字符串输出到控制台或串联)上实例化模板的推理错误。 在全D模式下,堆上的行也是不可变的,因此对其进行操作会导致内存混乱。

我不得不多次抱怨编译器中的错误。 但是,对于与C ++竞争复杂性的语言而言,这不足为奇。 在编写本文时,我还不得不面对4个错误-尝试使用新的编译器构建dlangide时出现了两个错误,而移植哲学家问题时则遇到了两个错误(例如,使用beginthreadex时崩溃)。

该模式只是最近才出现,并且由BetterC模式限制引起的错误已在链接阶段消除。 要事先了解这一点,需要精确裁剪语言的哪些功能-通常必须直接进行操作。

餐饮哲学家的资料
 // compile dmd -betterC import core.sys.windows.windows; import core.stdc.stdio; import core.stdc.stdlib : rand; //import std.typecons; // -impossible ( //import std.string; - impossible extern (Windows) alias btex_fptr = void function(void*) /*nothrow*/; //extern (C) uintptr_t _beginthreadex(void*, uint, btex_fptr, void*, uint, uint*) nothrow; /* Dining Philosophers example for a habr.com * by Siemargl, 2019 * BetterC variant. Compile >dmd -betterC Philo_BetterC.d */ extern (C) uintptr_t _beginthread(btex_fptr, uint stack_size, void *arglist) nothrow; alias HANDLE uintptr_t; alias HANDLE Fork; const philocount = 5; const cycles = 20; HANDLE[philocount] forks; struct Philosopher { const(char)* name; Fork left, right; HANDLE lifethread; } Philosopher[philocount] philos; extern (Windows) void PhilosopherLifeCycle(void* data) nothrow { Philosopher* philo = cast(Philosopher*)data; for (int age = 0; age++ < cycles;) { printf("%s is thinking\n", philo.name); Sleep(rand() % 100); printf("%s is hungry\n", philo.name); WaitForSingleObject(philo.left, INFINITE); WaitForSingleObject(philo.right, INFINITE); printf("%s is eating\n", philo.name); Sleep(rand() % 100); ReleaseMutex(philo.right); ReleaseMutex(philo.left); } printf("%s is leaving\n", philo.name); } extern (C) int main() { version(Windows){} else { static assert(false, "OS not supported"); } philos[0] = Philosopher ("Aristotlet".ptr, forks[0], forks[1], null); philos[1] = Philosopher ("Kant".ptr, forks[1], forks[2], null); philos[2] = Philosopher ("Spinoza".ptr, forks[2], forks[3], null); philos[3] = Philosopher ("Marx".ptr, forks[3], forks[4], null); philos[4] = Philosopher ("Russel".ptr, forks[0], forks[4], null); foreach(ref f; forks) { f = CreateMutex(null, false, null); assert(f); } foreach(ref ph; philos) { ph.lifethread = _beginthread(&PhilosopherLifeCycle, 0, &ph); assert(ph.lifethread); } foreach(ref ph; philos) WaitForSingleObject(ph.lifethread, INFINITE); // Close thread and mutex handles for( auto i = 0; i < philocount; i++ ) { CloseHandle(philos[i].lifethread); CloseHandle(forks[i]); } return 0; } 


为了进行比较,来源为完整D。

在玫瑰花环上,您还可以看到其他语言的选项。

IEC 61131-3 ST(1993,最新标准2013)


微控制器的利基编程语言。 该标准包含5种编程选项,但是例如以梯形逻辑编写应用程序仍然是一次冒险。 因此,我们专注于一种选择-结构化文本。
标准GOST R IEC 61131-3-2016的文本-230页。

有PC / x86和ARM的实现-以及商业的实现,其中最著名的是CODESYS (通常也以不同的名称进行了分许可),并通过C广播开放-Beremiz

由于与C集成,因此很有可能连接应用程序编程所需的库。 另一方面,在该领域中,可以接受逻辑是单独旋转,并且仅用作另一个程序或系统的数据服务器-与操作员或已经可以写在任何东西上的DBMS的接口-无需实时要求,甚至任何临时性一般而言...

用户程序的多线程编程相对较新出现-在微控制器中以前不需要。

类型转换大部分仅是显式的(在最新标准中有所放松)。 但是溢出控制取决于实现。

在该标准的最新版本中,出现了OOP。 错误处理由自定义中断处理程序完成。

我们可以说没有为用户分配动态内存。 历史上发生过-由微控制器处理的数据量始终从上方受到恒定限制。

来源(未验证)
 (* Dining Philosophers example for a habr.com * by Siemargl, 2019 * ISO61131 ST language variant. Must be specialized 4 ur PLC * ) CONFIGURATION PLC_1 VAR_GLOBAL Forks : USINT; Philo_1: Philosopher; (* Instance block - static vars *) Philo_2: Philosopher; Philo_3: Philosopher; Philo_4: Philosopher; Philo_5: Philosopher; END_VAR RESOURCE Station_1 ON CPU_1 TASK Task_1 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_2 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_3 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_4 (INTERVAL := T#100MS, PRIORITY := 1); TASK Task_5 (INTERVAL := T#100MS, PRIORITY := 1); PROGRAM Life_1 WITH Task_1: Philo_1(Name := 'Kant', 0, 1, Forks); PROGRAM Life2 WITH Task_2: Philo_2(Name := 'Aristotel', 1, 2, Forks); PROGRAM Life3 WITH Task_3: Philo_3(Name := 'Spinoza', 2, 3, Forks); PROGRAM Life4 WITH Task_4: Philo_4(Name := 'Marx', 3, 4, Forks); PROGRAM Life5 WITH Task_5: Philo_5(Name := 'Russel', 4, 0, Forks); END_RESOURCE END_CONFIGURATION FUNCTION_BLOCK Philosopher; USING SysCpuHandling.library; VAR_INPUT Name: STRING; Left: UINT; Right: UINT; END_VAR VAR_IN_OUT Forks: USINT; END_VAR VAR Thinking: BOOL := TRUE; (* States *) Hungry: BOOL; Eating: BOOL; HaveLeftFork: BOOL; TmThink: TON; TmEating: TON; END_VAR TmThink(In := Thinking; PT := T#3s); TmEating(In := Eating; PT := T#5s); IF Thinking THEN (* Just waiting Timer *) Thinking := NOT TmThink.Q; Hungry := TmThink.Q; ELSIF Hungry (* Try Atomic Lock Forks *) IF HaveLeftFork IF SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Right, bSet := 1) = ERR_OK THEN Hungry := FALSE; Eating := TRUE; ELSE RETURN; END_IF ELSIF IF SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Left, bSet := 1) = ERR_OK THEN HaveLeftFork := TRUE; ELSE RETURN; END_IF END_IF ELSIF Eating (* Waiting Timer, then lay forks *) IF TmEating.Q THEN Thinking := TRUE; Eating := FALSE; HaveLeftFork := FALSE; SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Right, bSet := 0); SysCpuTestAndSetBit(Address := Forks, Len := 1, iBit := Left, bSet := 0); END_IF END_IF END_FUNCTION_BLOCK 


安全C(2011)


实验C,去除了危险的芯片,并增加了模块化和多线程功能。 项目现场
约103页的说明。 如果您强调与C的差异- 很少,约为10

使用数组和指针是安全的动态内存,具有自动引用计数功能-带有双重释放检查和悬空链接。

标准库具有用于GUI,多线程,网络功能(包括http服务器)的最少功能集。

但是-此实现仅适用于Windows x86。 虽然编译器和库代码是开放的。

作为另一项研究任务的一部分,我整理了一个Web服务器布局,该布局从IoT传感器收集数据:一个75 Kb执行模块和一个<1 MB的部分内存集。

餐饮哲学家的资料
 /* Dining Philosophers example for a habr.com * by Siemargl, 2019 * Safe-C variant. Compile >mk.exe philosafec.c */ from std use console, thread, random; enum philos (ushort) { Aristotle, Kant, Spinoza, Marx, Russell, }; const int cycles = 10; const ushort NUM = 5; uint lived = NUM; packed struct philosopher // 32-bit { philos name; byte left, right; } philosopher philo_body[NUM]; SHARED_OBJECT forks[NUM]; void philosopher_life(philosopher philo) { int age; for (age = 0; age++ < cycles; ) { printf("%s is thinking\n", philo.name'string); delay((uint)rnd(1, 100)); printf("%s is hungry\n", philo.name'string); enter_shared_object(ref forks[philo.left]); enter_shared_object(ref forks[philo.right]); printf("%s is eating\n", philo.name'string); delay((uint)rnd(1, 100)); leave_shared_object(ref forks[philo.right]); leave_shared_object(ref forks[philo.left]); } printf("%s is leaving\n", philo.name'string); InterlockedExchange(ref lived, lived-1); } void main() { philos i; assert philosopher'size == 4; philo_body[0] = {Aristotle, 0, 1}; philo_body[1] = {Kant, 1, 2}; philo_body[2] = {Spinoza, 2, 3}; philo_body[3] = {Marx, 3, 4}; philo_body[4] = {Russell, 0, 4}; for (i = philos'first; i <= philos'last; i++) { assert run philosopher_life(philo_body[(uint)i]) == 0; } while (lived > 0) sleep 0; // until all dies for (i = philos'first; i <= philos'last; i++) { destroy_shared_object(ref forks[(uint)i]); } } 


最后- 符合功能要求的摘要表
当然,我会错过或曲解某些内容-请更正它。

来源来自github上的文章

Source: https://habr.com/ru/post/zh-CN442372/


All Articles