编译器是想滚雪球一样滚出来的,一开始用机器语言写一个很功能有限的汇编器(汇编接近机器语言,几乎就是简单的替换,比较好写),然后用它编译更强大的汇编器/编译器。更高级的语言由稍微低级点的语言编写,然后发展到合适的程度之后,某些语言实现了自举,即自己编译自己的编译器。
编译的结果是二进制文件,二进制文件其实是和系统关系并没有那么大,只是一个内存的镜像。具体的Windows或者Linux之间不同的只是封装格式PE/EFI的区别,这些信息只是用来告诉系统该怎么加载这个镜像而已,自己写一个操作系统来加载RAW内存镜像,这样的程序也是可以运行的。如果一个程序没有用系统提供的API,能够直接访问硬件中断的话就和系统无关了。不过现在的系统都有保护,不允许直接调用硬件中断,都是调用系统的API,系统再调用硬件,来执行。
不同语言不仅仅是语法不同,编译结果的执行方式也是不一样的。Java编译结果是字节码,要在Java虚拟机里再解释编译成Java虚拟机的字节码来运行。有些程序类似Go有runtime的概念,能够在运行时自省修改自己。lisp这种语言甚至数据和程序都不区分,数据就是程序。
不过在微观的看,这些都只是运行方式/实现方式不同,但是运行过程都是一样的,这是冯诺依曼体系决定的,即CPU永远执行内存中下一条指令。