C语言 rustc 如何能够从 bash 进程替换中编译源代码,而 gcc 不能?


$ rustc <(echo 'fn main(){ print!("Hello world!");}')
$ ls
63
$ gcc <(echo '#include<stdio.h> int main(){ printf("Hello world!n"); return 0;}')
/dev/fd/63: file not recognized: Illegal seek
collect2: error: ld returned 1 exit status

为什么ld无法链接程序?

gcc命令主要是调度引擎。对于每个输入文件,它根据文件名的扩展名确定它是哪种类型的文件,然后将文件传递给适当的处理器。因此.c文件由 C 编译器编译,.h文件组装成预编译头,.go文件发送到 cgo 编译器,依此类推。

如果文件名没有扩展名或扩展名无法识别,gcc假定它是某种对象文件,应该参与最后的链接步骤。这些文件被传递给collect2实用程序,然后该实用程序调用ld,可能调用两次。进程替换就是这种情况,它会产生像/dev/fd/63这样的文件名,其中不包括扩展名。

ld不依赖于文件名来标识目标文件格式。它通常由几个不同的对象文件识别器构建,每个识别器都依赖于某种"幻数"(即文件开头或附近的特殊模式(。它一次调用一个识别器,直到找到一个乐于解释文件的识别器。如果该文件未被识别为二进制格式,ld假定它是一个链接器脚本(这是一个纯文本文件(,并尝试将其解析为二进制格式。

当然,在尝试之间,ld需要倒带文件,并且由于进程替换安排传递管道而不是文件,因此查找将失败。(如果您尝试通过 stdin 重定向将文件传递到管道,也会发生同样的事情,您可以这样做:如果您将-指定为文件名,gcc 会将stdin作为文件进行处理。但它坚持要你告诉它它是什么样的文件。见下文。

由于ld无法回退文件,因此在文件与其第一次猜测不匹配后,它将失败。因此,来自ld的错误消息有点误导,因为您可能认为该文件已经编译并且随后的失败发生在链接步骤中。事实并非如此;由于文件名没有扩展名,gcc直接跳到链接阶段,几乎立即失败。

对于进程替换、管道、标准输入和名称不正确的文件,您仍然可以手动告诉gcc文件是什么。您可以使用-x选项执行此操作,该选项记录在 GCC 手册中有关控制输出类型的选项的部分中(尽管在这种情况下,该选项实际上控制输入类型(。

互联网上流传着许多这样的问题的答案,包括StackOverflow上的各种答案,声称GCC试图检测输入文件的语言。它没有这样做,而且从来没有这样做过。(我怀疑它永远不会,因为它编译的一些语言彼此非常相似,以至于准确的检测是不可能的。唯一执行自动检测的组件是ld,并且只有在 GCC 不可撤销地决定将输入文件视为目标文件或链接器脚本时才这样做。

至少在您的情况下,您可以在手动指定输入语言时使用进程替换,使用-xc.但是,您应该在包含语句后放置换行符。

$ gcc -xc <(echo '#include<stdio.h>
int main(){ printf("Hello world!n"); return 0;}')
$ ls
a.out
$ ./a.out 
Hello world!

有关这起作用的可能原因,请参阅查尔斯的答案和对此答案的评论。

最新更新