为什么使用低级语言或接近它(C)的嵌入式系统而不是高级语言,当所有的都会编译成机器代码



我已经搜索过了,但找不到明确的答案。如果我们在计算机中编译代码(功能强大),那么我们只是向嵌入式设备中的内存发送机器指令。据我所知,如果我们使用任何类型的语言,这都不会有什么不同,因为最终,我们只会向嵌入式设备发送一个机器代码,而代码编译这一昂贵的阶段已经由一台强大的机器完成了!

为什么要使用C语言?为什么不是Java?我们在最后发送一个机器代码。

答案部分在于语言的运行时需求和平台提供的期望:C的运行时大小是最小的——它需要一个堆栈,这就是它能够开始运行代码的原因。对于兼容的实现,需要静态数据初始化,但您可以在没有它的情况下运行代码-初始化本身甚至可以用C编写,甚至堆和标准库初始化都是可选的,库的存在也是可选的。它不需要操作系统依赖,不需要解释器,也不需要虚拟机。

大多数其他语言都需要更多的运行时支持,而这通常是由操作系统、运行时库或虚拟机提供的。要"独立"操作这些语言,需要"内置"支持,因此支持会大得多,以至于在许多情况下,您还可以部署带有操作系统和/或JVM的系统,例如在任何情况下。

当然,特定语言适用于嵌入式系统还有其他原因,例如硬件级访问、性能和确定性行为。

虽然运行时环境和/或操作系统的问题是小型嵌入式系统中不常见高级语言的主要原因,但这绝不是闻所未闻的。例如,.Net Micro Framework允许在嵌入式系统中使用C#,并且有许多嵌入式JVM实现,当然Linux发行版也被广泛嵌入,这使得语言选择几乎没有限制。Net Micro运行在数量有限的处理器体系结构上,需要相当大的内存(>256kb),JVM实现可能也有类似的要求。Linux不会在低于16Mb ROM/4Mb RAM的情况下启动。两者都不特别适用于具有微秒级截止日期的硬实时应用程序。

C或多或少在8、16、32和64位平台上无处不在,从第一天起通常可用于任何体系结构,而对其他语言(至少在32位平台上可能不是C++)的支持可能是可变的和不完整的,可能只在更成熟或广泛使用的平台上可用。

从开发人员的角度来看,一个重要的考虑因素也是目标平台和语言的交叉编译工具的可用性。因此,这是一个良性循环,开发者选择C(或者越来越多地选择C++),因为它是最广泛可用的工具,而工具/芯片供应商提供C和C++工具链,因为这是开发者的需求。再加上库、开源代码、调试器、RTOS等形式的第三方支持,选择一种几乎没有任何支持的语言将是一个勇敢(或愚蠢)的开发人员。受到这种影响的不仅仅是高级语言。我曾经参与过一个用Forth编程的项目——一种比C语言还低的语言——这是一次孤独的经历,虽然有热情的语言倡导者,但坦率地说,他们有点疯狂,喜欢语言福音而不是商业成功。C在短时间内达到了临界质量验收,并且很难去除。C++得益于与C的广泛互操作性和类似的最低运行时要求,以及通常支持这两种语言的工具链。因此,采用C++的唯一障碍主要是开发人员的惰性,以及在某种程度上在8位和16位平台上的可用性。

你有点误解了。让我们从解释计算机内部工作的基础开始。我将在这里使用简单实用的概念。关于基本理论,请阅读图灵机。那么,你的机器是由什么组成的?所有计算机都有两个基本组件:处理器和内存。

内存是一组按顺序排列的"单元",其工作方式有点像表格。如果将值"写入"第1个CCD_单元格,则可以通过从第2个N单元格"读取"来检索相同的值。这使计算机能够"记住"事物。如果计算机要执行计算,它需要从某个地方检索输入数据,并将数据输出到某个地方。那个地方就是记忆。在实践中,内存就是我们所说的RAM,是随机存取存储器的缩写。

然后我们有了处理器。它的工作是对内存进行实际计算。要执行的实际操作是由程序强制执行的,即处理器能够理解和执行的一系列指令。处理器解码并执行一条指令,然后执行下一条,依此类推,直到程序停止(停止)机器。如果程序是add cell #1 and cell #2 and store result in cell #3,处理器将获取单元12的值,将它们的值相加,并将结果存储到单元3中。

