为什么标准库不编程语言原语?[关闭]


30

我在想为什么要有stdlib之类的标准库(在我学过的所有编程语言中,例如C ++,Java,Python),而不要使用类似的“函数”作为语言本身的原始形式。


4
您是什么意思?“为什么编译器不能简单地将函数调用转换为一组指令”?无论是否使用标准库,这大致就是编译器的工作(好吧,仅Python和Java到JVM字节码;类似的概念)。标准库确实与代码的编译没有任何关系->指令。
Delioth

25
@Delioth我认为Simone在问为什么标准语言$ LANG中的所有内容都不是该语言的原始构造/功能。我想说的是,对于任何对编程语言非常了解的人都是一个合理的问题:)
Andres F.

33
标准库通常填补了一种有效的编程语言与人们将使用的有用语言之间的空白。
Telastyn

6
Python标准库的很大一部分实际上是用C编写的,并且已经被编译。
ElmoVanKielmo

1
相比之下,在大多数BASIC实现中,所有内容都是该语言的一部分,根本没有库,也没有对它们的支持(在某些实现中,保留了调用机器语言例程的能力)。
Euro Micelli

Answers:


32

请允许我扩展一下@Vincent(+1)的好答案

为什么编译器不能简单地将函数调用转换为一组指令?

它可以并且至少通过两种机制来做到这一点:

  • 内联函数调用 -在翻译过程中,编译器可以直接内联其实现替换源代码调用,而不用实际调用该函数。函数仍然需要在某个地方定义一个实现,并且可以在标准库中实现。

  • 内在函数 —内在函数是已通知编译器的函数,而不必在库中找到该函数。这些通常是为那些实际上无法以其他任何方式访问的硬件功能保留的,它们是如此简单,以至于甚至汇编语言库函数调用的开销也被认为很高。(编译器通常只能自动以其语言内联源代码,而不能自动嵌入内联机制所在的汇编函数。)

尽管如此,最好的选择有时还是编译器将源语言中的函数调用转换为机器代码中的函数调用。递归,虚拟方法和庞大的大小是内联并不总是可能/可行的一些原因。(另一个原因是构建的意图,例如单独的编译(对象模块),单独的加载单元(例如DLL))。

使大多数标准库函数本征化也没有任何真正的优势(这将使更多的知识硬编码到编译器中,而没有真正的优势),因此再次进行机器代码调用通常是最合适的。

C是一种著名的语言,可以说它省略了其他支持标准库功能的显式语言语句。尽管库已经存在,但该语言已从标准库功能转移到做更多的工作,而不再是该语言语法中的明确声明。例如,其他语言的IO经常以各种语句的形式提供其自身的语法,而C语法未定义任何IO语句,而是仅遵循其标准库以提供所有这些内容,这些都可以通过函数调用来访问,编译器已经知道该怎么做。


3
好答案。应当添加一些用词解释为什么用C做出决定的原因:如果我没记错的话,主要原因实际上是因为它使为许多不同的硬件体系结构创建C编译器变得更容易。
布朗

10
@DocBrown到1975年,编程语言开发领域出现了足够多的示例(ALGOL-68,有人吗?)表明,将所有内容都烤入该语言的尝试直接导致在确定语言规范和在生产语言实现中。
Joker_vD

5
一个类似的例子是Python的处理方式print:在2.x中,它是一条语句,具有自己的特殊语法,但是在3.x中,它变成了另一个函数调用。有关官方说明,请参见PEP 3105
dan04

1
@DocBrown,可移植性几乎绝对不是原因。创建Unix和C时,它们是为一台机器(一台备用PDP-7)而设计和制造的,正如Ken Thompson想知道从失败的Multics项目中可以挽救哪些概念。创建C也是出于以下原因:具有用于(重新)实现Unix的高级语言。他们基本上是软件设计方面的实验,不是对商用多平台OS和语言的认真尝试。例如,请参阅bell-labs.com/usr/dmr/www/chist.html
Euro Micelli

@EuroMicelli:我没有看到矛盾。您的参考资料包含许多有关可移植性何时变得重要的详细信息,它实际上是在C和Unix开发的早期。我只能在这里猜测,但是如果C发明者不会故意使这种语言变小,那么我认为他们不可能如此迅速而成功地将其移植到许多不同的体系结构中是不可能的。
布朗

70

这仅仅是为了使语言本身尽可能简单。您需要区分语言的功能,例如循环的类型或将参数传递给函数的方式等,以及大多数应用程序所需的常用功能。

库是可能对许多程序员有用的函数,因此它们被创建为可共享的可重用代码。标准库被设计为程序员通常需要的非常常见的功能。这样,编程语言立即可用于更广泛的程序员。可以在不更改语言本身核心功能的情况下对其进行更新和扩展。


