精华内容
下载资源
问答
  • C语言的编译链接过程要把我们编写一个c程序(源代码)转换成可以在硬件上运行程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式目标文件过程。链接把目标文件、操作系统...

    本文转自

    http://blog.csdn.net/yimingsilence/article/details/52800987

    C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。过程图解如下:



      从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。

      编译过程

      编译过程又可以分成两个阶段:编译和会汇编。

      编译

      编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,源文件的编译过程包含两个主要阶段:

      第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。如#include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中。这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境的限制。一个环境需要的代码跟另一个环境所需的代码可能有所不同,因为可用的硬件或操作系统是不同的。在许多情况下,可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应当前的环境。

      主要是以下几方面的处理:

      (1)宏定义指令,如 #define a b

      对于这种伪指令,预编译所要做的是将程序中的所有ab替换也就是说宏定义用实体进行替换),但作为字符串常量的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。

      (2)条件编译指令,如#ifdef#ifndef#else#elif#endif等。

      这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。

      (3)头文件包含指令,如#include 'FileName'或者#include等。头文件原地展开

      在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在 /usr/include目录下。在程序中#include它们要使用尖括号(< >)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号('')。

      (4)特殊符号,预编译程序可以识别一些特殊的符号。

      例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

      预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。

      第二个阶段编译、优化阶段,经过预编译得到的输出文件中,只有常量;如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,}, +,-,*,\等等。

      编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码,汇编过程主要是为了得到汇编代码

      优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。

      对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。

      后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISCCISCVLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。

      汇编

      汇编实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段:

      代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。

      数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

      UNIX环境下主要有三种类型的目标文件:

      (1)可重定位文件

      其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。

      (2)共享的目标文件

      这种文件存放了适合于在两种上下文里链接的代码和数据。第一种是链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个目标文件;第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。

      (3)可执行文件

      它包含了一个可以被操作系统创建一个进程来执行之的文件。汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。

      链接过程

      由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。

      例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。

      链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。

      根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:

      (1)静态链接

      在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。

      (2)动态链接

      在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。

      对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。

      我们在Linux使用的gcc编译器便是把以上的几个过程进行捆绑,使用户只使用一次命令就把编译工作完成,这的确方便了编译工作,但对于初学者了解编译过程就很不利了,下图便是gcc代理的编译过程:


      从上图可以看到:

      预编译

      将.c 文件转化成 .i文件

      使用的gcc命令是:gcc –E

      对应于预处理命令cpp

      编译

      将.c/.h文件转换成.s文件

      使用的gcc命令是:gcc –S

      对应于编译命令 cc –S

      汇编

      将.s 文件转化成 .o文件(链接文件)

      使用的gcc 命令是:gcc –c

      对应于汇编命令是 as

      链接

      将.o文件转化成可执行程序

      使用的gcc 命令是: gcc   xxx.o  -o  xxx

      对应于链接命令是 ld

      总结起来编译过程就上面的四个过程:预编译、编译、汇编、链接。Lia了解这四个过程中所做的工作,对我们理解头文件、库等的工作过程是有帮助的,而且清楚的了解编译链接过程还对我们在编程时定位错误,以及编程时尽量调动编译器的检测错误会有很大的帮助的



    本文转自 菜鸟养成记 51CTO博客,原文链接:http://blog.51cto.com/11674570/1952576
    展开全文
  • C语言的编译链接过程要把我们编写一个c程序(源代码)转换成可以在硬件上运行程序(可执行代码),需要进行编译和链接。编译是把文本形式源代码翻译为机器语言形式目标文件过程。链接把目标文件、操作系统...
  • C语言的编译链接过程要把我们编写一个c程序(源代码)转换成可以在硬件上运行程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式目标文件过程。链接把目标文件、操作系统...

    C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。过程图解如下:

    d4e1904fb10250d270b4c23146c9ed43.png

    从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。

    编译过程

    编译过程又可以分成两个阶段:编译和会汇编。

    编译

    编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,源文件的编译过程包含两个主要阶段:

    第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。如#include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中。这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境的限制。一个环境需要的代码跟另一个环境所需的代码可能有所不同,因为可用的硬件或操作系统是不同的。在许多情况下,可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应当前的环境。

    主要是以下几方面的处理:

    (1)宏定义指令,如 #define a b

    对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。

    (2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。

    这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。

    (3) 头文件包含指令,如#include "FileName"或者#include 等。

    在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在 /usr/include目录下。在程序中#include它们要使用尖括号(< >)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。

    31/3123>

    展开全文
  • C语言的编译过程

    千次阅读 2015-07-16 10:37:38
    一个源程序运行得到结果一般都要经过编译执行的过程,C语言编译和执行过程如下: 编译的过程: 编译的功能将人们能看懂的高级语言,转换成计算机能看懂的二进制语言,可以分为下面的六个阶段: 词法分析阶段...

    一个源程序运行得到结果一般都要经过编译和执行的过程,C语言的编译和执行过程如下:
    这里写图片描述

    编译的过程:
    编译的功能是将人们能看懂的高级语言,转换成计算机能看懂的二进制语言,可以分为下面的六个阶段:

    • 词法分析阶段:根据语言的词法规则来进行分析,词法的规则可用正规文法或正规式来表示是指有限自动机能识别正规文法的语言和正规式组成的集合。
    • 语法分析阶段:在词法分析的基础上将单词分别分解成各类语法单位,它依据语言的语法规则对源程序结构进行分析。
    • 语义分析阶段:检查代码是否有语义错误,为代码生成阶段做准备。
    • 中间代码生成阶段:经过上边述阶段后,会生成一个中间代码,即把源程序转化为一种内部的记号语言,这一阶段依据的是中间代码生成规则。
    • 代码优化阶段:对中间代码进行改造和优化,使得代码节省时间和空间,提高执行效率。
    • 目标代码生成阶段:把中间代码转化为机器上的指令代码,生成的目标代码与机器的硬件和系统有关。

    一个源文件经过上述的6个过程即可转化成目标代码文件,把高级语言转化成机器语言。

    C语言编译的完整过程如下图:
    这里写图片描述
    1、编译预处理
    处理伪指令和特殊符号。伪指令一般指以“#”为开头的指令,包含以下四个方面:
    (1)宏定义指令
    宏定义又称为宏替换,指用一个表达式替换另一个式子,简称为宏。
    (2)条件指令
    条件指令可以使程序员通过定义不同的宏自由控制程序代码的执行,执行哪些代码,跳过哪些代码。预编译处理会将那些多余的代码去掉。
    (3)头文件指令
    #include<stdio.h>,是指把相应的头文件包含至程序中,包含头文件是为了包含头文件中系统定义的函数,以便程序直接调用。
    (4)特殊符号
    预编译的时候会检测有无特殊符号,如LINE和FILE都被当做特殊符号处理,用适当的值来代替。

    源文件经过预编译处理后会变成一个没有宏定义、头文件、条件指令和特殊符号的文件,这个文件与源文件的功能是一样的但是内容有所改变。

    2、编译和优化
    编译:把预编译得到的文件进行词法、语法、语义分析等,使之转化为功能等价的中间代码或者汇编代码。
    优化:对代码进行优化,减小代码的时间和空间,提高执行效率。

    3、汇编程序
    一个程序要执行必须转化成机器语言,汇编程序是将汇编代码转化成机器代码,即机器所能识别的代码。

    4、链接程序
    链接程序把彼此有关的目标文件都联系起来,即把这些文件都联系成一个统一的整体转入机器的内存中,是目标文件能够相互调用。

    展开全文
  • 代码由CPU执行的,而目前的CPU并不能直接执行诸如if…else之类的语句,它只能执行二进制指令。但是二进制指令对人类实在太不友好了:我们很难快速准确的判断一个二进制指令1000010010101001代表什么?所以科学家...

    Q:代码是如何运行的?

    代码是由CPU执行的,而目前的CPU并不能直接执行诸如if…else之类的语句,它只能执行二进制指令。但是二进制指令对人类实在是太不友好了:我们很难快速准确的判断一个二进制指令1000010010101001代表什么?所以科学家们发明汇编语言(实际上就是二进制指令的助记符)。

    假设10101010代表读取内存操作,内存地址是10101111,寄存器地址是11111010,那么完整的操作101010101010111111111010就代表读取某个内存地址的值并装载到寄存器,而汇编语言并没有改变这种操作方式,它只是二进制指令的映射:

    LD:10101010

    id:10101111

    R:11111010

    这样上述指令就可以表达为LD id R ,大大增强了代码的可读性。

    但是这样还不够友好,CPU只能执行三地址表达式,和人的思考方式、语言模式相距甚远。所以伟大的科学家们又发明了高级语言。

    高级语言之所以称之为“高级”,就是因为它更加符合我们的思维和阅读习惯。if…else这种语句看起来要比1010101010舒服的多了。但是计算机并不能直接执行高级语言,所以还需要把高级语言转化为汇编语言/机器指令才能执行。这个过程就是编译。

    JavaScript是什么类型的语言?

    JavaScript 动态类型的动态语言;

    在运行时代码可以根据某些条件改变自身结构,如JavaScript在运行时新的函数、对象、甚至代码都可以被引进(eval),因此JavaScript是动态语言。JavaScript数据类型不是在编译阶段确定,而是在运行时确定,所以JavaScript是动态类型的语言。

    JavaScript是 解释型语言且弱类型,JavaScript 代码需要在机器(node 或者浏览器)上安装一个工具(JS 引擎)才能执行,这是解释型语言所需要的。在生成 AST 之后,就开始一边解释,一边执行。

    Q:JavaScript需要编译吗?

    347da8948555

    MDN定义.png

    与传统的编译语言不同,它不是提前编译的,编译结果也不能在分布式系统中进行移植。但是JavaScript引擎进行编译的步骤和传统的编译语言非常相似,在某些环节可能比预想的要复杂,具体表现在:

    JavaScript引擎在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化等。

    与其他编译语言不同,JavaScript的编译过程不是发生在构建之前的,因此JavaScript引擎不会有大量的时间进行优化。

    对于JavaScript,大部分情况下编译发生在代码执行前的几微秒(甚至更短)。

    JavaScript引擎用尽了各种办法(如JIT,可以延迟编译甚至实施重编译)来保证性能最佳。

    JavaScript是如何运行的

    在传统编译语言的流程中,程序中的一段源代码在执行之前会经历一系列步骤,统称为“编译”。

    常见编译型语言(例如:Java)来说,编译步骤分为:词法分析->语法分析->语义检查->代码优化和字节码生成。

    对于解释型语言(例如 JavaScript)来说,通过词法分析 -> 语法分析 -> 语法树,生成 AST 之后,就开始一边解释,一边执行。

    在JavaScript的执行过程主要有以下几个关键角色:

    引擎:从头到尾负责整个JavaScript程序的编译及执行过程;

    编译器:负责语法分析以及代码生成等;

    作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。作用域本质上就是程序存储和访问变量的规则。

    我们带着Q:变量住在哪里?它们储存在哪里?程序需要时如何找到它们?一起看看JavaScript的具体执行过程:

    分词/词法分析(Tokenizing/Lexing)

    编译器将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token)。如果是有状态的解析,还会赋予单词语义。

    词法单元生成器在判断a是一个独立的词法单元还是其他词法单元的一部分时,调用的是有状态的解析规则,那么这个过程就被称为词法分析。

    For example

    如程序var a = 2;通常会被分解为词法单元:var、a、=、2、; 具体如下图所示。并且给它们加上标注,代表这是一个变量还是一个操作。空格是否会被当作词法单元,取决于空格在这门语言中是否具有意义。

    347da8948555

    分词.png

    解析/语法分析(Parsing)

    语法分析的任务是在词法分析的基础上将单词序列组合成各类语法短语,如“程序”,“语句”,“表达式”等等。

    语法分析程序判断源程序在结构上是否正确。如var str ='s ;这就是典型的语法错误,这种代码无法生成AST,在词法分析阶段就会报错。通常我们这么写代码,IDE 就会报错。这是IDE的优化工作,和词法分析相关。

    将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。

    For example

    上述例子var a = 2;被分解的词法单元在语法分析阶段会被转换成如下结构:

    347da8948555

    AST.png

    预编译(开放内存空间,存放变量和函数)

    当JavaScript引擎解析脚本时,它会先在预编译期对所有声明的变量和函数进行处理!编译阶段的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来,因此这个过程编译器和作用域会进行如下互动:

    347da8948555

    预编译-编译器.png

    ⚠️ 预编译阶段没有初始化行为(赋值),匿名函数不参与预编译。只有在解释执行阶段才会进行变量初始化。

    JavaScript的作用域才有词法作用域工作模型,JavaScript 的变量和函数作用域是在定义时决定的,而不是执行时决定的。

    例:看一个简单的声明语句var name = 'bubble';在JS引擎眼里,它包含两个声明,其中

    var name 在编译时(此步骤由编译器)处理,

    name=bubble在运行时处理,即第4步(解释执行由JS引擎处理)。

    解释执行

    在执行过程中,JavaScript 引擎是严格按着作用域机制(scope)来执行的。引擎在运行时会完成对变量的赋值操作,因此和作用域有如下互动:

    347da8948555

    解释执行-JS引擎.png

    作用域套作用域,就有了作用域链:

    JavaScript 引擎通过作用域链(scope chain)把多个嵌套的作用域串连在一起,并借助这个链条帮助 JavaScript 解释器检索变量的值。这个作用域链相当于一个索引表,并通过编号来存储它们的嵌套关系。当 JavaScript 解释器检索变量的值,会按着这个索引编号进行快速查找,直到找到全局对象(global object)为止,如果没有找到值,则传递一个特殊的 undefined 值。

    var scope = "global";

    scopeTest();

    function scopeTest(){

    console.log(scope);

    var scope = "local";

    console.log(scope);

    }

    打印结果:undefined,local;

    而引擎查找变量的方式会直接影响到查找的结果,尤其在变量未声明的情况下:

    347da8948555

    引擎查找变量的方式.png

    总结一下,任何JavaScript代码片段在执行前都要进行编译(通常就在执行前)。因此,JavaScript编译器首先会对var a = 2;这段程序进行编译,然后做好执行它的准备,并且通常马上就会执行它。

    让我们看看引擎对下面这段代码做了什么吧!

    var a = 1; // 变量声明

    function b(y){ //函数声明

    var x = 1;

    console.log('so easy');

    };

    var c = function(){ //变量声明

    //...

    }

    b(100);

    var d = 0;

    页面产生便创建了GO全局对象(Global Object),也就是window对象;

    第一个script脚本文件加载;

    脚本文件加载后,分析语法是否合法;

    开始预编译:

    查找变量声明,作为GO属性,值赋予undefined;

    查找函数声明,作为GO属性,值赋予函数体;

    GO/window = {

    //页面加载创建GO同时,创建了document、screen等属性

    a: undefined,

    c: undefined,

    b: function(y){

    var x = 1;

    console.log('so easy');

    }

    }

    解释执行代码,找到变量并赋值(直到执行函数b)

    GO/window = {

    a: 1,

    c: function(){ },

    b: function(y){

    var x = 1;

    console.log('so easy');

    }

    }

    执行函数b之前,发生预编译:

    创建AO活动对象(Active Object)

    查找函数形参及函数内变量声明,形参名及变量名作为AO对象的属性,值为undefined

    实参值赋给形参

    AO = {

    //创建AO同时,创建了arguments等等属性,此处省略

    y: 100,

    x: undefined

    }

    解释执行函数中的代码;

    x=1 输出so easy

    第一个脚本文件执行完毕,加载第二个脚本文件

    第二个脚本文件加载完毕后,进行语法分析

    语法分析完毕,开始预编译

    重复最开始的预编译步骤……

    测试

    例1

    function foo() {

    console.log(a);

    a = 1;

    }

    foo(); // Uncaught ReferenceError: a is not defined

    function bar() {

    a = 1;

    console.log(a);

    }

    bar(); // 1

    这是因为函数中的 "a" 并没有通过 var 关键字声明,所有不会被存放在 AO 中。没有 a 的值,然后就会到全局去找,全局也没有,所以会报错。

    例2

    console.log(foo);

    function foo(){

    console.log("foo");

    }

    var foo = 1;

    会打印函数,而不是 undefined 。

    这是因为在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

    下方开始大量扩展知识

    编程语言的分类

    与硬件的距离

    比较低级 Low-level 语言

    最低级的语言就是机器语言,由0和1构成,通过面板、打孔卡输入。

    接下来是汇编语言,它对硬件指令做了简单的封装,一些操作可以用ADD、MOVE等英文单词来表示。目前在内核/驱动中会被用到。

    比较高级 High-level 语言

    除了上面两种,其他语言都是高级语言,将很多细节交由计算机(编译器)把控,同时变得更加抽象。高级语言中也有相对低级和高级的。如C属于非常低级的高级语言,因为C语言中也还是时不时的会用到硬件知识。而类似Ruby或JS这样的脚本语言就基本不用操心硬件的事了。

    一般来讲,跟硬件离的越近,就越能通过打磨去挖掘硬件潜力,写成的程序执行效率就会越高,但是开发效率肯定也就越低。

    是否需要编译

    所有语言最终都需要转变为机器码,基于其转换为机器码的方式,高级语言可大致分为编译型和解释型两类(汇编语言无须编译或解释,仅需汇编成机器码)

    编译型语言(Compiled Language)

    利用编译器先将代码编译为机器码,再加以运行。如C++代码在Windows上会编译成.obj文件,而在Linux上则生成.o文件。

    解释型语言(Interpreted Language)

    利用解释器,在运行期间,动态将代码逐行解释(Interpret)为机器代码执行。

    有时也叫脚本语言(Scripting Language)。如Python,Ruby、BASIC、JavaScript,写好之后无需编译,直接运行于自己的解释器之上。

    编译型语言的运行速度更快(因为已经预先编译好,运行时无须执行解释这一步骤),而因此,编译型语言的开发/调试时间也较长,因为每次调试之前都需要编译一次。而解释型语言则可以快速的测试和调试,因为跟硬件隔了一层,所以效率上一般是比较低的,但功能上可以更为灵活。

    半解释半编译

    Java就是两种类型的结合典型。无论是在什么操作系统上.java文件编译出的都是.class文件(这就是字节码文件,一种中间形态的目标代码)。然后Java对不同的系统提供不同的Java虚拟机用于解释执行字节码文件。解释执行并不生成目标代码,但其最终还是要转为汇编/二进制指令来给计算机执行的。

    Java采用半解释半编译的好处就是大大提升了开发效率,然而相应的则降低了代码的执行效率,毕竟虚拟机是有性能损失的。

    编程范式(Programming Paradigms)

    也叫编程范型、编程典范,基于编程语言的特点而进行分类的方式,一种语言可以支持超过一种编程范型,常见范式如下:

    命令式和声明式

    这是两个相对/并列的范式,命令式编程描述过程 ,声明式编程描述目标。

    命令式编程(Imperative programming)

    命令式编程描述计算所需作出的行为。几乎所有的计算机硬件都是命令式的。

    子范式:过程式和面向对象式,过程式更靠近机器,面向对象式更贴近程序员。

    过程式编程(Procedural programming)

    把操作转换成语句一步步的去做,主要使用顺序、条件选择、循环三种基本结构来编写程序。

    来源于结构化编程,其概念基于过程(procedure、routine、subroutine、function),过程由一系列可执行可计算的步骤构成。

    Fortran、ALGOL、COBOL、BASIC、Pascal和C等语言采用过程式编程。

    面向对象式编程(Object-oriented programming)

    具有对象概念的编程范式,先把数据封装成对象,通过对象之间的交互来实现功能。

    重要的面向对象编程语言包括Common Lisp、Python、C++、Java、C#、Perl、PHP、Ruby、Swift等。

    声明式编程(Declarative programming)

    声明式编程描述目标的性质,让计算机明白目标,而非流程。声明式编程通常被定义为所有的“非命令式编程”。

    声明式编程包括数据库查询语言(SQL)、正则表达式、逻辑式编程、函数式编程和configuration management。声明式编程通常用作解决人工智能和约束满足问题。

    子范型:函数式编程、逻辑式编程、约束式编程、数据流式编程

    函数式编程(Functional programming)

    又称泛函编程,它将计算视为数学上的函数运算,避免变量或状态(只有函数及其参数)。其最重要的基础是λ演算(lambda calculus),λ演算的函数可以接受函数当作输入和输出。

    分为纯函数式编程(Purely functional programming)和函数逻辑式编程(Functional logic programming,函数式编程和逻辑式编程的组合)

    逻辑式编程(Logic programming)

    逻辑式编程基于数理逻辑,它设置答案所须匹配的规则来解决问题,而非设置步骤来解决问题。过程为:事实+规则=结果。

    最常用的逻辑式编程语言是Prolog,Mercury则较适用于大型方案。

    约束式编程(Constraint programming)

    在这种范式中,变量之间的关系是以约束的形式陈述的,它们并非明确说明了要去执行步骤的某一步,而是规范其解的一些属性。

    数据流式编程(Dataflow programming)

    将程序建模为一个描述数据流的有向图。例如BLODI。

    结构化和非结构化

    这是两个相对的范式,非结构化编程是最早的编程范式,现今的计算机科学家都同意结构化编程的好处。

    结构化编程(Structured programming)

    通过子程序、代码块、for循环、while循环等结构来取代之前的goto语句,以提高代码的清晰程度,获得更好的可读性。现今的大部分高级语言都是结构化的。

    结构化编程的流程包括顺序、选择(if, else, switch)、循环(for, while)几类。

    非结构化编程(Non-structured programming)

    是最早的编程范式,相对于结构化编程,特点是其控制流是通过(容易引起混乱的)goto语句跳转实现的。非结构化编程包括机器语言、汇编语言、MS-DOS batch、以及早期的BASIC及Fortran等等。

    泛型编程(Generic programming)

    泛型允许程序员在用强类型语言编写代码时使用一些以后才指定的类型。

    Ada、Delphi、C#、Java、Swift称之为泛型(generics),Scala和Haskell称之为参数多态(parametric polymorphism),C++称之为模板。

    动态or静态分类

    动态语言(Dynamic programming language)在运行时可以改变自身结构:新的函数、对象甚至代码可以被引进,已有的函数可以被删除或有其他结构上的变化。JavaScript、PHP、Python、Ruby属于动态语言,而C和C++则不属于动态语言。

    大部分动态语言都使用动态类型,但也有些不是。

    语言类型系统(Type system)分类

    动态和静态类型检查

    静态类型检查

    对类型的检查发生在编译时。编译语言通常使用静态类型检查。

    动态类型检查

    对类型的检查发生在运行时。动态类型检查经常出现在脚本语言和解释型语言中。

    大部分动态语言都使用动态类型,但也有些不是。

    强弱类型

    按照编程语言对于混入不同数据类型的值进行运算时的处理方式不同分为强类型和弱类型。

    强类型(Strongly typed)

    强类型的语言遇到函数形参和实参的类型不匹配时通常会失败。

    弱类型(Weakly/Loosely typed)

    弱类型的语言常常会进行隐式的转换(并可能造成不可知的后果)。

    类型安全和内存安全

    按照类型运算和转换的安全性不同分为类型安全和内存安全。通常来说,类型安全和内存安全是同时存在的。

    类型安全

    它不允许导致不正确的情况的运算或转换,计算机科学就认为该语言是类型安全的。

    内存安全

    指程序不被允许访问没有分配给它的内存,比如:内存安全语言会做数组边界检查。

    比如以下例子:

    var x:= 5

    var y:= “37”

    var z:= x + y

    上例中的z的值为42,不管编写者有没有这个意图,该语言定义了明确的结果,且程序不会就此崩溃,或将不明定义的值赋给z。就这方面而言,这样的语言就是类型安全的。

    再比如:

    int x = 5

    char y[] = “37”

    char* z = x + y

    在这个例子中,z将会指向一个超过y地址5个字节的存储器地址,相当于指向y字符串的指针之后的两个空字符之处。这个地址的内容尚未定义,且有可能超出存储器的定址界线,这就是一个类型不安全/内存不安全的语言。

    显式声明和隐式暗示

    许多静态类型系统,如C和Java,要求变量声明类型:编写者必须以指定类型明确地关系到每一个变量上。其它的,如Haskell,则进行类型推断:编译器根据编写者如何运用这些变量,以草拟出关于这个变量的类型的结论。

    例如,给定一个函数f(x,y),它将x和y加起来,编译器可以推断出x和y必须是数字——因为加法仅定义给数字。因此,任何在其它地方以非数值类型(如字符串或链表)作为参数来调用f的话,将会发出一个错误。

    在代码中数值、字符串常量以及表达式,经常可以在详细的前后文中暗示类型。例如,一个表达式3.14可暗示浮点数类型;而[1, 2, 3]则可暗示一个整数的链表;通常是一个数组。

    推荐阅读

    展开全文
  • 生成可执行的二进制机器代码 执行 【在特定的系统的环境下运行C 语言】 \n 和 \ t 的区别; \ n 相当于打了 一个回车 \t 相当于一个 Tab 键 int main(){} 和 void main(){} 的区别 void main() {} 防止为 return 0;...
  • C语言文件的编译执行的四个阶段

    千次阅读 2016-07-03 22:49:11
    C语言文件的编译执行的四个阶段并分别描 C语言编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译...
  • C语言文件的编译执行的四个阶段

    千次阅读 2016-07-15 13:37:47
    C语言文件的编译执行的四个阶段并分别描述     C语言编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器...
  • 学过C语言的人都应该知道,我们所编辑的C语言程序不能直接放到机器上运行,它只不过一个带".c"后缀文件(也称为源代码)而已,需要经过一定处理才能转换成机器上可运行执行文件。我们将对C语言的这种...
  • #includeint main(){printf("Hello World!\n");return 0;}对于这个最简单“Hello World!”程序——hello.c,它生命周期一开始时一个高级c程序,因为它处于...然而这些指令按照一种称为可执行目标程序格式...
  • c语言编译链接整个过程

    千次阅读 2016-10-19 13:33:34
    C语言的编译链接过程要把我们编写一个c程序(源代码)转换成可以在硬件上运行程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式目标文件过程。链接把目标文件、操作系统...
  • C语言的编译链接过程

    2013-09-15 15:46:09
    C语言的编译链接过程要把我们编写一个c程序(源代码)转换成可以在硬件上运行程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式目标文件过程。链接把目标文件、操作系统...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,339
精华内容 935
关键字:

c语言是编译执行的语言

c语言 订阅