rustc如何能够编译bash进程替换的源代码,而gcc却不能?

4
$ 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不能链接程序?

4
跨站点重复:https://unix.stackexchange.com/questions/164107/why-does-bash-process-substitution-not-work-with-some-commands - Benjamin W.
2
Gcc是一个编译器集合。如果您没有使用“-x c”(用于C或其他语言)指定语言,它就不知道要使用哪个后端,因为没有文件扩展名告诉它,所以它必须检查文件。它通过寻找来实现,因为读取可能会不可逆地消耗单个流,并且在单个流上失败(bash进程替换创建FIFO - 单次流),因此它失败了。 - Petr Skocik
啊——调用ld(将其视为对象文件)可以解释一切。 - Charles Duffy
@CharlesDuffy:是的,我使用了strace来追踪错误,而且确实是来自于ld(通过collect2)。逻辑是ld尝试各种方式来解释文件,最终回退到“链接脚本”。但由于它无法倒回文件,因此实际上无法进行测试。 - rici
2个回答

7
gcc命令主要是一个调度引擎。对于每个输入文件,它会根据文件名的扩展名确定文件类型,并将文件传递给适当的处理器。因此,.c文件由C编译器编译,.h文件被组装成预编译头文件,.go文件被发送到cgo编译器等等。
如果文件名没有扩展名或扩展名无法识别,则gcc会假定它是某种对象文件,应该参与最终的链接步骤。这些文件将被传递给collect2实用程序,然后调用ld,可能会调用两次。这将发生在进程替换的情况下,它会产生像/dev/fd/63这样的文件名,不包括扩展名。 ld不依赖文件名来识别对象文件格式。它通常使用几个不同的对象文件识别器进行构建,每个识别器都依赖于某种“幻数”(即,在文件开头或附近的特殊模式)。它按顺序调用这些识别器,直到找到满意解释文件的识别器。如果文件未被识别为二进制格式,则ld会将其视为链接器脚本(即纯文本文件)并尝试解析它。
当然,在尝试之间,ld需要倒回文件,并且由于进程替换安排传递管道而不是文件,因此寻找失败。 (如果您尝试通过将stdin重定向到管道来传递文件,则会发生同样的事情,您可以这样做:如果指定-作为文件名,则gcc将处理stdin作为文件。但它要求您告诉它文件的类型。请参见下面。)
由于ld无法倒回文件,因此在文件未匹配其第一个猜测后,它将失败。因此,ld的错误消息有点误导性,因为您可能认为文件已经被编译,随后的失败是在链接步骤中发生的。那不是真的;因为文件名没有扩展名,gcc直接跳过了链接阶段,几乎立即失败。
在进程替换、管道、stdin和命名错误的文件的情况下,您仍然可以手动告诉gcc文件类型。您可以使用-x选项来完成这个操作,该选项在GCC手册中有关控制输出类型的选项中记录(尽管在这种情况下,该选项实际上控制输入类型)。
在互联网上有很多关于这类问题的答案,包括在StackOverflow上的各种答案,声称GCC试图检测输入文件的语言。它不会这样做,也从未这样做过。(而且我怀疑它永远不会这样做,因为它编译的某些语言彼此非常相似,准确的检测将是不可能的。)唯一自动检测的组件是ld,并且只有在GCC已经决定将输入文件视为对象文件或链接脚本时才执行。

3

至少在您的情况下,当手动指定输入语言时,可以使用进程替换,使用-xc。然而,在包含语句之后应该加上换行符。

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

关于为什么这样会有效,可以参考查尔斯的回答以及对该回答的评论。


@CharlesDuffy gcc -c some-fifo.c 是有效的。尝试 rm -f some-fifo.c && mkfifo some-fifo.c; echo '#include<stdio.h> int main(){ printf("Hello world!\n"); return 0;}' > some-fifo.c & gcc some-fifo.c && ./a.out(或使用 -c)。 - Petr Skocik
...嗯。我想我实际上是在一个gcc二进制文件是包装器而不是真正的GCC的平台上运行。如果/当名称不明确时,扫描文件内容是有意义的。 - Charles Duffy
谢谢你们两个的调查。我不应该对原因做任何假设,因此我删除了我的猜测。我们应该尝试更大更复杂的C程序。也许进程替换只在某些情况下起作用。 - Socowi
@Socowi:在使用“-x”指示GCC使用哪个组件来处理文件时,进程替换的效果非常好。GCC始终是一个包装器,这就是为什么称其为“编译器集合”的原因。 - rici

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接