现在,有一个内在的问题程序存储在哪里,如果有的话?首先,程序不能硬编码到连线中。否则,这个系统就像一台电脑,而不是你的微波炉。对于这些问题,有两种不同的方法/解决方案:哈佛体系结构和冯·诺依曼体系结构。

基本上,在哈佛体系结构中,数据(一如既往)存储在内存中。代码(或程序)存储在其他地方,通常是只读存储器。在冯·诺依曼体系结构中,代码存储在内存中,只是数据的另一种形式。因此,代码就是数据,数据就是代码。值得注意的是,大多数现代系统使用Von Neumann架构有几个原因,包括这是实现实时编译的唯一方法,实时编译是现代基于字节码的编程语言(如Java)运行时系统的重要组成部分。

我们现在知道了这台机器是做什么的,以及它是如何做到这一点的。但是,数据和代码是如何存储的?什么是"基本格式",如何解释?你可能听说过二进制数字系统。在我们通常的十进制数字系统中,我们有十位数字,从零到九。然而,为什么正好是十位数?难道他们不能是八岁、十六岁、六十岁,甚至两岁吗?请注意,创建基于一元的计算系统是不可能的。

你听说过计算机是"逻辑性的、冷冰冰的"吗。这两个都是真的。。。除非您的机器有AMD处理器或特殊类型的奔腾。该理论指出,每个逻辑谓词都可以简化为"真"或"假"。也就是说,"treu"one_answers"false"是逻辑的基础。另外,电脑是由电子设备组成的,不是吗?电灯开关是开着还是关着,不是吗?所以,在电气水平上,我们可以很容易地识别出两个电压水平,对吧?我们想在计算机中处理逻辑的东西,比如数字,对吧?因此,零和一可能是唯一可行的解决方案。

现在,考虑到所有的理论,让我们来谈谈编程语言和汇编语言。汇编语言是一种以人类程序员可读的方式表达二进制指令的方式。例如,像这样的东西。。。

ADD 0, 1 # Add cells 0 and 1 together and store the result in cell 0

可以由汇编程序翻译成。。。

110101110000000000000001

两者都是等价的,但人类只会理解前者,处理器只会理解后者。

编译器是一种程序,它将预期符合给定编程语言规则的输入数据转换为另一种通常较低级别的形式。例如,C编译器可能会使用此代码。。。

x = some_function(y + z);

并将其翻译成汇编代码,例如(当然这不是真正的汇编,BTW!)。。。

# Assume x is at cell 1, y at cell 2, and z at cell 3.
# Assuem that, when calling a function, the first argument
# is at cell 16, and the result is stored in cell 0.
MOVE 16, 2
ADD 16, 3
CALL some_function
MOVE 1, 0

汇编程序会吐(这不是随机的)。。。

11101001000100000000001001101110000100000000001110111011101101111010101111101111110110100111010010000000100000000

现在,让我们来谈谈另一种语言,即Java。Java的编译器不提供汇编/原始二进制代码,而是提供字节码。字节码……就像一种通用的、更高级的汇编语言,CPU无法理解(也有例外),但另一个直接在CPU上运行的程序可以理解这意味着一些受过不良教育的人散布的谎言,即"解释和编译的程序最终都归结为机器代码"是错误的。例如,如果解释器是用C编写的,并且有这行代码。。。

Bytecode some_bytecode;
/* ... */
execute_bytecode(&some_bytecode);

(注意:我不会再把它转换成汇编/二进制!)处理器执行解释器,解释器的代码通过执行字节码指定的操作来执行字节码。尽管如果没有正确优化,这可能会严重降低性能,但这并不是本身的问题,而是反射、垃圾收集和异常等因素可能会增加相当大的开销。对于内存小、处理器慢的嵌入式系统来说,这是你想要的。您正在将宝贵的系统资源浪费在不需要的东西上。如果C程序在你的Arduino上运行缓慢,想象一下一个完整的Java/Python程序,里面有各种各样的铃声和口哨声!即使在将字节码插入系统之前将其翻译成机器代码,也必须支持所有额外的东西,这基本上会导致相同的不必要的开销/浪费。您仍然需要对反射、异常、垃圾收集等的支持……这基本上是一样的。

在大多数其他环境中,这并不是什么大不了的事情,因为内存便宜而丰富,处理器快速而强大。嵌入式系统有特殊的需求,它们本身就很特殊,在这片土地上,东西不是免费的。

为什么要使用C这样的语言?为什么不使用Java呢?我们正在发送一台机器最后的代码。

