【发布时间】:2010-04-03 23:09:52
【问题描述】:
如果这是一个主观或重复的问题,我深表歉意。搜索起来有点尴尬,所以我不确定要包含哪些字词。
我想知道的是,当您不包含 stdio 和 stdlib 等标准库时,C 中的基本基础工具/功能是什么。
如果没有printf()、fopen() 等,我该怎么办?
此外,这些库在技术上是“C”语言的一部分,还是只是非常有用且有效的必要库?
【问题讨论】:
如果这是一个主观或重复的问题,我深表歉意。搜索起来有点尴尬,所以我不确定要包含哪些字词。
我想知道的是,当您不包含 stdio 和 stdlib 等标准库时,C 中的基本基础工具/功能是什么。
如果没有printf()、fopen() 等,我该怎么办?
此外,这些库在技术上是“C”语言的一部分,还是只是非常有用且有效的必要库?
【问题讨论】:
C 标准有这样的说法(5.1.2.3/5):
对符合要求的最低要求 实现是:
— 在序列点,易失性对象 在以前的意义上是稳定的 访问是完整的和后续的 访问尚未发生。
——程序终止时,所有数据 写入文件应相同 结果是执行 根据摘要计划 语义会产生。
——输入输出动态 互动设备应发生 如指定 7.19.3.
因此,如果没有标准库函数,程序唯一可以保证具有的行为与 volatile 对象的值有关,因为您无法使用任何有保证的文件访问或“交互式设备”。 “纯 C”仅通过标准库函数提供交互。
不过,纯 C 并不是全部,因为您的硬件可能具有某些地址,这些地址在读取或写入时会执行某些操作(无论是 SATA 还是 PCI 总线、原始视频内存、串行端口,等等哔声或闪烁的 LED)。因此,了解您的硬件,您可以在不使用标准库函数的情况下用 C 编写大量代码。潜在地,您可以实现 C 标准库,尽管这可能需要访问特殊的 CPU 指令以及特殊的内存地址。
但是在纯 C 中,没有扩展,并且标准库函数被删除,除了读取命令行参数、做一些工作并从main 返回状态码之外,你基本上什么也做不了。这不是被嗤之以鼻的,它仍然是图灵完备的,受资源限制,虽然你唯一的资源是自动和静态变量,没有堆分配。这不是一个非常丰富的编程环境。
标准库是 C 语言规范的一部分,但在任何语言中,“本身”的语言和库之间都存在一条线。这是一个概念上的差异,但最终原则上并不是一个非常重要的差异,因为标准说它们是结合在一起的。任何做非标准事情的人都可以像删除库一样容易地删除语言特性。无论哪种方式,结果都不是 C 的一致实现。
请注意,C 的“独立”实现只需要实现标准的子集,包括不包括任何 I/O,因此您处于我上面描述的位置,依赖于特定于硬件的扩展来获得做任何有趣的事。如果您想根据标准区分“核心语言”和“库”,那么这可能是划清界限的好地方。
【讨论】:
volatile uint32_t * 或其他方式使用它;这是volatile 的一部分。虽然是的,但您经常需要屏障指令或其他不属于纯 C 的东西,并且会通过普通 C 实现中的扩展来公开。
如果没有printf()、fopen()等怎么办?
只要您知道如何连接您正在使用的系统,您就可以不用标准 C 库。在只有几千字节内存的嵌入式系统中,您可能根本不想使用标准库。
这是一个Hello World!在 Linux 和 Windows 上不使用任何标准 C 函数的示例:
例如在 Linux 上,您可以直接在内联汇编中调用 Linux 系统调用:
/* 64 bit linux. */
#define SYSCALL_EXIT 60
#define SYSCALL_WRITE 1
void sys_exit(int error_code)
{
asm volatile
(
"syscall"
:
: "a"(SYSCALL_EXIT), "D"(error_code)
: "rcx", "r11", "memory"
);
}
int sys_write(unsigned fd, const char *buf, unsigned count)
{
unsigned ret;
asm volatile
(
"syscall"
: "=a"(ret)
: "a"(SYSCALL_WRITE), "D"(fd), "S"(buf), "d"(count)
: "rcx", "r11", "memory"
);
return ret;
}
void _start(void)
{
const char hwText[] = "Hello world!\n";
sys_write(1, hwText, sizeof(hwText));
sys_exit(12);
}
您可以查看“系统调用”的手册页,您可以在其中找到如何进行系统调用。在 Intel x86_64 上,您将系统调用 ID 放入 RAX,然后返回值将存储在 RAX 中。参数必须按此顺序放入 RDI、RSI、RDX、R10、R9 和 R8 中(使用参数时)。
一旦你有了这个,你应该看看如何在 gcc 中编写内联汇编。 syscall 指令会更改 RCX、R11 寄存器和内存,因此我们将其添加到 clobber 列表中,让 GCC 知道它。
GNU 链接器的默认入口点是_start。通常标准库提供它,但没有它你需要提供它。 它不是一个真正的函数,因为没有调用者函数可以返回。所以我们必须进行另一个系统调用来退出我们的进程。
编译:
gcc -nostdlib nostd.c
它输出Hello world!,然后退出。
在 Windows 上,系统调用不会发布,而是隐藏在另一个抽象层 kernel32.dll 后面。无论您是否愿意,它总是在您的程序启动时加载。因此,您可以简单地从 Windows SDK 中包含 windows.h 并照常使用 Win32 API:
#include <windows.h>
void _start(void)
{
const char str[] = "Hello world!\n";
HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD written;
WriteFile(stdout, str, sizeof(str), &written, NULL);
ExitProcess(12);
}
windows.h 与标准 C 库无关,因为您也应该能够用任何其他语言编写 Windows 程序。
你可以像这样使用 MinGW 工具编译它:
gcc -nostdlib C:\Windows\System32\kernel32.dll nostdlib.c
然后编译器足够聪明,可以解析导入依赖并编译你的程序。
如果你反汇编程序,你只能看到你的代码在那里,没有标准库膨胀。
所以你可以在没有标准库的情况下使用 C。
【讨论】:
你能做什么?一切!
C 中没有魔法,也许除了预处理器。
最难的可能是编写 putchar - 因为那是依赖于平台的 I/O。
创建你自己的可变参数版本是一个很好的本科练习,一旦你有了它,就做你自己版本的 vaprintf,然后是 printf 和 sprintf。
1986 年我在 Macintosh 上完成了所有这些工作,当时我对 Lightspeed C 提供的 stdio 例程不满意 - 用 win_putchar、win_printf、in_getchar 和 win_scanf 编写了我自己的窗口处理程序。
整个过程称为引导,它可能是编码中最令人满意的体验之一 - 使用具有相当实际意义的基本设计。
【讨论】:
char * 数据转换为函数指针,您可以在基本上任何系统上嵌入和执行任意 C 汇编代码。
_IO_putc_unlocked,这是一个在内部结构上执行一些指针运算的宏。
如果您不需要标准库,您当然没有义务使用它们。相当多的嵌入式系统要么没有标准库支持,要么出于某种原因无法使用它。该标准甚至专门讨论了不支持库的实现,C99 标准 5.1.2.1 “独立环境”:
在独立环境中(C 程序的执行可能在没有任何操作系统优势的情况下发生),程序启动时调用的函数的名称和类型是实现定义的。除了第 4 条要求的最小集合之外,独立程序可用的任何库设施都是实现定义的。
C99 要求在独立实现中可用的标头是 <float.h>、<iso646.h>、<limits.h>、<stdarg.h>、<stdbool.h>、<stddef.h> 和 <stdint.h>。这些头文件只定义类型和宏,因此不需要函数库来支持它们。
如果没有标准库,您将完全依赖自己的代码、任何可能对您可用的非标准库,以及您可能能够与之交互的任何操作系统系统调用(可能被视为非标准库) -标准库调用)。很可能你必须让你的 C 程序调用汇编例程来连接设备和/或平台上的任何操作系统。
【讨论】:
std 库是“标准”库,因为要使 C 编译器符合标准(例如 C99),这些库必须是“可包含的”。有关可能有助于理解这意味着什么的有趣示例,请在此处查看 Jessica McKellar 的挑战:
http://blog.ksplice.com/2010/03/libc-free-world/
编辑:以上链接已失效(感谢 Oracle ...) 我认为这个链接反映了这篇文章:https://sudonull.com/post/178679-Hello-from-the-libc-free-world-Part-1
【讨论】:
你不能做很多事情,因为大多数标准库函数都依赖于系统调用;您只能使用内置的 C 关键字和运算符来做些什么。它还取决于系统;在某些系统中,您可能能够以某种方式操作位,从而产生一些外部功能,但这很可能是例外而不是规则。
然而,C 的优雅在于它的简单性。与包含许多功能作为语言一部分的 Fortran 不同,C 非常依赖于它的库。这赋予了它很大程度的灵活性,但代价是平台之间的一致性有所降低。
这很有效,例如,在实现完全独立的“库”的操作系统中,它可以通过内核本身的实现提供类似的功能。
库的某些部分被指定为 ANSI C 的一部分;我想它们是语言的一部分,但不是其核心。
【讨论】:
它们都不是语言关键字的一部分。但是,所有 C 发行版都必须包含这些库的实现。这确保了许多程序的可移植性。
首先,理论上你可以使用 C 和汇编的组合自己实现所有这些功能,所以理论上你可以做任何事情。
实际上,库函数主要是为了节省您重新发明轮子的工作。有些东西(如字符串和库函数)更容易实现。其他事情(如 I/O)在很大程度上取决于操作系统。为一个操作系统编写自己的版本是可能的,但它会降低程序的可移植性。
但是你可以编写一些程序来做很多有用的事情(例如,计算 PI 或生命的意义,或者模拟自动机)。但是,除非您直接将操作系统用于 I/O,否则很难观察到输出是什么。
在日常编程中,一种编程语言的成功通常需要一个有用的高质量标准库和用于许多有用任务的库。这些可以是第一方或第三方,但必须存在。
【讨论】:
CRT 与关键字和语法一样,是 C 语言的一部分。如果您使用 C,您的编译器必须为您的目标平台提供实现。
编辑: 它与 C++ 的 STL 相同。所有语言都有一个标准库。也许汇编程序是例外,或者其他一些严重的低级语言。但大多数中/高级都有标准库。
【讨论】:
标准 C 库是 ANSI C89/ISO C90 的一部分。我最近一直在为以前不符合 ANSI 的 C 编译器开发库。
P.J. Plauger 的书The Standard C Library 是该项目的一个很好的参考。除了阐明标准的要求外,Plauger 还解释了每个 .h 文件的历史以及一些 API 设计背后的原因。他还提供了该库的完整实现,当标准中的某些内容不清楚时,这对我有很大帮助。
该标准描述了 15 个头文件(包括 stdio.h、stdlib.h 以及 float.h、limits.h、math.h、locale.h 等)中每一个的宏、类型和函数。
除非包含标准库,否则编译器不能声称是 ANSI C。
【讨论】:
汇编语言具有简单的命令,可以将值移动到 CPU、内存和其他基本功能的寄存器,以及执行机器的核心功能和计算。 C 库基本上是汇编代码块。您还可以在 C 程序中使用汇编代码。 var 是汇编代码指令。当您在数字前使用0x 使其成为十六进制时,即为汇编指令。汇编代码是机器码的可读形式,是电路路径实际开关状态的直观形式。
因此,虽然机器代码以及汇编代码内置于机器中,但 C 语言是由各种预先形成的代码组合组合而成,包括您自己的函数,这些函数可能部分是汇编语言,部分是调用汇编语言或其他 C 库的其他函数。所以汇编代码是所有编程的基础,之后任何人都可以猜测什么是什么。这就是为什么有这么多语言而真正的标准这么少的原因。
【讨论】:
是的,你可以在没有库的情况下做很多事情。
救命稻草是 GCC 中的 __asm__。这是一个关键字,所以可以。
主要是因为每种编程语言都是基于汇编构建的,您可以直接在某些操作系统下进行系统调用。
【讨论】: