程序员是不是一定要懂java编译原理理

程序员是不是一定要懂编译原理? - 知乎221被浏览11943分享邀请回答1添加评论分享收藏感谢收起每个程序员是否都应该学习编译原理、操作系统和计算机组成原理? - 知乎116被浏览11116分享邀请回答45 条评论分享收藏感谢收起31 条评论分享收藏感谢收起查看更多回答抱怨贴,做程序员到底要学多少东西,学到什么程度才是个头--程序员太苦逼了
353回复/10亮 62719浏览
大学无聊,不小心学了编程,误入了IT行业,然后发现自从进入这个行业之后,就是无止境的学习,有学不完的东西。学的太痛苦了,学了几年了就没停过。。。看完html、css基础、又有高级的html、css、又有html5、css3、后面还有js,然后光会几本的js并没有什么卵用,还要会jquery和各种框架,会了这些框架之后,不算牛逼,你还要去学js的几本原理,然后js是相当的奇葩,完全掌握的难度丝毫不亚于JAVA with JVM。前端还没完全吃透,然后搞后台,就最简单的PHP来说,除了会写普通代码,还要学数据库,数据库涉及到SQL又要学、表结构又要设计的好,还有服务器又要学个APACHE或NGINX等,各个都要会配置,各个都要会装,哦,对了,一般都是在Linux下的,所以你又要学Linux、会基本的命令,然后安装编译又是烦的很的事情,都过去了之后,你想稍微先进点,还有各种NOSQL、缓存、负载、主从等着你去配置,全趟平了之后。其实也就是个普通程序员的水平,后面其实还有一大堆东西呢。能否自己写PHP框架、能否轻松二次开发、能否对PHP做异步、怎么扛高并发、如何理解PHP底层原理、如何编写PHP内核扩展,省略无数个etc.。完全可以学死人。上面看上去很多,其实还是很LOW,为了高大上,光会那些三脚猫的编程技术根本不够大牛看的,首先,你起码要学下C和C++,否则你对底层的理解是远远不够的,可能会傻傻的觉得字符串类型就应该是和诸如整形之类的一摸一样,比如内存只需要new就行了。会了C和C++就够了吗?还差远了,它们只是最基本的工具,你要能用它写基本的程序,能浏览一些开源代码,能对系统API有更多的了解,然后里面会涉及到计算机网络、操作系统等,若你想看汇编,还要学下计算机组成原理,想很透彻还有编译原理等着你,想更好的了解,那还需要补习离散数学等。然后还忘了数据结构和算法,这个要玩的深入不知道要投下去多少时间(前面的东西也一样)底层差不多了(也仅仅是差不多),你还要了解现在的大数据,设计各种分析、各种机器学习,各种数据可视化,各种数学(啃数学书会cost太多太多的时间)如果要和移动端合作,你可能还要去懂移动端的东西,各种GUI编程,底层又会碰到图形学最后最后,技术发展超级快,你一定要跟上,不断的学,工资高也没什么时间玩,估计想做NB程序员,就要天天当高三吧!又或者用考研来形容更合适(毕竟周围人都可以各种玩,自己苦逼到底)
这些回帖亮了
关键你要摸到 各种制品的背后 的思路,没什么技术是凭空出来的,都是从以前的技术发展出来的。
基础知识肯定是必须的,但是也没要求你一闭眼睛算法就能倒背如流的地步啊,但是一定要有概念啊,至少解决问题的时候,你得知道往哪个方向考虑问题吧。
举个例子,数据库,通常都会遇到检索速度的问题,为了加快检索速度,我们会加index,背后的道理是用空间换时间,或者把内容载入内存,这背后的道理是提高IO速度。
那你脱离了数据库,比如说你自己设计了一个数据结构,他检索慢,你是不是也得考虑加个index啊,是不是也得考虑提高IO速度啊?
道理都是通的。。。你把Oracle数据库提供的那一套功能,按图索骥去找SQL Server,基本都差不多。
或者你按照C++里面的面向对象的思想去学学Java,这有啥难的,很快就能上手了.
再比如你的整体架构遇到了性能问题, 分散啊, 读写分离啊, 横向拓展,纵向拓展,
技术再怎么变, 换汤不换药啊..
说说你的移动端问题, 我们都知道 做画面, 无非就是一个画面表现力(控件布局), 后台事件循环处理, 以及暴露给你的事件处理接口 . 这些玩意从 Windows 3.1 时代就已经开始这么做了,到了 Android , 最后的道理也是一样的, 你如果知道画面处理机制的本源, 新形式来了算啥... 不过是学学新技术的方言而已..
其实在IT行业越混越觉得, 其实这个行业的学习成本其实也没那么高, 关键是你对于问题的理解能力有多强了.
已有JRs,赞赏了 5 虎扑币
机械狗镇楼~~
程序员是事物驱动的,不是我要学这个我要学那个,而是我承担的某个需求,我要开发的应用要用到什么语言。
我严重怀疑楼主还没工作过。发自手机虎扑
引用12楼 @ 发表的:
敢问大神持续学了多少年了
我08年上大学开始学,现在7年半了,985211大学4年毕业3年半,步行街标准高一点点的收入,还是有脸说一说自己的经验的吧
我觉得楼主这类问题都是伪命题……程序语言技术框架的更新换代,万变不离其宗,底层还是一样的东西,无非就是让你更便捷高效的使用罢了。
没有理解到本质,只在外在的语法糖上纠结,才会产生技术好多啊这类抱怨……
引用27楼 @ 发表的:
求问大神,我也是这专业的,我们应该学习哪些底层最基本的东西,把这些基本学好
我说的是我自己的学法,比较偏科班,非速成,你可以参考一下
基础:数据结构 算法 c/c++
初期比较核心的就这3样,数据结构都要熟练的掌握,这个没什么好说的
然后算法其实用的比较少,你看了知道有这么回事就行了,不必死记硬背
c/c++ 对于互联网行业,几乎不会用到这种语言了……游戏和传统软件还可能会用到,但是我觉得c/c++对于底层实现,内存管理,cpu时间优化,代码封装有巨大的锻炼作用,基本学好用好,写过2~3w行代码,学任何语言都很简单,无非就是语法变了一点而已
进阶基础:linux web server(网络协议) db
这个有个很简单的方法,自己配一套lamp环境,知道是什么回事,写个小blog程序就掌握40%了,剩下的遇到问题google一下,慢慢提高就好
db建议还是看看教科书,了解一下优化sql语句的基本知识
互联网行业一般需求3种技术 后台(php java python) 客户端(ios android) 前端(js)
自己有兴趣 学一个方向 穿插着学就好了
学后台的最好了解一些前端基本知识
客户端和前端都了解一些后端基本知识(根据我前面说的你是天然了解的)
然后工作了 所谓的新技术 就是这些东西换个皮罢了。。很容易掌握的
知足吧,比我学医强多了。
程序员面对台电脑专心写程序就行了,而且收入那么高,专科三年都行吧。
学医一般5年,出来还要三到五年,甚至更久。
谁都不容易,但相对来说,程序员已经很好了,你们在哭,我们岂不是没脸活了。
引用0楼 @ 发表的:
大学无聊,不小心学了编程,误入了IT行业,然后发现自从进入这个行业之后,就是无止境的学习,有学不完的东西。/div>
就这些,想拿个好的base也不够了。高数没学好不去读个研究生,你就别想去深入去搞机器学习,广告竞价,数据挖掘,模式识别等这些base较高的领域。写程序真的只是最最基本的要求了。
然而,最后我发现,在我们这一行拿的最高的那些人,其实到最后啥也不会,就是会说会忽悠。
你说的我全都会,真没觉得有什么难的……难道别的职业也是毕业了就完全不需要提高自己就可以工作30年的吗……
楼上那些大神保证持续艰苦学习了好一段时间,而我现在属于已经投入了一长段时间进去,退出太可惜,前进太痛苦的阶段。作为一个脑子一热半路出家搞编程的人,给那些想转行进入这个行业的人的忠告就是:学一门编程语言,学一套API,看上去能做出一个完整的软件了,但实际上这点知识只是冰山一角,而且基于此的基本的开发能力也会随着时代的发展变得越来越没价值,如果要长远发展,要补回科班的技术,那堆技术对外行来说很难对付,会陷入长期的学习中,没有非常持久的激情很难完成完美的转型,悲催的情况就成了拼体力的码畜了。
码农如果老了,直接报废进垃圾堆,其他行业还好些,老了能成为资深某师。那为什么说搞计算机、互联网的大牛全是30岁以上?因为那是架构师 ,设计设计整个框架的 ,BAT这种级别的公司才需要几十个人吧 &&不是写具体代码的码农,写代码得35以下 &年纪大身体也吃不消&[ 此帖被钢铁中后卫在 16:00修改 ]
机械狗镇楼~~
没这么夸张吧 学到一定程度就吃老本了
怀疑程序员真的那么好吗,天天狂学狂加班,很多行业也可以做的很好,比如你卖楼(如果楼市不好就换个行业),天天研究,研究户型产品,研究客户,各种挑灯夜战,各种刻苦,也不会混的很差吧
所以坚持干下去的还是很少
没有耐力和强大热情,不要混这行,淘汰更新太快,这行不是靠经验吃饭,老技术写的天花乱坠没毛用,
知足吧,比我学医强多了。
程序员面对台电脑专心写程序就行了,而且收入那么高,专科三年都行吧。
学医一般5年,出来还要三到五年,甚至更久。
谁都不容易,但相对来说,程序员已经很好了,你们在哭,我们岂不是没脸活了。
你说的我全都会,真没觉得有什么难的……难道别的职业也是毕业了就完全不需要提高自己就可以工作30年的吗……
程序员学不完吧。。。
听起来好像很吊啊
额都一样。。。
金融行业也是的。。。
我现在准备考CPA,年后开始CFA和司法的坑
引用6楼 @ 发表的:
你说的我全都会,真没觉得有什么难的……难道别的职业也是毕业了就完全不需要提高自己就可以工作30年的吗……
敢问大神持续学了多少年了
码农如果老了,直接报废进垃圾堆,其他行业还好些,老了能成为资深某师。那为什么说搞计算机、互联网的大牛全是30岁以上?因为那是架构师 ,设计设计整个框架的 ,BAT这种级别的公司才需要几十个人吧 &&不是写具体代码的码农,写代码得35以下 &年纪大身体也吃不消&[ 此帖被钢铁中后卫在 16:00修改 ]
做软件就是这样,日新月异的技术更新,猿猴只要不跟上就很容易被淘汰...
技术就这样。
以前老师给我们说,我们现在学的技术是人家国外二三十年前的东西。
你说如果不不停的学,如何能赶得上人家?
当然,单纯的混口饭吃,差不多就行,但现在就算混饭吃也得不停的吸收新知识吧。
说不定哪天公司来个新人就把你淘汰了呢?
如果爱好这门,那就更得不停的学了。
最后,刚刚入行时,公司的服务器师语重心长的跟我说:千万别做程序…………
所以后来我改行了……哈哈哈。
幸好我也改了,以我那三脚猫的能力,说不定怎么死的都不知道。
程序员已经是门槛最低,学习最简单,投入产出比最高的工科专业了。你见过其他什么专业能自学成才的?
关键你要摸到 各种制品的背后 的思路,没什么技术是凭空出来的,都是从以前的技术发展出来的。
基础知识肯定是必须的,但是也没要求你一闭眼睛算法就能倒背如流的地步啊,但是一定要有概念啊,至少解决问题的时候,你得知道往哪个方向考虑问题吧。
举个例子,数据库,通常都会遇到检索速度的问题,为了加快检索速度,我们会加index,背后的道理是用空间换时间,或者把内容载入内存,这背后的道理是提高IO速度。
那你脱离了数据库,比如说你自己设计了一个数据结构,他检索慢,你是不是也得考虑加个index啊,是不是也得考虑提高IO速度啊?
道理都是通的。。。你把Oracle数据库提供的那一套功能,按图索骥去找SQL Server,基本都差不多。
或者你按照C++里面的面向对象的思想去学学Java,这有啥难的,很快就能上手了.
再比如你的整体架构遇到了性能问题, 分散啊, 读写分离啊, 横向拓展,纵向拓展,
技术再怎么变, 换汤不换药啊..
说说你的移动端问题, 我们都知道 做画面, 无非就是一个画面表现力(控件布局), 后台事件循环处理, 以及暴露给你的事件处理接口 . 这些玩意从 Windows 3.1 时代就已经开始这么做了,到了 Android , 最后的道理也是一样的, 你如果知道画面处理机制的本源, 新形式来了算啥... 不过是学学新技术的方言而已..
其实在IT行业越混越觉得, 其实这个行业的学习成本其实也没那么高, 关键是你对于问题的理解能力有多强了.
已有JRs,赞赏了 5 虎扑币
引用5楼 @ 发表的:
知足吧,比我学医强多了。
程序员面对台电脑专心写程序就行了,而且收入那么高,专科三年都行吧。
学医一般5年,出来还要三到五年,甚至更久。
谁都不容易,但相对来说,程序员已经很好了,你们在哭,我们岂不是没脸活了。
你医科还不知足?40岁以上个个是专家,越老越吃香,技术和经验可以积累。
程序员呢,还不是年轻的时候吃口青春饭,能干到40岁以后吗?
码农如果老了,直接报废进垃圾堆,其他行业还好些,老了能成为资深某师。那为什么说搞计算机、互联网的大牛全是30岁以上?
因为那是架构师 ,设计设计整个框架的 ,BAT这种级别的公司才需要几十个人吧
不是写具体代码的码农,写代码得35以下
年纪大身体也吃不消[ 此帖被钢铁中后卫在 15:59修改 ]
引用15楼 @ 发表的:
技术就这样。
以前老师给我们说,我们现在学的技术是人家国外二三十年前的东西。
你说如果不不停的学,如何能赶得上人家?
当然,单纯的混口饭吃,差不多就行,但现在就算混饭吃也得不停的吸收新知识吧。
说不定哪天公司来个新人就把你淘汰了呢?
如果爱好这门,那就更得不停的学了。
最后,刚刚入行时,公司的服务器师语重心长的跟我说:千万别做程序…………
所以后来我改行了……哈哈哈。
幸好我也改了,以我那三脚猫的能力,说不定怎么死的都不知道。
改做什么了
做程序真的得真心热爱,不然真的很折磨,天天改需求,加需求,别说整什么架构,算法。就是把功能实现出来都他妈写不完
同程序员飘过。。。毕业两年,也有楼主的这种疑问,目前有点迷惘,不知道以后要怎么走,希望在这个帖子可以看到启发自己的建议发自手机虎扑
只要有钱,管不了这么多了
引用17楼 @ 发表的:
关键你要摸到 各种制品的背后 的思路,没什么技术是凭空出来的,都是从以前的技术发展出来的。
基础知识肯定是必须的,但是也没要求你一闭眼睛算法就能倒背如流的地步啊,但是一定要有概念啊,至少解决问题的时候,你得知道往哪个方向考虑问题吧。
举个例子,数据库,通常都会遇到检索速度的问题,为了加快检索速度,我们会加index,背后的道理是用空间换时间,或者把内容载入内存,这背后的道理是提高IO速度。
那你脱离了数据库,比如说你自己设计了一个数据结构,他检索慢,你是不是也得考虑加个index啊,是不是也得考虑提高IO速度啊?
道理都是通的。。。你把Oracle数据库提供的那一套功能,按图索骥去找SQL Server,基本都差不多。
或者你按照C++里面的面向对象的思想去学学Java,这有啥难的,很快就能上手了.
再比如你的整体架构遇到了性能问题, 分散啊, 读写分离啊, 横向拓展,纵向拓展,
技术再怎么变, 换汤不换药啊..
说说你的移动端问题, 我们都知道 做画面, 无非就是一个画面表现力(控件布局), 后台事件循环处理, 以及暴露给你的事件处理接口 . 这些玩意从 Windows 3.1 时代就已经开始这么做了,到了 Android , 最后的道理也是一样的, 你如果知道画面处理机制的本源, 新形式来了算啥... 不过是学学新技术的方言而已..
其实在IT行业越混越觉得, 其实这个行业的学习成本其实也没那么高, 关键是你对于问题的理解能力有多强了.
道理确实是这样的,掌握了一个方面的基本原理后,这方面的发展都是可以掌控的,但是总觉得要hold住大多数原理还是需要相当痛苦和漫长的学习。。。
您需要登录后才可以回复,请
& 允许多选
528人参加识货团购468.00元31人参加识货团购59.00元253人参加识货团购399.00元18人参加识货团购150.00元415人参加识货团购628.00元630人参加识货团购119.00元388人参加识货团购17.00元2345人参加识货团购398.00元155人参加识货团购298.00元699人参加识货团购319.00元50人参加识货团购99.00元82人参加识货团购328.00元新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
论坛徽章:34
获得技术图书《高级C/C++编译技术》的用户有:
CUTianrui007
陌路巨额投入
活动详情:
请以上获奖者在日前将姓名,公司,职务,行业,电话,邮箱,QQ,地址,站内短信发送给以便及时给您快递奖品。
发不了站短的请加群,然后联系群主CU客服。
注:因特殊原因,每次活动的获奖者我都会通知各位,如果大家在截止日期之前还未联系到管理员,那么本次活动的得奖资格将被取消,所以请大家及时的与管理员取得联系,谢谢合作!
&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp
论坛徽章:51
恭喜恭喜啊。大前端开发者需要了解的基础编译原理和语言知识 - 知乎专栏
{"debug":false,"apiRoot":"","paySDK":"/api/js","wechatConfigAPI":"/api/wechat/jssdkconfig","name":"production","instance":"column","tokens":{"X-XSRF-TOKEN":null,"X-UDID":null,"Authorization":"oauth c3cef7c66aa9e6a1e3160e20"}}
{"database":{"Post":{"":{"contributes":[{"sourceColumn":{"lastUpdated":,"description":"不定期更新互联网技术知识点、总结归纳、整理开发技巧,相互学习,共同进步!\n\n不定期更新移动端的产品研究、见解、归纳。 \n\n分享当前主流的界面设计尺寸、主流平台的设计规范、主流APP的设计规范。 \n设计类干货资源整合,设计类技能教程等。","permission":"COLUMN_PUBLIC","memberId":,"contributePermission":"COLUMN_PUBLIC","translatedCommentPermission":"all","canManage":true,"intro":"不定期更新互联网技术文章、产品研究、工具干货。","urlToken":"lishichao","id":14044,"imagePath":"v2-05a3efacf81bef39a0f44.jpg","slug":"lishichao","applyReason":"0","name":"互联网笔记","title":"互联网笔记","url":"/lishichao","commentPermission":"COLUMN_ALL_CAN_COMMENT","canPost":true,"created":,"state":"COLUMN_NORMAL","followers":9129,"avatar":{"id":"v2-05a3efacf81bef39a0f44","template":"/{id}_{size}.jpg"},"activateAuthorRequested":false,"following":false,"imageUrl":"/v2-05a3efacf81bef39a0f44_l.jpg","articlesCount":418},"state":"accepted","targetPost":{"titleImage":"/v2-a8dda9c9b668a6c807d5dbee5dd6f5b4_r.jpg","lastUpdated":,"imagePath":"v2-a8dda9c9b668a6c807d5dbee5dd6f5b4.jpg","permission":"ARTICLE_PUBLIC","topics":[225,],"summary":"作者:bestswifter原文链接: 在我刚刚进入大学,从零开始学习 C 语言的时候,我就不断的从学长的口中听到一个又一个语言,比如 C++、Java、Python、JavaScript 这些大众的,也有 Lisp、Perl、Ruby 这些相对小…","copyPermission":"ARTICLE_COPYABLE","translatedCommentPermission":"all","likes":0,"origAuthorId":0,"publishedTime":"T22:42:25+08:00","sourceUrl":"","urlToken":,"id":3208770,"withContent":false,"slug":,"bigTitleImage":false,"title":"大前端开发者需要了解的基础编译原理和语言知识","url":"/p/","commentPermission":"ARTICLE_ALL_CAN_COMMENT","snapshotUrl":"","created":,"comments":0,"columnId":0,"content":"","parentId":0,"state":"ARTICLE_PUBLISHED","imageUrl":"/v2-a8dda9c9b668a6c807d5dbee5dd6f5b4_r.jpg","author":{"bio":"有情怀→渴望做出一款颠覆性App的代码工作者","isFollowing":false,"hash":"fd983d762fb8","uid":528000,"isOrg":false,"slug":"amin706","isFollowed":false,"description":"
奇点日报创始人,追求产品交互和设计的产品经理一枚\n 高逼格程序员技术分享平台 → \n『心动屋』→ 发现令您心动的好物\n『壹日程』→ 专注任务管理和待办计划提醒
\n『拾光记』→ 拾起你走过的时光\n『口袋密码』→ 安全简洁的账号管家\n『破壳日』→ 精美的生日 · 节日 · 纪念日礼物提醒工具\n 邮箱 → ","name":"Larry","profileUrl":"/people/amin706","avatar":{"id":"v2-a5db75ec863beb","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"memberId":,"excerptTitle":"","voteType":"ARTICLE_VOTE_CLEAR"},"id":694987}],"title":"大前端开发者需要了解的基础编译原理和语言知识","author":"amin706","content":"作者:bestswifter原文链接: 在我刚刚进入大学,从零开始学习 C 语言的时候,我就不断的从学长的口中听到一个又一个语言,比如 C++、Java、Python、JavaScript 这些大众的,也有 Lisp、Perl、Ruby 这些相对小众的。一般来说,当程序员讨论一门语言的时候,默认的上下文经常是:“用 xxx 语言来完成 xxx 任务”。所以一直困扰着的我的一个问题就是,为什么完成某个任务,一定要选择特定的语言,比如安卓开发是 Java,前端要用 JavaScript,iOS 开发使用 Objective-C 或者 Swift。这些问题的答案非常复杂,有的是技术原因,有的是历史原因,有的会考虑成本,很难得出统一的结论,只能 case-by-case 的分析。这篇文章并非专门解答上述问题,而是希望通过介绍一些通用的概念,帮助读者掌握分析问题的能力,如果这个概念在实际编程中用得到,我也会举一些具体的例子。在阅读本文前,不妨思考一下这几个问题,如果没有头绪,建议看完文章以后再思考一遍。如果觉得答案显而易见,恭喜你,这篇文章并非为你准备的:什么是编译器,它以什么为分界线,分为前端和后端?\nJava 是编译型语言还是解释型语言,Python 呢?\nC 语言的编译器也是 C 语言,那它怎么被编译的?\n目标文件的格式是什么样的,段表、符号表、重定位表有什么作用?\nSwift 是静态语言,为什么还有运行时库?\n什么是 ABI,ABI 不稳定有什么问题?\n什么是 WebAssembly,为什么要推出这门技术,用 C++ 代替 JavaScript 可行么?\nJavaScript 和 DOM API 是什么关系,JavaScript 可以读写文件么?\nC++ 代码可以自动转换成 Java 代码么,任意两种语言是否可以互转?\n为什么说 Python 是胶水语言,它可以用来开发 iOS/Android 么?\n编译原理就像数学是一个公理体系,从简单的公理就能推导出各种高阶公式一样,我们从最基本的 C 语言和编译说起。int main(void) {\n
int a = strlen(\"Hello world\");
// 字符串的长度是 11\n
return 0;\n}\n相关的介绍编译过程的文章很多,读者应该都非常熟悉了,整个流程包括预处理、词法分析、语法分析、生成中间代码,生成目标代码,汇编,链接 等。已有的文章大多分析了每一步的逻辑,但很少谈实现思路,我会尽量用简单的语言来描述每一步的实现思路,相信这样有助于加深记忆。由于主要谈的概念和思路,难免会有一些不够准确的抽象,读者学会抓重点就行。预处理是一个独立的模块,它放在最后介绍,我们先看词法分析。词法分析最先登场的是编译器,它负责前五个步骤,也就是说编译器的输入是源代码,输出是中间代码。编译器不能像人一样,一眼就看明白源代码的内容,它只能比较傻的逐个单词分析。词法分析要做的就是把源代码分割开,形成若干个单词。这个过程并不像想象的那么简单。比如举几个例子:int t 表示一个整数,而 intt 只是一个变量名。\nint a() 表示一个函数而非整数 a,int a () 也是一个函数。\na = 没有具体价值,它可以是一个赋值语句,还可以是 a == 1 的前缀,表示一个判断。\n词法分析的主要难点在于,前缀无法决定一个完整字符串的含义,通常需要看完整句以后才知道每个单词的具体含义。同时,C 语言的语法也不简单,各种关键字,括号,逗号,语法等等都会给词法分析的实现增加难度。词法分析的主要实现原理是状态机,它逐个读取字符,然后根据读到的字符的特点转换状态。比如这是 GCC 的词法分析状态机(引用自《编译系统透视》):\n如果自己实现的话,思路也不难。外面包一个循环,然后各种 switch...case 就完事了。词法分析应该算是最简单的一节。语法分析经过词法分析以后,编译器已经知道了每个单词,但这些单词组合起来表示的语法还不清楚。一个简单的思路是模板匹配,比如有这样的语句:\nint a = 10;\n它其实表示了这么一种通用的语法格式:类型 变量名 = 常量;所以 int a = 10; 当然可以匹配上这种模式。同理,它不可能匹配 类型 函数名(参数); 这种函数定义模式,因为两者结构不一致,等号无法被匹配。语法分析比词法分析更复杂,因为所有 C 语言支持的语法特性都必须被语法分析器正确的匹配,这个难度比纯新手学习 C 语言语法难上很多倍。不过这个属于业务复杂性,无论采用哪种解决方案都不可避免,因为语法规则的数量就是这么多。在匹配模式的时候,另一个问题在于上述的名词,比如 类型、参数,很难界定。比如int 是类型,long long 也是类型,unsigned long long 也是类型。(int a) 可以是参数,(int a, int b) 也是参数,(unsigned long long a, long long double b, int *p) 看起来能把人逼疯。下面举一个简单的例子来解释 int a = 10 是如何被解析的,总的思路是归纳与分解。我们把一个复杂的式子分割成若干部分,然后分析各个部分,这样可以简化复杂度。对于 int a = 10 来说,他是一个声明,声明由两部分组成,分别是声明说明符和初始声明符列表。声明说明符比较简单,它其实是若干个类型的串联:声明说明符 = 类型 + 类型的数组(长度可以为 0)而且我们知道若干个类型连在一起又变成了声明说明符,所以上述等式等价于:声明说明符 = 类型 + 声明说明符(可选)再严谨一些,声明说明符还可以包括 const 这样的限定说明符,inline 这样的函数说明符,和 _Alignas 这样的对齐说明符。借用书中的公式,它的完整表达如下:\n这才仅仅是声明语句中最简单的声明说明符,仅仅是几个类型和关键字的组合而已。后面的初始声明符列表的解析更复杂。如果有能力做完这些解析,恭喜你,成功的解析了声明语句。你会发现什么定义语句啦,调用语句啦,正妩媚的向你招手╮(╯▽╰)╭。成功解析语法以后,我们会得到抽象语法树(AST: Abstract Syntax Tree)。以这段代码为例:\nint fun(int a, int b) {\n
int c = 0;\n
c = a +\\n}\n它的语法树如下:\n语法树将字符串格式的源代码转化为树状的数据结构,更容易被计算机理解和处理。但它距离中间代码还有一定的距离。生成中间代码以 GCC 为例,生成中间代码可以分为三个步骤:语法树转高端 gimple\n高端 gimple 转低端 gimple\n低端 gimple 经过 cfa 转 ssa 再转中间代码\n简单的介绍一下每一步都做了什么。语法树转高端 gimple这一步主要是处理寄存器和栈,比如 c = a + b 并没有直接的汇编代码和它对应,一般来说需要把 a + b 的结果保存到寄存器中,然后再把寄存器赋值给 c。所以这一步如果用 C 语言来表示其实是:int temp = a + // temp 其实是寄存器\nc =\n另外,调用一个新的函数时会进入到函数自己的栈,建栈的操作也需要在 gimple 中声明。高端 gimple 转低端 gimple这一步主要是把变量定义,语句执行和返回语句区分存储。比如:\nint a = 1;\na++;\nint b = 1;\n会被处理成:\nint a = 1;\nint b = 1;\na++;\n这样做的好处是很容易计算一个函数到底需要多少栈空间。此外,return 语句会被统一处理,放在函数的末尾,比如:if (1 & 0) {\n
return 1;\n}\nelse {\n
return 0;\n}\n会被处理成:\nif (1 & 0) {\\n}\nelse {\\n}\na:\n
return 1;\nb:\n
return 0;\n低端 gimple 经过 cfa 转 ssa 再转中间代码这一步主要是进行各种优化,添加版本号等,我不太了解,对于普通开发者来说也没有学习的必要。中间代码的意义其实中间代码可以被省略,抽象语法树可以直接转化为目标代码(汇编代码)。然而,不同的 CPU 的汇编语法并不一致,比如 AT&T与Intel汇编风格比较 这篇文章所提到的,Intel 架构和 AT&T 架构的汇编码中,源操作数和目标操作数位置恰好相反。Intel 架构下操作数和立即数没有前缀但 AT&T 有。因此一种比较高效的做法是先生成语言无关,CPU 也无关的中间代码,然后再生成对应各个 CPU 的汇编代码。生成中间代码是非常重要的一步,一方面它和语言无关,也和 CPU 与具体实现无关。可以理解为中间代码是一种非常抽象,又非常普适的代码。它客观中立的描述了代码要做的事情,如果用中文、英文来分别表示 C 和 Java 的话,中间码某种意义上可以被理解为世界语。另一方面,中间代码是编译器前端和后端的分界线。编译器前端负责把源码转换成中间代码,编译器后端负责把中间代码转换成汇编代码。LLVM IR 是一种中间代码,它长成这样:\ndefine i32 @square_unsigned(i32 %a) {\n
%1 = mul i32 %a, %a\n
ret i32 %1\n}\n生成目标代码目标代码也可以叫做汇编代码。由于中间代码已经非常接近于实际的汇编代码,它几乎可以直接被转化。主要的工作量在于兼容各种 CPU 以及填写模板。在最终生成的汇编代码中,不仅有汇编命令,也有一些对文件的说明。比如:\n
\"test.c\"
# 文件名称\n
# 全局变量 m\n
# 数据段声明\n
# 4 字节对齐\n
m, @objc\n
m, 4\nm:\n
# m 的值是 10\n
main, @function\nmain:\n
...\n汇编汇编器会接收汇编代码,将它转换成二进制的机器码,生成目标文件(后缀是 .o),机器码可以直接被 CPU 识别并执行。从目标代码可以猜出来,最终的目标文件(机器码)也是分段的,这主要有以下三个原因:分段可以将数据和代码区分开。其中代码只读,数据可写,方便权限管理,避免指令被改写,提高安全性。\n现代 CPU 一般有自己的数据缓存和指令缓存,区分存储有助于提高缓存命中率。\n当多个进程同时运行时,他们的指令可以被共享,这样能节省内存。\n段分离我们并不遥远,比如命令行中的 objcopy 可以自行添加自定义的段名,C 语言的 __attribute((section(段名)))__ 可以把变量定义在某个特定名称的段中。对于一个目标文件来说,文件的最开头(也叫作 ELF 头)记录了目标文件的基本信息,程序入口地址,以及段表的位置,相当于是对文件的整体描述。接下来的重点是段表,它记录了每个段的段名,长度,偏移量。比较常用的段有:.strtab 段: 字符串长度不定,分开存放浪费空间(因为需要内存对齐),因此可以统一放到字符串表(也就是 .strtab 段)中进行管理。字符串之间用\\0 分割,所以凡是引用字符串的地方用一个数字就可以代表。\n.symtab: 表示符号表。符号表统一管理所有符号,比如变量名,函数名。符号表可以理解为一个表格,每行都有符号名(数字)、符号类型和符号值(存储地址)\n.rel 段: 它表示一系列重定位表。这个表主要在链接时用到,下面会详细解释。\n链接在一个目标文件中,不可能所有变量和函数都定义在文件内部。比如 strlen 函数就是一个被调用的外部函数,此时就需要把 main.o 这个目标文件和包含了 strlen函数实现的目标文件链接起来。我们知道函数调用对应到汇编其实是 jump 指令,后面写上被调用函数的地址,但在生成 main.o 的过程中,strlen() 函数的地址并不知道,所以只能先用 0 来代替,直到最后链接时,才会修改成真实的地址。链接器就是靠着重定位表来知道哪些地方需要被重定位的。每个可能存在重定位的段都会有对应的重定位表。在链接阶段,链接器会根据重定位表中,需要重定位的内容,去别的目标文件中找到地址并进行重定位。有时候我们还会听到动态链接这个名词,它表示重定位发生在运行时而非编译后。动态链接可以节省内存,但也会带来加载的性能问题,这里不详细解释,感兴趣的读者可以阅读《程序员的自我修养》这本书。预处理最后简单描述一下预处理。预处理主要是处理一些宏定义,比如#define、#include、#if 等。预处理的实现有很多种,有的编译器会在词法分析前先进行预处理,替换掉所有 # 开头的宏,而有的编译器则是在词法分析的过程中进行预处理。当分析到 # 开头的单词时才进行替换。虽然先预处理再词法分析比较符合直觉,但在实际使用中,GCC 使用的却是一边词法分析,一边预处理的方案。编译 VS 解释总结一下,对于 C 语言来说,从源码到运行结果大致上需要经历编译、汇编和链接三个步骤。编译器接收源代码,输出目标代码(也就是汇编代码),汇编器接收汇编代码,输出由机器码组成的目标文件(二进制格式,.o 后缀),最后链接器将各个目标文件链接起来,执行重定位,最终生成可执行文件。编译器以中间代码为界限,又可以分前端和后端。比如 clang 就是一个前端工具,而 LLVM 则负责后端处理。另一个知名工具 GCC(GNU Compile Collection)则是一个套装,包揽了前后端的所有任务。前端主要负责预处理、词法分析、语法分析,最终生成语言无关的中间代码。后端主要负责目标代码的生成和优化。关于编译原理的基础知识虽然枯燥,但掌握这些知识有助于我们理解一些有用的,但不太容易理解的概念。接下来,我们简单看一下别的语言是如何运行的。Java在 Java 代码的执行过程中,可以简单分为编译和执行两步。Java 的编译器首先会把 .java 格式的源码编译成 .class 格式的字节码。字节码对应到 C 语言的编译体系中就是中间码,Java 虚拟机执行这些中间码得到最终结果。回忆一下上文对中间码的解释,一方面它与语言无关,仅仅描述客观事实。另一方面它和目标代码的差距并不大,已经包括了对寄存器和栈的处理,仅仅是抽象了 CPU 架构而已,只要把它具体化成各个平台下的目标代码,就可以交给汇编器了。解释型语言一般来说我们也把解释型语言叫做脚本语言,比如 Python、Ruby、JavaScript 等等。这类语言的特点是,不需要编译,直接由解释器执行。换言之,运行流程变成了:源代码 -& 解释器 -& 运行结果需要注意的是,这里的解释器只是一个黑盒,它的实现方式可以是多种多样的。举个例子,它的实现可以非常类似于 Java 的执行过程。解释器里面可以包含一个编译器和虚拟机,编译器把源码转化成 AST 或者字节码(中间代码)然后交给虚拟机执行,比如 Ruby 1.9 以后版本的官方实现就是这个思路。至于虚拟机,它并不是什么黑科技,它的内部可以编译执行,也可以解释执行。如果是编译执行,那么它会把字节码编译成当前 CPU 下的机器码然后统一执行。如果是解释执行,它会逐条翻译字节码。有意思的是,如果虚拟机是编译执行的,那么这套流程和 C 语言几乎一样,都满足下面这个流程:源代码 -& 中间代码 -& 目标代码 -& 运行结果下面是重点!!!下面是重点!!!下面是重点!!!因此,解释型语言和编译型语言的根本区别在于,对于用户来说,到底是直接从源码开始执行,还是从中间代码开始执行。以 C 语言为例,所有的可执行程序都是二进制文件。而对于传统意义的 Python 或者 JavaScript,用户并没有拿到中间代码,他们直接从源码开始执行。从这个角度来看, Java 不可能是解释型语言,虽然 Java 虚拟机会解释字节码,但是对于用户来说,他们是从编译好的 .class 文件开始执行,而非源代码。实际上,在 x86 这种复杂架构下,二进制的机器码也不能被硬件直接执行,CPU 会把它翻译成更底层的指令。从这个角度来说,我们眼中的硬件其实也是一个虚拟机,执行了一些“抽象”指令,但我相信不会有人认为 C 语言是解释型语言。因此,有没有虚拟机,虚拟机是不是解释执行,会不会生成中间代码,这些都不重要,重要的是如果从中间代码开始执行,而且 AST 已经事先生成好,那就是编译型的语言。如果更本质一点看问题,根本就不存在解释型语言或者编译型语言这种说法。已经有人证明,如果一门语言是可以解释的,必然可以开发出这门语言的编译器。反过来说,如果一门语言是可编译的,我只要把它的编译器放到解释器里,把编译推迟到运行时,这么语言就可以是解释型的。事实上,早有人开发出了 C 语言的解释器:C 源代码 -& C 语言解释器(运行时编译、汇编、链接) -& 运行结果我相信这一点很容易理解,规范和实现是两套分离的体系。我们平常说的 C 语言的语法,实际上是一套规范。理论上来说每个人都可以写出自己的编译器来实现 C 语言,只要你的编译器能够正确运行,最终的输出结果正确即可。而编译型和解释型说的其实是语言的实现方案,是提前编译以获得最大的性能提高,还是运行时去解析以获得灵活性,往往取决于语言的应用场景。所以说一门语言是编译型还是解释型的,这会非常可笑。一个标准怎么可能会有固定的实现呢?之所以给大家留下了 C 语言是编译型语言,Python 是解释型语言的印象,往往是因为这门语言的应用场景决定了它是主流实现是编译型还是解释型。自举不知道有没有人思考过,C 语言的编译器是如何实现的?实际上它还是用 C 语言实现的。这种自己能编译自己的神奇能力被称为自举(Bootstrap)。乍一看,自举是不可能的。因为 C 语言编译器,比如 GCC,要想运行起来,必定需要 GCC 的编译器将它编译成二进制的机器码。然而 GCC 的编译器又如何编译呢……解决问题的关键在于打破这个循环,我们可以先用一个比 C 语言低级的语言来实现一个 C 语言编译器。这件事是可能做到的,因为这个低级语言必然会比 C 语言简单,比如我们可以直接用汇编代码来写 C 语言的编译器。由于越低级的语言越简单,但表达能力越弱,所以用汇编来写可能太复杂。这种情况下我们可以先用一个比 C 语言低级但比汇编高级的语言来实现 C 语言的编译器,同时用汇编来实现这门语言的编译器。总之就是不断用低级语言来写高级语言的编译器,虽然语言越低级,它的表达能力越弱,但是它要解析的语言也在不断变简单,所以这件事是可以做到的。有了低级语言写好的 C 语言编译器以后,这个编译器是二进制格式的。此时就可以删掉所有的低级语言,只留一个二进制格式的 C 语言编译器,接下来我们就可以用 C 语言写编译器,再用这个二进制格式的编译器去编译 C 语言实现的 C 语言编译器了,于是完成了自举。以上逻辑描述起来比较绕,但我想多读几遍应该可以理解。如果实在不理解也没关系,我们只要明白 C 语言可以自举是因为它可以编译成二进制机器码,只要用低级语言生成这个机器码,就不再需要低级语言了,因为机器码可以直接被 CPU 执行。从这个角度来看,解释型语言是不可能自举的。以 Python 为例,自举要求它能用 Python 语言写出来 Python 的解释器,然而这个解释器如何运行呢,最终还是需要一个解释器。而解释器体系下, Python 都是从源码经过解释器执行,又不能留下什么可以直接被硬件执行的二进制形式的解释器文件,自然是没办法自举的。然而,就像前面说的,Python 完全可以实现一个编译器,这种情况下它就是可以自举的。所以一门语言能不能自举,主要取决于它的实现形式能否被编译并留下二进制格式的可执行文件。运行时本文的读者如果是使用 Objective-C 的 iOS 开发者,想必都有过在面试时被 runtime 支配的恐惧。然而,runtime 并非是 Objective-C 的专利,绝大多数语言都有这个概念。所以有人说 Objective-C 具有动态性是因为它有 runtime,这种说法并不准确,我觉得要把 Objective-C 的 runtime 和一般意义的运行时库区分开,认识到它仅仅是运行时库的一个组成部分,同时还是要深入到方法调用的层面来谈。运行时库的基本概念以 C 语言为例,有非常多的操作最终都依赖于 glibc 这个动态链接库。包括但不限于字符串处理(strlen、strcpy)、信号处理、socket、线程、IO、动态内存分屏(malloc)等等。这一点很好理解,如果回忆一下之前编译器的工作原理,我们会发现它仅仅是处理了语言的语法,比如变量定义,函数声明和调用等等。至于语言的功能, 比如内存管理,內建的类型,一些必要功能的实现等等。如果要对运行时库进行分类,大概有两类。一种是语言自身功能的实现,比如一些內建类型,内置的函数;另一种则是语言无关的基础功能,比如文件 IO,socket 等等。由于每个程序都依赖于运行时库,这些库一般都是动态链接的,比如 C 语言的 (g)libc。这样一来,运行时库可以存储在操作系统中,节省内存占用空间和应用程序大小。对于 Java 语言来说,它的垃圾回收功能,文件 IO 等都是在虚拟机中实现,并提供给 Java 层调用。从这个角度来看,虚拟机/解释器也可以被看做语言的运行时环境(库)。swift 运行时库经过这样的解释,相信 swift 的运行时库就很容易理解了。一方面,swift 是绝对的静态语言,另一方面,swift 毫无疑问的带有自己的运行时库。举个最简单的例子,如果阅读 swift 源码就会发现某些类型,比如字符串(String),或者数组,再或者某些函数(print)都是用 swift 实现的,这些都是 swift 运行时库的一部分。按理说,运行时库应该内置于操作系统中并且和应用程序动态链接,然而坑爹的 Swift 在本文写作之时依然没有稳定 ABI,导致每个程序都必须自带运行时库,这也就是为什么目前 swift 开发的 app 普遍会增加几 Mb 包大小的原因。说到 ABI,它其实就是一个编译后的 API。简单来说,API 是描述了在应用程序级别,模块之间的调用约定。比如某个模块想要调用另一个模块的功能,就必须根据被调用模块提供的 API 来调用,因为 API 中规定了方法名、参数和返回结果的类型。而当源码被编译成二进制文件后,它们之间的调用也存在一些规则和约定。比如模块 A 有两个整数 a 和 b,它们的内存布局如下:\n这时候别的模块调用 A 模块的 b 变量,可以通过初始地址加偏移量的方式进行。如果后来模块 A 新增了一个整数 c,它的内存布局可能会变成:如果调用方还是使用相同的偏移量,可以想见,这次拿到的就是变量 a 了。因此,每当模块 A 有更新,所有依赖于模块 A 的模块都必须重新编译才能正确工作。如果这里的模块 A 是 swift 的运行时库,它内置于操作系统并与其他模块(应用程序)动态链接会怎么样呢?结果就是每次更新系统后,所有的 app 都无法打开。显然这是无法接受的。当然,ABI 稳定还包括其他的一些要求,比如调用和被调用者遵守相同的调用约定(参数和返回值如何传递)等。JavaScript 那些事我们继续刚才有关运行时的话题,先从 JavaScript 的运行时聊起,再介绍 JavaScript 的相关知识。JavaScript 是如何运行的JavaScript 和其他语言,无论是 C 语言,还是 Python 这样的脚本语言,最大的区别在于 JavaScript 的宿主环境比较奇怪,一般来说是浏览器。无论是 C 还是 Python,他们都有一个编译器/解释器运行在操作系统上,直接把源码转换成机器码。而 JavaScript 的解释器一般内置在浏览器中,比如 Chrome 就有一个 V8 引擎可以解析并执行 JavaScript 代码。因此 JavaScript 的能力实际上会受到宿主环境的影响,有一些限制和加强。首先来看看 DOM 操作,相关的 API 并没有定义在 ECMAScript 标准中,因此我们常用的
并非是 JavaScript 自带的功能,这通常是由宿主平台通过 C/C++ 等语言实现,然后提供给 JavaScript 的接口。同样的,由于浏览器中的 JavaScript 只是一个轻量的语言,没有必要读写操作系统的文件,因此浏览器引擎一般不会向 JavaScript 提供文件读写的运行时组件,它也就不具备 IO 的能力。从这个角度来看,整个浏览器都可以看做 JavaScript 的虚拟机或者运行时环境。因此,当我们换一个宿主环境,比如 Node.js,JavaScript 的能力就会发生变化。它不再具有 DOM API,但多了读写文件等能力。这时候,Node.js 就更像是一个标准的 JavaScript 解析器了。这也是为什么 Node.js 让 JavaScript 可以编写后端应用的原因。JIT 优化解释执行效率低的主要原因之一在于,相同的语句被反复解释,因此优化的思路是动态的观察哪些代码是经常被调用的。对于那些被高频率调用的代码,可以用编译器把它编译成机器码并且缓存下来,下次执行的时候就不用重新解释,从而提升速度。这就是 JIT(Just-In-Time) 的技术原理。但凡基于缓存的优化,一定会涉及到缓存命中率的问题。在 JavaScript 中,即使是同一段代码,在不同上下文中生成的机器码也不一定相同。比如这个函数:\nfunction add(a, b) {\n
return a +\n}\n如果这里的 a 和 b 都是整数,可以想见最终的代码一定是汇编中的 add 命令。如果类似的加法运算调用了很多次,解释器可能会认为它值得被优化,于是编译了这段代码。但如果下一次调用的是 add(\"hello\", \"world\"),之前的优化就无效了,因为字符串加法的实现和整数加法的实现完全不同。于是优化后的代码(二进制格式)还得被还原成原先的形式(字符串格式),这样的过程被称为去优化。反复的优化 -& 去优化 -& 优化 …… 非常耗时,大大降低了引入 JIT 带来的性能提升。JIT 理论上给传统的 JavaScript 带了了 20-40 倍的性能提升,但由于上述去优化的存在,在实际运行的过程中远远达不到这个理论上的性能天花板。WebAssembly前文说过,JavaScript 实际上是由浏览器引擎负责解析并提供一些功能的。浏览器引擎可能是由 C++ 这样高效的语言实现的,那么为什么不用 C++ 来写网页呢?实际上我认为从技术角度来说并不存在问题,直接下发 C++ 代码,然后交给 C++ 解释器去执行,再调用浏览器的 C++ 组件,似乎更加符合直觉一些。之所以选择 JavaScript 而不是 C++,除了主流浏览器目前都只支持 JavaScript 而不支持 C++ 这个历史原因以外,更重要的一点是一门语言的高性能和简单性不可兼得。JavaScript 在运行速度方面做出了牺牲,但也具备了简单易开发的优点。作为通用编程语言,JavaScript 和 C++ 主要的性能差距就在于缺少类型标注,导致无法进行有效的提前编译。之前说过 JIT 这种基于缓存去猜测类型的方式存在瓶颈,那么最精确的方式肯定还是直接加上类型标注,这样就可以直接编译了,代表性的作品有 Mozilla 的Asm.js。Asm.js 是 JavaScript 的一个子集,任何 JavaScript 解释器都可以解释它:\nfunction add(a, b) {\n
// 任何整数和自己做按位或运算的结果都是自己\n
// 所以这个标记不改变运算结果,但是可以提示编译器 a、b 都是整数\n
return a + b | 0\n}\n如果有 Asm.js 特定的解释器,完全可以把它提前编译出来。即使没有也没关系,因为它完全是 JavaScript 语法的子集,普通的解释器也可以解释。然而,回顾一下我们最初对解释器的定义: 解释器是一个黑盒,输入源码,输出运行结果。Asm.js 其实是黑盒内部的一个优化,不同的黑盒(浏览器)无法共享这一优化。换句话说 Asm.js 写成的代码放到 Chrome 上面和普通的 JavaScript 毫无区别。于是,包括微软、谷歌和苹果在内的各大公司觉得,是时候搞个标准了,这个标准就是 WebAssembly 格式。它是介于中间代码和目标代码之间的一种二进制格式,借用WebAssembly 系列(四)WebAssembly 工作原理 一文的插图来表示:通常从中间代码到机器码,需要经过平台具体化(转目标代码)和二进制化(汇编器把汇编代码变为二进制机器码)这两个步骤。而 WebAssembly 首先完成了第二个步骤,即已经是二进制格式的,但只是一系列虚拟的通用指令,还需要转换到各个 CPU 架构上。这样一来,从 WebAssembly 到机器码其实是透明且统一的,各个浏览器厂商只需要考虑如何从中间代码转换 WebAssembly 就行了。由于编译器的前端工具 Clang 可以把 C/C++ 转换成中间代码,因此理论上它们都可以用来开发网页。然而谁会这么这么做呢,放着简单快捷,现在又高效的 JavaScript 不写,非要去啃 C++?跨语言那些事儿C++ 写网页这个脑洞虽然比较大,但它启发我思考一个问题:“对于一个常见的可以由某个语言完成的任务(比如 JavaScript 写网页),能不能换一个语言来实现(比如 C++),如果不能,制约因素在哪里”。由于绝大多数主流语言都是,也就是说一切可计算的问题,在这些语言层面都是等价的,都可以计算。那么制约语言能力的因素也就只剩下了运行时的环境是否提供了相应的功能。比如前文解释过的,虽然浏览器中的 JavaScript 不能读写文件,不能实现一个服务器,但这是浏览器(即运行时环境)不行,不是 JavaScript 不行,只要把运行环境换成 Node.js 就行了。直接语法转换大部分读者应该接触过简单的逆向工程。比如编译后的 .o 目标文件和 .class 字节码都可以反编译成源代码,这种从中间代码倒推回源代码的技术也被叫做反编译(decompile),反编译器的工作流程基本上是编译器的倒序,只不过完美的反编译一般来说比较困难,这取决于中间代码的实现。像 Java 字节码这样的中间代码,由于信息比较全,所以反编译就相对容易、准确一些。C 代码在生成中间代码时丢失了很多信息,因此就几乎不可能 100% 准确的倒推回去,感兴趣的读者可以参考一下知名的反编译工具Hex-Rays 的一篇博客。前文说过,编译器前端可以对多种语言进行词法分析和语法分析,并且生成一套语言无关的中间代码,因此理论上来说,如果某个编译器前端工具支持两个语言 A 和 B 的解析,那么 A 和 B 是可以互相转换的,流程如下:A 源码 &--& 语言无关的中间代码 &--& B 源码其中从源码转换到中间代码需要使用编译器,从中间代码转换到源码则使用反编译器。但在实际情况中,事情会略复杂一些,这是因为中间代码虽然是一套语言无关、CPU 也无关的指令集,但不代表不同语言生成的中间代码就可以通用。比如中间代码共有 1、2、3、……、6 这六个指令。A 语言生成的中间代码仅仅是所有指令的一个子集,比如是 1-5 这 5 个指令;B 语言生成的中间代码可能是所有指令的另一个子集,比如 2-6。这时候我们说的 B 语言的反编译器,实际上是从 2-6 的指令子集推导出 B 语言源码,它对指令 1 可能无能为力。以 GCC 的中间代码 RTL: Register Transfer Language 为例,官方文档 在对 RTL 的解释中,就明确的把 RTL 树分为了通用的、C/C++ 特有的、Java 特有的等几个部分。\n具体来说,我们知道 Java 并不能直接访问内存地址,这一点和浏览器上的 JavaScript 不能读写文件很类似,都是因为它们的运行环境(虚拟机)具备这种能力,但没有在语言层面提供。因此,含有指针四则运算的 C 代码无法直接被转换成 Java 代码,因为 Java 字节码层面并没有定义这样的抽象,一种简单的方案是申请一个超大的数组,然后自己模拟内存地址。所以,即使编译器前端同时支持两种语言的解析,要想进行转换,还必须处理两种语言在中间代码层面的一些小差异,实际流程应该是:A 源码 &--& 中间代码子集(A) &--适配器--& 中间代码子集(B) &--& B 源码这个思路已经不仅仅停留在理论上了,比如 Github 上有一个库: emscripten 就实现了将任何 Clang 支持的语言(比如 C/C++ 等)转换成 JavaScript,再比如 lljvm实现了 C 到 Java 字节码的转换。然而前文已经解释过,实现单纯语法的转换意义并不大。一方面,对于图灵完备的语言来说,换一种表示方法(语言)去解决相同的问题并没有意义。另一方面,语言的真正功能绝不仅仅是语法本身,而在于它的运行时环境提供了什么样的功能。比如 Objective-C 的 Foundation 库提供了字典类型 NSDictionary,它如果直接转换成 C 语言,将是一个找不到的符号。因为 C 语言的运行时环境根本就不提供对这种数据结构的支持。因此凡是在语言层面进行强制转换的,要么利用反编译器拿到一堆格式正确但无法运行的代码,要么就自行解析语法树并为转换后的语言添加对应的能力,来实现转换前语言的功能。\n比如图中就是一个 C 语言转换 Java 的工具,为了实现 C 语言中的字符串申请和释放内存,这个工具不得不自己实现了 com.mtsystems.coot.String8 类。这样巨大的成本,显然不够普适,应用场景相对有限。总之,直接的语法转换是一个美好的想法,但实现起来难度大,收益有限,通常是为了移植已经用某个语言写好的框架,或者开个脑洞用于学习,但实际应用场景并不多。胶水语言 PythonPython 一个很强大的特点是胶水语言,可以把 Python 理解为各种语言的粘合剂。对于 Python 可以处理的逻辑,用 Python 代码即可完成。如果追求极致的性能或者调用已经实现的功能,也可以让 Python 调用已经由别的语言实现的模块,以 Python 和 C 语言的交互解释一下。首先,如果是 C 语言要执行 Python 代码,显然需要一个 Python 的解释器。由于在 Mac OS X 系统上,Python 解释器是一个动态链接库,所以只要导入一下头文件即可,下面这段代码可以成功输出 “Hello Python!!!”:\n#include &stdio.h&\n#import &Python/Python.h&\n\nint main(int argc, const char * argv[]) {\n
Py_SetProgramName(argv[0]);\n
Py_Initialize();\n
PyRun_SimpleString(\"print 'Hello Python!!!'\\n\");\n
Py_Finalize();\n
return 0;\n}\n如果是在 iOS 应用里,由于 iOS 系统没有对应的动态库,所以需要把 Python 的解释器打包成一个静态库并且链接到应用中,网上已经有人做好了: python-for-iphone,这就是为什么我们看到一些教育类的应用模拟了 Python 解释器,允许用户编写 Python 代码并得到输出。Python 调用 Objective-C/C 也不复杂,只需要在 C 代码中指定要暴露的模块 A 和要暴露的方法 a,然后 Python 就可以直接调用了:import A\nA.a()\n详细的教程可以看这里: 如何实现 C/C++ 与 Python 的通信?有时候,如果能把自己熟悉的语言应用到一个陌生的领域,无疑会大大降低上手的难度。以 iOS 开发为例,开发者的日常其实是利用 Objective-C 语法来描述一些逻辑,最终利用 UIKit 等框架完成和应用的交互。 一种很自然而然的想法是,能不能用 Python 来实现逻辑,并且调用 Objective-C 的接口,比如 UIKit、Foundation 等。实际上前者是完全可以实现的,但是 Python 调用 Objective-C 远比调用 C 语言要复杂得多。一方面从之前的分析中也能看出,并不是所有的源码编译成目标文件都可以被 Python 引用;另一方面,最重要的是 Objective-C 方法调用的特性。我们知道方法调用实际上会被编译成 msg_Send 并交给 runtime 处理,最终找到函数指针并调用。这里 Objective-C 的 runtime 其实是一个用 C 语言实现动态链接库,它可以理解为 Objective-C 运行时环境的一部分。换句话说,没有 runtime 这个库,包含方法调用的 Objective-C 代码是不可能运行起来的,因为 msg_Send 这个符号无法被重定向,运行时将找不到 msg_Send 函数的地址。就连原生的 Objective-C 代码都需要依赖运行时,想让 Python 直接调用某个 Objective-C 编译出来的库就更不可能了。想用 Python 写开发 iOS 应用是有可能的,比如: PyObjc,但最终还是要依赖 Runtime。大概的思路是首先用 Python 拿到 runtime 这个库,然后通过这个库去和 runtime 交互,进而具备了调用 Objective-C 和各种框架的能力。比如我要实现 Python 中的 UIView 这个类,代码会变成这样:import objc\n\n# 这个 objc 是动态加载 libobjc.dylib 得到的\n# Python 会对 objc 做一些封装,提供调用 runtime 的能力\n# 实际的工作还是交给 libobjc.dylib 完成\n\nclass UIView:\n
def __init__(self, param):\n
objc.msgSend(\"UIView\", \"init\", param)\n这么做的性价比并不高,如果和 JSPatch 相比,JSPatch 使用了内置的 JavaScriptCore 作为 JavaScript 的解析器,而 PyObjc 就得自己带一个 libPython.a 解释器。此外,由于 iOS 系统的沙盒限制,非越狱机器并不能拿到 libobjc 库,所以这个工具只能在越狱手机上使用。OCS既然说到了 JSPatch 这一类动态化的 iOS 开发工具,我就斗胆猜测一下腾讯 OCS 的实现原理,目前介绍 OCS 的文章寥寥无几,由于苹果公司的要求,原文已经被删除,从新浪博客上摘录了一份: OCS ——史上最疯狂的 iOS 动态化方案。如果用一句话来概述,那么就是 OCS 是一个 Objective-C 解释器。首先,OCS 基于 clang 对下发的 Objective-C 代码做词法、语法分析,生成 AST 然后转化成自定义的一套中间码(OSScript)。当然,原生的 Objective-C 可以运行,绝不仅仅是编译器的功劳。就像之前反复强调的那样,运行时环境也必不可少,比如负责 GCD 的 libdispatch 库,还有内存管理,多线程等等功能。这些功能原来都由系统的动态库实现,但现在必须由解释器实现,所以 OCS 的做法是开发了一套自己的虚拟机去解释执行中间码。这个运行原理就和 JVM 非常类似了。当然,最终还是要和 Objective-C 的 Runtime 打交道,这样才能调用 UIKit 等框架。由于对虚拟机的实现原理并不清楚,这里就不敢多讲了,希望在学习完 JVM 以后再做分享。参考资料AT&T与Intel汇编风格比较\nglibc\nWebAssembly 系列(一)生动形象地介绍 WebAssembly\nDecompilers and beyond\npython-for-iphone\n如何实现 C/C++ 与 Python 的通信?\nWebAssembly 系列(四)WebAssembly 工作原理\n扯淡:大白话聊聊编译那点事儿\nrubicon-objc\nOCS ——史上最疯狂的 iOS 动态化方案\n虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩\nJavaScript的功能是不是都是靠C或者C++这种编译语言提供的?\n计算机编程语言必须能够自举吗?\n如何评论浏览器最新的 WebAssembly 字节码技术?\nObjective-C Runtime —— From Build To Did Launch\n10 GENERIC\n写个编译器,把C++代码编译到JVM的字节码可不可行?","updated":"T14:42:25.000Z","canComment":false,"commentPermission":"anyone","commentCount":5,"collapsedCount":0,"likeCount":155,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","titleImage":"/v2-a8dda9c9b668a6c807d5dbee5dd6f5b4_r.jpg","links":{"comments":"/api/posts//comments"},"reviewers":[],"topics":[{"url":"/topic/","id":"","name":"前端开发"},{"url":"/topic/","id":"","name":"编译原理"},{"url":"/topic/","id":"","name":"编程语言"}],"adminClosedComment":false,"titleImageSize":{"width":750,"height":340},"href":"/api/posts/","excerptTitle":"","tipjarState":"inactivated","annotationAction":[],"sourceUrl":"","pageCommentsCount":5,"hasPublishingDraft":false,"snapshotUrl":"","publishedTime":"T22:42:25+08:00","url":"/p/","lastestLikers":[{"bio":"工程师的自我修养","isFollowing":false,"hash":"c86edb85cedd9a123bcb45","uid":305800,"isOrg":false,"slug":"futureToday","isFollowed":false,"description":"","name":"futureToday","profileUrl":"/people/futureToday","avatar":{"id":"v2-1b59fa525f57ab66b325ac1b0d0781f2","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":null,"isFollowing":false,"hash":"5d6c0d8bee498dec6c27ed3fe583e5d2","uid":594940,"isOrg":false,"slug":"shuai-zhong-shuai","isFollowed":false,"description":"","name":"帅中帅","profileUrl":"/people/shuai-zhong-shuai","avatar":{"id":"1f6a456ead61","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":null,"isFollowing":false,"hash":"f4c602daac7c4a01288f90e","uid":849000,"isOrg":false,"slug":"zhi-ou-31","isFollowed":false,"description":"","name":"之欧","profileUrl":"/people/zhi-ou-31","avatar":{"id":"da8e974dc","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":null,"isFollowing":false,"hash":"ee5f8dc752f882ec4b04b8","uid":323100,"isOrg":false,"slug":"weiyan-hai","isFollowed":false,"description":"","name":"aniwei","profileUrl":"/people/weiyan-hai","avatar":{"id":"ce2bfb9dbad207f09a0c61","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"前国内计科学生……","isFollowing":false,"hash":"e448f967b47e9f98e05900","uid":484100,"isOrg":false,"slug":"leopard-67","isFollowed":false,"description":"","name":"Leopard","profileUrl":"/people/leopard-67","avatar":{"id":"6bbb4bae34d","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false}],"summary":"作者:bestswifter原文链接: 在我刚刚进入大学,从零开始学习 C 语言的时候,我就不断的从学长的口中听到一个又一个语言,比如 C++、Java、Python、JavaScript 这些大众的,也有 Lisp、Perl、Ruby 这些相对小…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"annotationDetail":null,"commentsCount":5,"likesCount":155,"FULLINFO":true}},"User":{"amin706":{"isFollowed":false,"name":"Larry","headline":"
奇点日报创始人,追求产品交互和设计的产品经理一枚\n 高逼格程序员技术分享平台 → \n『心动屋』→ 发现令您心动的好物\n『壹日程』→ 专注任务管理和待办计划提醒
\n『拾光记』→ 拾起你走过的时光\n『口袋密码』→ 安全简洁的账号管家\n『破壳日』→ 精美的生日 · 节日 · 纪念日礼物提醒工具\n 邮箱 → ","avatarUrl":"/v2-a5db75ec863beb_s.jpg","isFollowing":false,"type":"people","slug":"amin706","bio":"有情怀→渴望做出一款颠覆性App的代码工作者","hash":"fd983d762fb8","uid":528000,"isOrg":false,"description":"
奇点日报创始人,追求产品交互和设计的产品经理一枚\n 高逼格程序员技术分享平台 → \n『心动屋』→ 发现令您心动的好物\n『壹日程』→ 专注任务管理和待办计划提醒
\n『拾光记』→ 拾起你走过的时光\n『口袋密码』→ 安全简洁的账号管家\n『破壳日』→ 精美的生日 · 节日 · 纪念日礼物提醒工具\n 邮箱 → ","profileUrl":"/people/amin706","avatar":{"id":"v2-a5db75ec863beb","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false,"badge":{"identity":null,"bestAnswerer":null}}},"Comment":{},"favlists":{}},"me":{},"global":{},"columns":{"next":{}},"columnPosts":{},"columnSettings":{"colomnAuthor":[],"uploadAvatarDetails":"","contributeRequests":[],"contributeRequestsTotalCount":0,"inviteAuthor":""},"postComments":{},"postReviewComments":{"comments":[],"newComments":[],"hasMore":true},"favlistsByUser":{},"favlistRelations":{},"promotions":{},"switches":{"couldAddVideo":false},"draft":{"titleImage":"","titleImageSize":{},"isTitleImageFullScreen":false,"canTitleImageFullScreen":false,"title":"","titleImageUploading":false,"error":"","content":"","draftLoading":false,"globalLoading":false,"pendingVideo":{"resource":null,"error":null}},"drafts":{"draftsList":[],"next":{}},"config":{"userNotBindPhoneTipString":{}},"recommendPosts":{"articleRecommendations":[],"columnRecommendations":[]},"env":{"isAppView":false,"appViewConfig":{"content_padding_top":128,"content_padding_bottom":56,"content_padding_left":16,"content_padding_right":16,"title_font_size":22,"body_font_size":16,"is_dark_theme":false,"can_auto_load_image":true,"app_info":"OS=iOS"},"isApp":false},"sys":{}}}

我要回帖

更多关于 编译原理 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信