不,Java代码不会编译成机器代码,它需要目标系统上的虚拟机(JVM)。

然而,您对编译的看法在一定程度上是正确的,但"高级"语言仍然会导致机器代码的效率降低。例如,该语言可以包括垃圾收集、运行时正确性检查、不能使用所有"本机"数字类型等。

通常情况下,它取决于目标。在小型目标(即AVR等微控制器)上,您没有运行那么复杂的程序。此外,您需要直接访问硬件(例如UART)。像Java这样的高级语言不支持直接访问硬件,所以你通常会使用C.

在C和Java的情况下,有一个主要的区别:

使用C编译代码并获得在目标上运行的二进制文件。它直接在目标上运行。

Java创建Java字节码。目标CPU无法处理。相反,它需要运行另一个程序:Java运行时环境。这将Java字节码转换为实际的机器代码。显然,这是更多的工作,因此需要更多的处理能力。虽然这对于标准PC来说不是什么大问题,但对于小型嵌入式设备来说却是如此。(注意:有些CPU实际上支持直接运行Java字节码。但这些都是例外。)

一般来说,编译步骤不是问题,而是目标设备的有限资源和特殊要求。

你误解了一些东西,‘编译’java给出了不同的输出,然后编译一种低级语言,两者确实都是机器代码,但在c的情况下,机器代码可以由处理器直接执行,而java的输出将处于中间阶段,即字节码,处理器无法执行它,它需要一些额外的工作,翻译成机器代码,这是唯一可直接执行的格式,虽然这需要额外的时间,但c将是一个有吸引力的选择,因为它的速度很快,使用低级别语言,你可以编写代码,然后编译到目标机器(你需要为编译器指定目标,因为每个处理器都有自己的机器代码),那么处理器可以理解您的代码。另一方面,c允许直接访问硬件,这在类似java的语言中是不允许的,即使是通过api

这是一个行业。

高级语言有三种。解释(lua,python,javascript),编译为字节码(java,c#),并编译为机器码(c,c++,fortran,cobol,pascal)

是的,C是一种高级语言,更接近java而不是汇编。

高级语言之所以流行有两个原因。

内存管理和广泛的标准库。

托管内存是有成本的,必须有人来管理它。这不仅是java和c的问题,在java和c中,必须有人实现VM,而且对于baremetal c/c++来说,也必须有人执行内存分配函数。

由于没有足够的资源,所有目标都不能支持广泛的标准库。即,avrArduino不支持完整的c++标准库。

C获得了流行,因为它可以很容易地转换为等效的汇编代码。大多数语句都可以在不进行优化的情况下转换为一堆固定的汇编指令,因此编译器很容易编程。其标准结构紧凑,易于实施。C语言占了上风,因为它成为了所有arch中最低级别语言的实际标准。

因此,最终,除了cython、go、rust、haskell等特殊的雪花,业界决定机器码是从C、C++编译的,大多数优化工作都是这样进行的

像java这样的语言决定向progaramer隐藏内存,所以祝你好运,尝试在那里与低级别的东西进行接口。从设计上讲,几乎没有人愿意让它们与C竞争。实际上,没有GC的java将是具有不同语法的C++。

最后,如果所有的行业资金都花在了一种语言上,那么最便宜/最容易的事情就是选择这种语言。

您是对的,您可以使用任何生成机器代码的语言。但JAVA不是其中之一。JAVA、Python甚至一些编译成机器代码的语言可能对系统有很高的要求。你可以和一些人使用Pascal,但C在多年前赢得了C对Pascal的战争。还有一些其他语言被搁置一旁,如果你有一个编译器,你就可以使用。有一些新的语言可以使用,但这些工具并没有人们想要的那么成熟,目标也没有那么多。但他们不太可能推翻C.C。这是一个合适的权力/自由度,足够低和足够高。

Java是一种解释语言,(与所有解释语言一样)生成一个处理器无法直接执行的中间代码。因此,您发送到嵌入式设备的将是字节码,并且您应该在其上运行JVM并解释您的代码。显然不可行。关于编译语言(C、C++…),您可以正确地说,最后您将机器代码发送到设备。然而,考虑到使用一种语言的高级功能将产生比您预期的多得多的机器代码。例如,如果你使用多态性,你只有一个函数调用,但当你编译机器代码时,它会爆炸。还要考虑在嵌入式设备上使用动态内存(malloc、new…)通常是不可行的。

最新更新