3
不总是。PHP例如,它的广泛语言功能和语言本身几乎没有任何区别。
Vahid Amiri

15
我不会以PHP作为简单语言的示例
DrBreakalot

3
@DrBreakalot PHP是一种非常简单的语言。这并不是说它具有一致的设计,但这是另一个问题。
与莫妮卡(Monica)进行的轻度比赛

19
@LightnessRacesinOrbit我根本不会称PHP为“简单”:它具有基于类的对象系统,单独的一组“原始值”,独立函数,在对象系统上构建的一流封闭,命名空间机制,各种称为“静态”的概念,语句以及表达式和includerequirerequire_once,if / for / while(结构化编程),异常,单独的“错误值”系统,复杂的弱类型规则,复杂的运算符优先级规则以及on and on 。将其与Smalltalk,Scheme,Prolog,Forth等的简单性进行比较;)
Warbo

3
该答案中暗示但未明确指出的主要原因是,通过保持语言尽可能简单,在其他平台上实现起来要容易得多。由于标准库通常是用语言本身编写的,因此可以轻松移植。
BlueRaja-Danny Pflughoeft

34

除了已经给出的其他答案之外,将标准函数放入库中也是关注点分离

  • 解析语言并为其生成代码是编译器的工作。包含已经可以用该语言编写并作为库提供的任何内容不是编译器的工作。

  • 标准库的工作(总是隐式可用)是提供几乎所有程序都需要的核心功能。包含所有可能有用的功能不是标准库的工作。

  • 可选标准库的工作是提供许多程序无法提供的辅助功能,但是这些功能仍然是非常基本的,对于许多应用程序需要保证标准环境的运输也是必不可少的。包含所有已编写的可重用代码不是这些可选库的工作。

  • 用户库的工作是提供有用的可重用功能的集合。包含所有已编写的代码不是用户库的工作。

  • 应用程序源代码的工作是提供实际上仅与该一个应用程序相关的其余代码位。

如果您想要一种适合所有人的软件,那么您将获得极为复杂的东西。您需要进行模块化以将复杂性降低到可管理的水平。并且您需要模块化以允许部分实现:

  • 在单核嵌入式控制器上,线程库毫无用处。允许此嵌入式控制器的语言实现不包含pthread库只是正确的选择。

  • 在没有FPU的微控制器上,数学库毫无用处。同样,不必强迫提供类似的功能,sin()对于该语言的语言实现者来说,使该微控制器的工作变得更加轻松。

  • 在编写内核时,即使是核心标准库也毫无用处。您不能实现write()没有系统调用到内核中,你无法实现printf()write()。作为内核程序员,提供write()syscall 是您的工作,您不能仅仅期望它存在。

不允许标准库中此类遗漏的语言根本不适合许多任务。如果希望您的语言在不常见的环境中灵活使用,则在包含哪些标准库时必须灵活。您的语言对标准库的依赖程度越高,对其执行环境的假设就越多,从而将其使用限制在提供这些先决条件的环境中。

当然,诸如python和java之类的高级语言可以对其环境做出很多假设。而且它们倾向于在其标准库中包含很多东西。诸如C之类的低级语言在其标准库中提供的内容少得多,并且使核心标准库更小。这就是为什么您可以找到适用于几乎所有架构的C编译器,但可能无法在其上运行任何python脚本的原因。


16

编译器和标准库分开的一个重要原因是,它们具有两个不同的用途(即使它们都是由相同的语言规范定义的):编译器将高级代码转换为机器指令,而标准库提供了经过预测试的常用功能的实现。就像其他软件开发人员一样,编译器编写人员也重视模块化。实际上,一些早期的C编译器进一步将编译器拆分为单独的程序,以进行预处理,编译和链接。

这种模块化为您提供了很多优势:

  • 由于支持大多数与硬件无关的标准库代码,因此可以在支持新的硬件平台时最大程度地减少工作量。
  • 可以通过不同方式(对于速度,空间,资源使用等)优化标准库的实现。许多早期的计算系统只有一个编译器可用,并且拥有单独的标准库意味着开发人员可以交换实现以适应他们的需求。
  • 标准库功能甚至不必存在。例如,在编写裸机C代码时,您具有功能齐全的编译器,但大多数标准库功能不可用,甚至文件I / O之类的功能也无法实现。如果需要编译器来实现此功能,则在某些最需要它的平台上可能没有符合标准的C编译器。
  • 在早期的系统上,编译器通常是由设计硬件的公司开发的。操作系统供应商经常提供标准库,因为它们经常需要访问特定于该软件平台的功能(例如系统调用)。对于编译器编写者来说,必须支持所有不同的硬件和软件组合是不切实际的(过去在硬件体系结构和软件平台上都有更多的多样性)。
  • 在高级语言中,可以将标准库实现为动态加载的库。然后,一种标准库实现可由多种编译器和/或编程语言使用。

