$ 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
不能链接程序?$ 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手册中有关控制输出类型的选项中记录(尽管在这种情况下,该选项实际上控制输入类型)。ld
,并且只有在GCC已经决定将输入文件视为对象文件或链接脚本时才执行。至少在您的情况下,当手动指定输入语言时,可以使用进程替换,使用-xc
。然而,在包含语句之后应该加上换行符。
$ gcc -xc <(echo '#include<stdio.h>
int main(){ printf("Hello world!\n"); return 0;}')
$ ls
a.out
$ ./a.out
Hello world!
关于为什么这样会有效,可以参考查尔斯的回答以及对该回答的评论。
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
ld
(将其视为对象文件)可以解释一切。 - Charles Duffyld
(通过collect2
)。逻辑是ld
尝试各种方式来解释文件,最终回退到“链接脚本”。但由于它无法倒回文件,因此实际上无法进行测试。 - rici