没有库 C 实现
No library C implementation
我在查看不同的 C 实现时听说,任何希望实现 C 的系统都必须至少包含某些库,stdarg.h 等。我的问题是为什么会这样,不可能是如果没有一些头文件,C 库就不是图灵完备的,而且既然头文件已经写好了,我一定可以自己写。那么,为什么不允许只包含编译器+链接器工具链的 C 实现呢? (当然,在这种情况下与 OS 交互需要内联汇编或链接汇编代码以及系统调用等知识,但这并不意味着不能编写 C,不是吗? ?)
两个原因:兼容性和系统交互。
如果您不实现整个 C 标准库,那么其他人编写的代码将无法运行。即使是最基本的 C 程序也会使用库调用。
#include <stdio.h>
int main() {
printf("Hello world!\n");
return 0;
}
如果没有达成一致并完全实施 stdio.h
,该程序将不会 运行,因为编译器不知道 printf()
是什么意思。
然后是系统交互。 C 已被调用 "portable assembly"。这是因为不同的计算环境以不同的方式做事,但 C 为您提供了一些(好吧,其中一些)。你不能在不失去理智的情况下在汇编中编写一个可移植的stdio.h
。但它不止于此。每个 C 头文件都会保护您免受每个环境所做(或过去)非常不同的事情的影响。
stdlib.h
使您免受不同内存模型和进程控制的影响。
stdio.h
屏蔽不同IO系统
math.h
使您免受不同浮点实现的影响。
limits.h
使您免受不同数据大小的影响。
locale.h
使您免受不同语言环境的影响。
C 库提供了每个环境都可以写入的标准 API。当 C 被移植到一个新环境时,该环境负责根据该系统的细节实现这些库。 您不必那样做。
与开发 C 语言时相比,如今我们生活在一个更加同质化的环境中,但是大多数 C 标准库仍然保护您免受操作系统和硬件工作方式的基本差异的影响。
过去并不总是这样。我想到的一个例子是 运行 在 DOS 上玩游戏。声卡和视频卡(如果有的话)没有标准接口。 每个程序都必须为它们支持的每个声卡和视频卡driver发送。如果你的不在那里,对不起。如果他们的 driver 有问题,抱歉。
没有 C 标准库的编程有点像那样,但更糟糕。
您混淆了编程语言的 属性,即语言本身与标准规定的附加功能。
"Turing complete" 只是关于语言本身;基本上,如果您可以使用它来解决某些 class 问题(有关更准确的定义,请参阅 Wikipedia for a starter(!) )。这是一个相当抽象的概念,不包括任何库。基本上,如果你使用这样的库,你只需要能够用语言本身编写这些库。 C语言也是如此。
关于所需的库:你的前提是错误的。 C 很好地允许省略库本身。这就是托管(完整的库)和独立的(很少 target-specific headers,但没有生成代码)之间的区别。参见 4p6。
少数headers 通常是编译器本身的一部分。它们基本上提供了一些 typedef
s 和 #define
d 常量,例如整数类型的范围 (limits.h
) 和保证最小宽度的类型 (stdint.h
,通常也是 fixed-width 类型)。 stddef.h
例如提供 size_t
和 NULL
.
虽然您不需要使用那些 headers,但它们已经允许为程序逻辑编写可移植代码。只需将它们视为语言本身的一部分,为目标量身定制。
gcc C 编译器,例如,实际上是一个独立的实现:它只提供所需的 headers,但不提供标准库。相反,它依赖于系统库,例如Linux.
上的 glibc
注意:通常 re-invent 轮子是个坏主意。因此,如果您在托管环境中(即 full-grown OS),您应该使用可用的功能。否则你可能会 运行 陷入麻烦,因为这些例如可能会提供您的代码无法直接看到的附加功能。例如。调试或 system/user-wide 配置,如本地化支持。调试支持也可能取决于您使用标准库,例如瓦尔格林德。用您自己的代码替换内存分配至少会使这变得更加困难。
更不用说可维护性了。如果您使用标准名称和语义,不仅其他人会更容易理解您的代码,而且您自己也会更容易理解您的代码 - 只需等待几年并尝试理解您的旧代码。
OTOH,如果您使用的是 bare-metal 嵌入式系统,实际上很少使用标准库的大多数功能。包括例如printf
或 scnaf
只会使您的固件膨胀,通常没有任何实际用途。对于此类系统,有 stripped-down 个库(例如 newlib
)可能不完全兼容或允许省略某些昂贵的功能,例如浮点转换或数学库。如果您真的需要它们的许多功能,您仍然应该使用它们。有时有一个折衷的方法,但这需要对库的依赖关系有一些了解。
我在查看不同的 C 实现时听说,任何希望实现 C 的系统都必须至少包含某些库,stdarg.h 等。我的问题是为什么会这样,不可能是如果没有一些头文件,C 库就不是图灵完备的,而且既然头文件已经写好了,我一定可以自己写。那么,为什么不允许只包含编译器+链接器工具链的 C 实现呢? (当然,在这种情况下与 OS 交互需要内联汇编或链接汇编代码以及系统调用等知识,但这并不意味着不能编写 C,不是吗? ?)
两个原因:兼容性和系统交互。
如果您不实现整个 C 标准库,那么其他人编写的代码将无法运行。即使是最基本的 C 程序也会使用库调用。
#include <stdio.h>
int main() {
printf("Hello world!\n");
return 0;
}
如果没有达成一致并完全实施 stdio.h
,该程序将不会 运行,因为编译器不知道 printf()
是什么意思。
然后是系统交互。 C 已被调用 "portable assembly"。这是因为不同的计算环境以不同的方式做事,但 C 为您提供了一些(好吧,其中一些)。你不能在不失去理智的情况下在汇编中编写一个可移植的stdio.h
。但它不止于此。每个 C 头文件都会保护您免受每个环境所做(或过去)非常不同的事情的影响。
stdlib.h
使您免受不同内存模型和进程控制的影响。stdio.h
屏蔽不同IO系统math.h
使您免受不同浮点实现的影响。limits.h
使您免受不同数据大小的影响。locale.h
使您免受不同语言环境的影响。
C 库提供了每个环境都可以写入的标准 API。当 C 被移植到一个新环境时,该环境负责根据该系统的细节实现这些库。 您不必那样做。
与开发 C 语言时相比,如今我们生活在一个更加同质化的环境中,但是大多数 C 标准库仍然保护您免受操作系统和硬件工作方式的基本差异的影响。
过去并不总是这样。我想到的一个例子是 运行 在 DOS 上玩游戏。声卡和视频卡(如果有的话)没有标准接口。 每个程序都必须为它们支持的每个声卡和视频卡driver发送。如果你的不在那里,对不起。如果他们的 driver 有问题,抱歉。
没有 C 标准库的编程有点像那样,但更糟糕。
您混淆了编程语言的 属性,即语言本身与标准规定的附加功能。
"Turing complete" 只是关于语言本身;基本上,如果您可以使用它来解决某些 class 问题(有关更准确的定义,请参阅 Wikipedia for a starter(!) )。这是一个相当抽象的概念,不包括任何库。基本上,如果你使用这样的库,你只需要能够用语言本身编写这些库。 C语言也是如此。
关于所需的库:你的前提是错误的。 C 很好地允许省略库本身。这就是托管(完整的库)和独立的(很少 target-specific headers,但没有生成代码)之间的区别。参见 4p6。
少数headers 通常是编译器本身的一部分。它们基本上提供了一些 typedef
s 和 #define
d 常量,例如整数类型的范围 (limits.h
) 和保证最小宽度的类型 (stdint.h
,通常也是 fixed-width 类型)。 stddef.h
例如提供 size_t
和 NULL
.
虽然您不需要使用那些 headers,但它们已经允许为程序逻辑编写可移植代码。只需将它们视为语言本身的一部分,为目标量身定制。
gcc C 编译器,例如,实际上是一个独立的实现:它只提供所需的 headers,但不提供标准库。相反,它依赖于系统库,例如Linux.
上的 glibc注意:通常 re-invent 轮子是个坏主意。因此,如果您在托管环境中(即 full-grown OS),您应该使用可用的功能。否则你可能会 运行 陷入麻烦,因为这些例如可能会提供您的代码无法直接看到的附加功能。例如。调试或 system/user-wide 配置,如本地化支持。调试支持也可能取决于您使用标准库,例如瓦尔格林德。用您自己的代码替换内存分配至少会使这变得更加困难。
更不用说可维护性了。如果您使用标准名称和语义,不仅其他人会更容易理解您的代码,而且您自己也会更容易理解您的代码 - 只需等待几年并尝试理解您的旧代码。
OTOH,如果您使用的是 bare-metal 嵌入式系统,实际上很少使用标准库的大多数功能。包括例如printf
或 scnaf
只会使您的固件膨胀,通常没有任何实际用途。对于此类系统,有 stripped-down 个库(例如 newlib
)可能不完全兼容或允许省略某些昂贵的功能,例如浮点转换或数学库。如果您真的需要它们的许多功能,您仍然应该使用它们。有时有一个折衷的方法,但这需要对库的依赖关系有一些了解。