从历史上讲(至少从C的角度来看),该语言的原始,预标准化版本根本没有标准库。操作系统供应商和第三方通常会提供充满常用功能的库,但是不同的实现包含不同的内容,并且它们之间很大程度上不兼容。在对C进行标准化时,他们定义了一个“标准库”,以协调这些不同的实现并提高可移植性。C标准库是与语言分开开发的,就像Boost库针对C ++一样,但后来被集成到语言规范中。


6

特殊情况的其他答案:知识产权管理

值得注意的例子是.NET Framework中Math.Pow(double,double)的实现,该框架是Microsoft从Intel购买的,即使该框架是开源的,也没有公开。(确切地说,在上述情况下,它是内部调用而不是库,但这个想法成立。)与语言本身分离的库(理论上也是标准库的子集)可以使语言支持者在绘制语言时更具灵活性。保持透明与必须保持透明之间的界限(由于与第三方的合同或其他与知识产权相关的原因)。


这很混乱。您链接到的页面Math.Pow没有提到购买任何东西,也没有任何有关Intel的信息,它谈论的是人们在阅读该功能实现的源代码。
与莫妮卡(Monica)进行的轻度比赛

@LightnessRacesinOrbit –嗯,我仍然可以在那儿看到它(搜索“ intel”时)。您还可以找到对最新源代码的引用(在最新注释中),以及替代实现(在第二个答案中),该实现是公开可用的,但是其复杂性和注释效率低下,这提示了为什么仍未公开原始实现。真正有效的实现可能需要深入了解CPU级别上的许多细节,而这些细节在公共领域中不一定可用。
miroxlav

5

错误和调试。

错误:所有软件都有错误,您的标准库有错误,而编译器有错误。作为该语言的用户,当它们在标准库中而不是在编译器中时,更容易发现和解决这些错误。

调试:对我来说,查看标准库的堆栈跟踪信息要容易得多,让我对可能出问题的地方有所了解。因为该堆栈跟踪具有我了解的代码。当然,您可以做得更深入,也可以跟踪自己的内在功能,但是如果使用的是每天都在使用的语言,则要容易得多。


5

这是一个很好的问题!

最先进的

例如,C ++标准从不指定应在编译器或标准库中实现的内容:它仅指的是实现。例如,保留符号由编译器(作为内在函数)和标准库两者互换定义。

但是,我所知道的所有C ++实现都将具有由编译器提供的尽可能少的内在函数,以及由标准库提供的尽可能多的内在函数。

因此,尽管在编译器中将标准库定义为内部功能在技术上可行,但实际上却很少使用。

为什么?

让我们考虑一下将某些功能从标准库转移到编译器的想法。

好处:

  • 更好的诊断:内部函数可以是特殊情况。
  • 更好的性能:内部函数可以是特殊情况。

缺点:

  • 编译器质量增加:每种特殊情况都会增加编译器的复杂性;复杂性增加了维护成本,并增加了发生错误的可能性。
  • 迭代速度更慢:更改功能的实现需要更改编译器本身,这使得仅创建一个小型库(在之外std)变得更加困难。
  • 进入市场的门槛更高:更改某项商品的费用越高/难度越大,人们跳入的可能性就越小。

这意味着,无论现在还是将来,将某些东西移交给编译器都是很昂贵的,因此需要一个可靠的案例。对于某些功能,这是必要的(不能将其编写为常规代码),但是即使那样,也需要提取最少的通用部分以移至编译器并在标准库的顶部进行构建。


5

作为我自己的语言设计师,我想在这里回应一些其他答案,但是请正在构建语言的人的眼中提供。

完成将所有内容添加到API中后,API尚未完成。完成所有可能的工作后,API即告完成。

必须使用某种语言来指定编程语言。您必须能够传达使用您的语言编写的任何程序背后的含义。这种语言是非常难写,甚至更难写。通常,它往往是一种非常精确和结构良好的英语形式,用于向计算机,而不是其他开发人员(尤其是为您的语言编写编译器或解释器的开发人员)传达含义。这是C ++ 11规范[intro.multithread / 14]中的示例:

相对于M的值计算B,原子对象M上可见的副作用序列是按M的修改顺序排列的最大副作用子序列,其中第一个副作用相对于B可见,并且对于每种副作用,并不是B发生在它之前。由评估B确定的原子对象M的值应是通过某些操作存储在M相对于B的可见序列中的值。[注:可以表明,一个值的可见副作用序列鉴于以下一致性要求,计算是唯一的。—尾注]

死了!凡是愿意了解C ++ 11如何处理多线程的人都可以理解为什么措词必须如此不透明,但这并不能原谅它是如此……那么……如此不透明!

