本篇文章给大家谈谈e-world是什么意思、读音,以及同学你会hello world吗?给我讲清楚点对应的知识点,文章可能有点长,但是希望大家可以阅读完,增长自己的知识,最重要的是希望对各位有所帮助,可以解决了您的问题,不要忘了收藏本站喔。
面试官超级喜欢问helloworld问题特别是校招,我曾经校招就碰到过3次
其实很多看起来顺其自然简单的东西,背后是一套复杂的学问
记得很清楚第一次面试阿里巴巴的时候,面试官上来让我写一个helloworld程序
当时我真的一面黑人问号的确认了三遍,面试官依旧淡定的说是的
写完就让我聊helloworld,一个helloworld聊了一个小时
那时候面试是校招实习,聊完我真的怀疑人生了
这个问题非常考验应试者的计算机基础、自学能力以及对问题钻研的能力
代码如上,现在看来很简单怎么也不会想到这样的程序还会出错
不丢人的说,龙叔第一次在写这段代码的时候,这个简单的程序大概写了三四遍
好不容易倒腾完了,点击运行后发现少了头文件
加上之后再运行,发现少了结尾的;号
就这样倒腾了好几遍,终于在控制台输出了helloworld!!!,那一刻我激动得笑出了声
于是骄傲的我赶紧趁热打铁,写了下面的版本
这两个版本的代码都是C语言写的,C语言课程应该是大学的通识课了,用这个语言讲,大家都能看的明白
外甥非常好奇,这helloworld到底是怎么输出到屏幕的
龙叔也好奇过这个问题,只不过是在C语言学完之后才开始好奇
从冯·诺依曼的结构我们可以知道,计算机的基本组成部分如下:
每个部分都有自己的工作,恪尽职守,这个在系统设计上叫模块清晰、功能完整
接下来就从几个方面好好说说这个helloworld,让面试官目瞪口呆下
代码输入这么简单的问题,还用龙叔讲??
如上图首先说下输入过程,此图做了一个浓缩,主要部件键盘、主机(CPU、内存、磁盘)、显示器
代码输入过程看起来是蛮简单的,打开一个编辑器或者IDE,即可开始代码输入
刚开始学习推荐使用IDE,当然不是没有IDE就不能写代码
任何一个文本编辑器都可以进行代码输入
IDE(IntegratedDevelopmentEnvironment)集成开发环境,一般包括代码编辑器、编译器、调试器和图形用户界面等工具
比如写C&Cwithclass会下载vc++、devC++、VS、Clion等等软件,很棒,工具能提高生产力
我习惯用Clion,IDE都是根据自己的需要来选择,用着爽就行
IDE是一个软件,集成度很高的软件,启动IDE意味着操作系统必须启动一个进程该进程叫IDE进程
既然是集成内部还有很多线程负责集成模块的工作
关于进程、线程深层次的内容,后面文章会详细讲出这里就先不展开了
IDE进程会被操作系统管理和调度
要明白这个问题得先说说键盘工作原理
键盘的基本原理就是实时监控按键,将按键信息送入计算机
在键盘的内部设计中有定位按键位置的键位扫描电路,当任何键被按下是编码电路就会产生代码,这些代码会被送入接口电路,这些电路被称为键盘控制电路
根据键盘工作原理,分为编码键盘和非编码键盘
编码键盘:键盘控制电路的功能完全依靠硬件来自动完成,根据按键自动识别编码信息
非编码键盘:键盘控制电路的功能依靠硬件和软件共同完成
监控键盘的原理就是电位扫描,电位扫描分为逐行扫描法和行列扫描法
原来如此,原来键盘是这样工作的,从此我在飞速敲击键盘时会更有力量了
这仅仅是键盘驱动进程拿到键盘输入的结果,应用程序是如何获得输入数据的呢?
键盘后台进程拿到结果后会放在自己的共享内存中,应用程序通过共享内存获取到键盘输入结果
上图中很明显看到键盘输入是会发生IO操作的,IO整体内容这里不展开,后面文章会更新
一顿操作,此时IDE会拿到键盘输入的代码,你的helloworld代码终于在显示器中让你看到了
接下来说说躺在IDE中代码是如何运行出结果的
代码终于是敲好了,激动的你一般会想着要运行一手,迫不及待看到结果
别急再等等,我们书写的代码程序被称为源代码,CPU执行的是机器码,这个包含机器码的程序被称为可执行程序
先来看看源代码是如何变为可执行程序的
IDE是集成环境,很容易让初学者以为源代码直接被CPU执行了
源代码必须经过编译器编译才能成为二进制的可执行程序
IDE里面集成了编译器调试器,C语言的编译器主要有GNU编译器套件中的GCC、MicrosoftC或称MSC、BorlandTurboC或称TurboC
编译过程是一个复杂的过程,接下来聊聊这个复杂的过程
编译是个过程的总称,其中还包括不同的阶段,源代码预处理阶段、编译优化阶段、汇编阶段、链接阶段
预处理器将对其中的伪指令(以#开头的指令)和特殊符号进行处理,删除所有的注释,最后生成.i文件
gcc-EhelloWorld.cpp-ohelloWorld.i
此时.i文件是删除了注释、宏替换、头文件也加载进来了,该文件比源代码文件大
内容太多,代码就不粘贴了,大家自行试验下
编译程序所要作的工作就是通过词法分析、语法分析、语义分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码或汇编代码
词法分析和语法分析千万不要混淆了,校招面试的时候被面试官给绕了半天
词法分析器识别出Token,把字符串转换成一个个Token
Token包括关键字、标识符、字面量、操作符、界符等
为什么要这样做呢,把代码里的单词进行分类,编译器后面的阶段不就更好处理理解代码了嘛
语法分析阶段把Token串,转换成一个体现语法规则的树状数据结构,即抽象语法树AST
AST树反映了程序的语法结构
比如helloworld代码经过语法分析之后会得到一个AST树
很多人疑惑为什么要把程序转换成AST这么一颗树呢?
因为编译器不像人能直接理解语句的含义,AST树更有结构性,后续阶段可以针对这颗树做各种分析
语义分析顾名思义就是理解语义,也就是理解程序要做什么
比如理解"+"符号是执行加法、"="号是执行赋值操作、"for"结构就是去执行循环等等
这个阶段要做的就是进行上下文分析,上下文分析包括引用消解、类型分析以及检查等等
引用消解:找到变量所在的作用域,一个变量作用范围属于全局还是局部作用域
类型识别:比如执行a=3,需要识别出变量a的类型,因为浮点数和整型执行不一样,要执行不同的运算方式
类型检查:比如intb=3,是否可以进行定义赋值,等号右边的表达式必须返回一个整型的数据或者能够自动转换成整型的数据,才能够对类型为整型的变量b进行赋值
经过语义分析后获得的信息(引用消解信息、类型信息),会在AST上进行标注,形成带有标注的语法树,让编译器更好的理解程序的语义
在语法分析后有了程序的抽象语法树,在语义分析后有了带有标注的AST和符号表后,就可以深度优先遍历AST,并且一边遍历一边执行结点的语义规则
对于解释性语言整个遍历的过程就是执行代码的过程
解释性语言如Python等,在遍历带有标注和符号表的抽象语法树即可开始执行
编译性语言需要生成目标代码,如C、C++
编译型语言需要生成目标代码,而解释性语言只需要解释器去执行语义就可以了
之前校招面试的时候,面试官看我把helloworld讲的这么好,顺手问了句Java、Python执行helloworld的过程一样么?
当时愣了下,知道不一样但是没解释的很清晰
对于不同架构的CPU,生成的汇编代码不同,如果优化是针对每一种汇编代码,那这个过程就相当复杂了
所以在生成目标代码之前增加一个过程,先生成一个中间代码IR,统一优化后再生成目标代码
优化代码主要从分为本地优化、全局优化、过程间优化
本地优化:可用表达式分析、活跃性分析
全局优化:基于控制流图CFG作优化
过程间优化:跨越函数的优化,多个函数间作优化
说了一些干的,举个例子让大家理解下到底如何优化
活跃性分析就是将一些没有用到的代码删除,比如一些没有用到的变量
目标代码生成就是将优化后的IR代码翻译为汇编代码
gcc-ShelloWorld.cpp-ohelloWorld.s
生成的汇编代码:
上面的编译阶段的生成的汇编代码还是人能看懂的,不是给机器直接执行的,机器执行的叫做机器码
机器码放在可执行文件中
unix环境中存在好几种目标文件:
不同的操作系统的可执行文件格式不同
汇编程序生成的实际上是第一种类型的目标文件,链接完成之后才能生成可执行文件
将汇编阶段生成的一个个的目标文件链接在一起生成可执行文件
其实很多人不理解为什么需要链接这个过程,明明汇编阶段已经生成目标代码
举个例子大家就明白了,日常做系统开发的时候,我们讲究系统功能模块化现在都是微服务
一个复杂系统,往往会分成多个不同的子系统子系统在拆分为不同的功能模块
链接的过程也和这个类似一个复杂的软件需要拆分为多个不同的模块,每个模块独立编译
根据需要在"组合"起来,这个组装模块的过程就是链接
比如main函数中调用了printf函数,mian函数在编译时并不知道printf函数的地址(每个模块都是单独编译的)
但是调用又必须知道函数地址才能发生调用关系
编译时暂时把这个地址搁置,链接时在进行地址修正
链接完成之后会形成一个可执行文件,可执行文件也叫ELF文件
这个ELF文件以及其他文件也够喝一壶,放在后面讲聊文件系统一起聊
装载就是把可执行程序加载到内存中,供后续的CPU执行
在linux命令行中我们经常这样执行一个可执行程序
./a.out
这样一下就把程序加载到内存中,加载完成之后直接执行了
strace./a.out
这个命令可以看到所有的系统调用
可以看到第一个执行的系统调用是execve
通过manexecve可以看到这个函数的描述
execve()executestheprogrampointedtobyfilename.filenamemustbeeitherabinaryexecutable,orascriptstartingwithalineoftheform:
execve()执行文件指定的程序文件必须是二进制可执行文件,或者执行一个以shebang开头的脚本
主要执行工作落在了do_execve上,继续看看do_execve源码
前面就是计算一些参数如argv、env拷贝相关数据,最终装载程序执行search_binary_handler
list_for_each_entry函数非常重要,这个函数遍历所有formats列表,找到当前系统合适的可装载格式
前面已经说过,linux下可执行文件格式是ELF文件
retval=fmt->load_binary(bprm)就是load可执行程序
load_binary是加载二进制文件啊,我们的程序明明是ELF文件
仔细看看load_binary的源码会发现里面有一个初始化,初始化的时候会做一个赋值替换为
或许到这里大家基本已经了解了,但还是疑惑怎么才能判断加载的ELF文件
可以去看看源码怎么写的(源码太长,这里就不粘贴了告诉你位置有兴趣的自己去看看)
有个函数叫staticintload_elf_binary(structlinux_binprm*bprm);
再看看我们的可执行程序头上长啥样readelf-la.out即可查看可执行文件头部信息
解释器通过判断ProgramHeaders中的INTERP的值得到该可执行程序的文件类型
上面步骤是一个循环也称为CPU指令周期,CPU的工作就是一个周期接着一个周期,周而复始。
在Unix系统中,每个进程都会默认打开三种标准I/O分别是STDIN、STDOUT和STDERR
这只是第一次源码,愿意了解的可以看看vfprintf实现,你会发现底层使用了缓冲输出
输出是一次output,也就是会经历一次从内存外部文件系统的数据转移
到这里基本就讲完了了helloworld全部内容,讲完了不一定是讲透彻了
比如关于文件系统的知识、IO知识、CPU调度知识、进程管理、内存管理等等知识都没法通过一篇文章说透彻
说实话一个小小的helloworld藏着大学问,囊括的内容也实在是太丰富了
原文链接:https://www.cnblogs.com/zhonglongbo/p/14176636.html
如果你还想了解更多这方面的信息,记得收藏关注本站。