GNU C Compiler 其实现在更准确的应该叫 GNU Compiler Collection
-Processor->C Compiler->Assembler->Linker
C Pre
其中的前两个步骤通常可以替换 例如++ Pre-Processor->C++ Compiler
C-Processor->Objective-C Compiler
Objective C Pre
Cfortran Fortran Compiler Go Compiler
GCC能支持多种语言。
我们可以写C ,写汇编.s文件,C代码中调用汇编函数,.s由GCC Assembler处理,汇编+C代码形成我们想要的可执行文件。
当然可以使用其他现代语言来生成汇编得到目标文件。
gfortran -c is_prime.f90 编译汇编fortran文件。
gcc -c count_primes.c 编译汇编C文件。
gfortran count_primes.o is_prime.o -o count_primes
把两个目标文件,链接成一个可执行文件。
不同语言共存同一个可执行文件中 ,关键在于链接器
。
rustc --create-type=staticlib is_prime.rs
在C代码中声明rust中的函数 编译C代码,并将其与Rust编译好的库进行链接
gcc main.c libis_prime.a -o count_primes
反过来rust调用C也类似
仅仅互调函数,但不一定可以链接成功,不同语言出来的机器码可能底层指定不同,例如add(int a, int b)
返回 a+b
,这两个临时变量放在哪里,不同语言可能有自己的处理方式,放在不同的寄存器中,另一个语言可以从不是调用放放的位置取数据,导致行为未定义。传参和返回值
引用传递 值传递都有很大的不确定性。
即使两个编译器生成了有效可执行的汇编代码,最终链接二进制文件也会不一致,除非双方在通信方式上达成一致。
这类底层规则就是由所谓的应用二进制接口ABI Application Binary Interface 来定义的。
A语言调用B语言,则调用应该符合B语言的ABI预期。A调整去适应B,或者B调正去适应A。
C语言中用 extern
extern int is_prime(int n);
Go语言中用
[no_mangle]
#"C" fn is_prime(n: i32) -> i32 pub extern
Fortran用
function add(a, b) bind (C, name="add")
在Go中甚至可以在import "C"
前,用一段特殊的注释块来包含C头文件,甚至还能直接在Go源文件中写内联C代码
package main
/*
#cgo CFLAGS -I.
#cgo LDFLAGS -L. -ladd
#include "add.h"
*/
import "C"
import "fmt"
func main(){
:= C.add(3,4)
result }
package main
/*
int add(int a, int b){
return a+b;
}
*/
import "C"
import "fmt"
func main(){
:= C.add(3,4)
result }
每种语言都有自己的声明方式,但在编译时,这些声明都是这个函数会和其他语言写的代码交互,请确保生成的汇编代码遵循预期的ABI。