std::shared_ptr<T>::reset标准的库部分中的定义进行对比:

template <class Y> void reset(Y* p);

效果:相当于shared_ptr(p).swap(*this)

那有什么区别呢?在语言定义部分,作者不能假定读者理解语言原语。一切都必须用英文散文仔细说明。一旦到达库定义部分,就可以使用该语言指定行为。这通常容易得多!

原则上,可以在规范文档开始时从基元平稳地构建,直到定义我们认为的“标准库功能”,而不必在“语言基元”和“语言基元”之间划清界限。 “标准库”功能。在实践中,这条线被证明具有极大的价值,因为它使您可以使用旨在表达这些语言的语言来编写该语言的一些最复杂的部分(例如必须实现算法的部分)。

我们确实看到了一些模糊的线条:

  • 在Java中,java.lang.ref.Reference<T>可以由标准库类被继承java.lang.ref.WeakReference<T> java.lang.ref.SoftReference<T>java.lang.ref.PhantomReference<T>因行为Reference是如此之深与Java语言规范,他们需要把一些限制进入的“标准库”类来实现这一过程的一部分缠绕。
  • 在C#中,有一个类System.Delegate封装了委托的概念。尽管它的名字,它不是一个委托。它也是一个抽象类(无法实例化),无法从中创建派生类。只有系统才能通过语言规范中写入的功能来实现。

2

这是对现有答案的补充(对于评论来说太长了)。

使用标准库至少还有其他两个原因:

进入壁垒

如果库函数中包含特定的语言功能,而我想知道它的工作方式,则可以阅读该功能的源代码。如果我要提交错误报告/补丁/拉取请求,通常编写修复和测试用例并不难。如果在编译器中,则必须能够深入研究内部结构。即使使用相同的语言(应该如此,任何自重的编译器也应该是自托管的),编译器代码与应用程序代码完全不同。找到正确的文件可能要花费很多时间。

如果您走那条路,那么您将与许多潜在的参与者失去联系。

热门代码加载

许多语言在某种程度上都提供了此功能,但是热重载正在执行热重载的代码将极其复杂。如果SL与运行时分开,则可以重新加载它。


3
“任何自重的编译器都应该是自托管的”-完全没有。用C,C ++,Objective-C,Swift,Fortran等语言编写的LLVM版本编译所有这些语言将毫无意义。
gnasher729

@ gnasher729并不是一个特殊情况(以及其他多语言目标,例如CLR)吗?
贾里德·史密斯

@JaredSmith我想说这是一般情况,一点也不特殊。没有人再将“编译器”编写为整体应用程序了。他们生产编译器系统。完整编译器的大多数功能完全独立于正在编译的特定语言,并且与语言相关的大部分工作可以通过提供定义语言语法的不同数据来完成,而不是通过为每种语言编写不同的代码来完成要编译。
alephzero

2

这是一个有趣的问题,但是已经给出了许多很好的答案,所以我不会尝试完整的答案。

但是,我认为没有引起足够注意的两件事:

首先是整个事情不是超级明确的。确切地说,这是有点频谱,因为我们有理由做不同的事情。例如,编译器经常了解标准库及其功能。示例示例:C的“ Hello World”函数-printf-是我能想到的最好的函数。这是一个库函数,必须排序,因为它非常依赖平台。但是编译器需要知道它的行为(定义实现),以警告程序员错误的调用。这并不是特别整洁,但被视为一个不错的折衷方案。顺便说一句,这是对大多数“为什么设计”问题的真正答案:很多折衷方案,“在当时看来是个好主意”。并非总是“这是明确的方法”或“

其次,它允许标准库不完全是该标准。在很多情况下,希望使用一种语言,但通常伴随它们的标准库既不实用也不理想。在非标准平台上,最常见的情况是使用系统编程语言(如C)。例如,如果您的系统没有操作系统或调度程序,那么您就不会有线程。

使用标准库模型(并支持其中的线程),可以轻松地进行处理:编译器几乎相同,您可以重用适用的库中的某些位,而所有不能删除的内容都可以删除。如果将此内容编译到编译器中,则事情开始变得混乱。

例如:

  • 您不能成为兼容的编译器。

  • 您将如何表明您偏离标准。请注意,通常会有某种形式的import / include语法可能会失败,例如,如果标准库模型中缺少任何内容,则python的import或C的include很容易指出问题。

如果要调整或扩展“库”功能,也会遇到类似的问题。这比您想像的要普遍得多。只是坚持使用线程:Windows,Linux和一些外来网络处理单元都完全不同地执行线程。尽管linux / windows位可能是相当静态的,并且可以使用相同的API,但是NPU的内容将随着星期几和API的变化而变化。如果没有办法将这种事情分解出来,那么编译器将很快偏离人们的决定,因为他们很快决定需要/不可以支持哪些位。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.