C的日记-编译和执行

 

程序=算法(灵魂)+数据结构(加工对象)+程序设计方法(适当的)+语言(工具)


#include <stdio.h>】: C语言标准输入输出函数

上面那行代码是我们走进编程世界的第一行程序,今天我们从这里开始,来分析程序背后的隐秘。

---------------------------------------------------------------------------------------------------------------------------------------

 
当我们使用这些函数库函数时,(编译系统)要求我们在完成编译前,对这些函数进行导入。

include导入C基本库的某个头文件(如#include <stdio.h>),标准库的头文件结构大概是这样的

/* stdio.h standard header */ #ifndef _STDIO #define _STDIO #endif /*macros*/ #define NULL _NULL #define EOF -1 #define BUFSIZ 512 ... int fclose(FILE *); inf feof(FILE*); int getc(FILE *); int getchar(void); int printf(const char *,...); int putchar(int ); int scanf(const char *,...);
   ...

在头文件中,有宏的定义,条件的声明,函数的声明等等信息

/* printf.c */ #include "xstdio.h" ... /* 使用指针对输出流进行相关操作 */ int (printf)(const char *fmt,...){ int ans; va_list ap; va_start(ap,fmt); ans=_Printf(......); va end(ap); return(ans); }

printf.c定义了宏调用的隐藏库函数printf。而我们在函数中使用printf时,链接时先进行的是C标准函数库中的隐藏函数对字符串必要的处理,之后会调用系统提供的API或变量如va_list。

大致可以理解为在头文件中声明,在标准库函数中实现,在源代码中调用,而实现的依据就是通过操作系统提供API。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

【预备知识】:计算机软件结构;编译和链接过程

【计算机软件结构】

 开发工具/应用程序
       ||函数接口
      函数库———————————————C基本函数库,Java的swing函数库等.o/.obj
||操作系统接口(应用编程接口) Linux的glibc的API,windows的Windows API 系统运行库———————————————Linux运行库,windows运行库,用户自定义库等编译好形成的二进制文件.o/.obj ||系统调用接口 系统内核—————————————硬件驱动:由硬件开发商根据某个OS的系统调用接口设计的能运行自己硬件的程序。 ||硬件接口 /|\ 硬件———————————>————|

 

【编译过程】

    //例:hello.c

    #include <stdio.h>

    void main{ printf("Hello world"); }

   

 [预编译] hello.c+stdio.h -> hello.i
           删除#define,展开宏定义(#define后的大写常量)替换;
          处理有条件的预编译命令,#if等;
          处理#include预编译命令,将include包含的文件插入到当前命令的位置;
          删除所有注释标记;


    [编译]  hello.i -> hello.s
          功能:程序经过语法、词法、语义检查编译成汇编代码文件;
          步骤:
              <1>词法分析:通过对程序的代码分割成一系列的记号如[!(=等然后对记号进行分类放置!
                    记号分类:关键字
                             标识符->符号表
                             字面量->文字表
                             特殊符号
              <2>语法分析:对记号进行语法分析得到语法树!
                   语法树是以表达式为结点的树,树的叶子节点是变量,非叶子节点是符号常量。
              <3>语义分析:对语法树的进行静态语义分析,如类型判断、类型转换等!
                    语义分析后强制类型转换过的变量类型会更新到符号表中。
    [编译器:源代码优化]
          功能:语法树转换成中间码(如:三地址码)后进行简单运算。
                    
    ----------- ATT:从源程序到中间码都与机器无关!,中间码之后的操作和机器有关!!------------

    [汇编] hello.s -> hello.o
          功能:汇编代码按照对照表翻译成机器指令;
          步骤:
              <1>代码生成:根据目标机器的字长、寄存器、数据类型等来生成不同代码序列(汇编语言->机器语言);
              <2>目标代码优化:选择合适的寻址方式、位移计算乘法等来对二进制进行相应的操作。
        
    ----------- ATT:编译成目标文件后依旧未分配变量地址空间,分配变量/函数地址在链接时进行!!------------
    
【静态链接】 hello.o + X.o + XXX.o -> 最终可执行文件.exe/.deb
      需求:程序分割成多个模块后,模块间的通信如:变量的访问/函数访问 等需要通过变量/函数在内存中的绝对地址。
      功能:多个模块的目标文件和库一起链接形成可执行文件,最常见的库就是运行时库。
      备注:目标文件是不能独自完成任务的,如hello.c中的printf函数是头文件中的没错,但头文件调用了系统的运行时库才实现。
              运行时库是支持程序运行的基本函数集合。
              库本质上是一组打包存放的目标文件!!
      步骤:
          <1>地址和空间的分配:系统对变量/函数根据类型分配相应空间。
          <2>符号决议:对变量符号/函数符号的引用修正。
          <3>重定位:在编译过程中进行汇编等操作,但是所有操作地址置为0,而在编译过程中,拿已经分配好的地址对0进行置换,我们就把这个地址修正的过程成为重定位。重定位实际上就是给地址打补丁,使他们指向正确的地址。

【C的日记-静态和动态链接】

 

-----------------------我们来总结下(静态链接和动态链接可以同时存在,如.exe)---传送门-------------------------

 

【编译】
   编译系统(不同系统上不同)是把源代码(不同系统上相同)->通过编译先转换成目标程序(.obj)文件。

 

【链接】(静态链接)

   目标程序与C标准库转换而来的操作系统库函数(API)连接(把可执行文件段装载到应用程序虚拟内存中)->形成某个系统可执行的目标文件(如windows上的.exe)本质上都是机器二进制文件。


【执行】

  【链接】(动态链接)静态链接智能版,链接时需要检测

  【正式执行】
  下载软件的时候不同系统会有不同的版本,我们下载编译好的可执行文件,通过各种执行方法(GUI的双击,CLI上的命令)把可执行二进制文件加载到内存中,形成进程,然后把进程调度到CPU的时钟脉冲执行,进行用户输入输出或其他服务。


----------------------------------------------------------------------------------------------------------------------------------------------

有了上面的基础,我们开始思考~

  问:函数库是什么?

  答:我们可以简单理解有个库,库中有多个人们事先定义好的文件,这些文件中有一个或多个函数。

     问:这个库是什么? 

     答:可以理解为C中的<stdio.h>即标准输入输出库、java中的jar包。

     问:事先定义好的文件是什么? 

       答:一个编译好但未链接的源代码就是一个文件,如C中的.o,Java中的.class。

     问:文件中的函数怎么理解?

     答:函数就是在源代码中定义的啊,比如你定义了一个函数add(int x,int y){...};;你可以通过调用引入/导入该库,然后使用这些函数,如add(2,3);

        问:引入/导入如何理解

        答:有访问权限..或者可以说你能访问的到这个函数然后才能谈使用它,而include<stdio.h>和import..变相来说就是提供这个访问权限的。但是它们还是有区别的,include是把函数库插入到当前位置;import则提供了一个类库路径的简写,需要时去那个路径找类库。

     问:这个函数库/类库能通用么?

     答:不同编译器编译而来的目标文件由于命名规范、格式等不同大多无法通用,不同操作系统上的类库由于调用系统API不同大多也无法适用,结论就是只有在某 个特定的系统上、某个特定的编译器编译而来的类库才能通用啊。当然java是例外,后面会说,但是java也不可能使用所有其他编译器编译的到的类库。

     问:函数库是怎么使用的?

     答:先导入,有了访问权限之后在源代码中进行调用。如果是静态链接,那就先链接库文件再运行,如果是动态链接就先运行,运行时进行智能链接。

  ......

 

下面这些东西都是站在上面的知识点肩膀上说的,高度决定视野嘛~理解错误还请指正,拜谢!
---------------------------------------------------------------------------------
[C的标准库函数和操作系统API的关系]
标准C的库函数和数据类型在任何操作系统上都可以编译执行而且效果是一样的,但是在内部的实现方式和存储方式未必一样,没有可以不依赖底层存在的程序。可以把C编译器看成操作系统上的一个特殊应用程序,它是应用程序和操作系统的桥梁;C调用的库函数是位于系统内核上的函数,这个库函数依赖于系统提供的API(操作系统函数)而运行,windows和linux都有各自的API,这个API有的相同(如C的标准库函数),有些不同(文件存储方式函数)。这些API函数由底层硬件可识别的汇编语言或二进制编码组成。
在linux和windows中,C的标准库函数是一定一样的(兼容),即操作系统提供的API以及实现方式都是一样的。但是其他的非标准函数未必一样,

---------------------------------------------------------------------------------
【为什么linux不支持exe文件直接打开?】
Frist:什么是操作系统?
操作系统是管理和控制硬件和软件资源的最基本的用户软件,位于硬件和应用软件之间,同时提供硬件和软件的接口。
编译器根据操作系统提供的API把 源程序和库函数 编译成二进制形式的操作系统可执行程序,有两个原因:
    <1>可执行文件格式不同,windows是PE,linux是ELF;
    <2>不同系统提供的的API是不同的,只有部分函数是相同的;(wine是使用API转换做出linux上对应于windows的相应dll函数)

能在windows平台上直接运行了的程序都是编译过的,已经和系统API或硬件有一定关联,由于系统或硬件的不同,所以应用程序在不同系统间无法随便移植。

但是Java源程序编译后依然可以移植。
----------------------------------------------------------------------------------
【java的跨平台是怎么回事】
首先何为跨平台,跨平台不是源码跨平台,而特定的编译器编译后的软件也只能在对应的操作系统上执行。
所以跨平台需要跨越语言编码和操作系统的阻隔,不能直接编译成机器语言,否则就与平台相关。
所以Java使用了【Java编译器】使得源程序编译成【通用的中间码(字节码)】,中间码与平台无关。
再给不同系统配置不同的【解释器】,在不同的操作系统通过Java虚拟机使用中间码转换调用不同的API。
由此实现了一处编译,到处运行!
----------------------------------------------------------------------------------

[安卓项目发布如何实现的]
使用某个IDE导入某个安卓项目后,我们可以在类库文件夹中导入官方提供的某些如support-v7类库,这个类库我们以现有知识大胆猜测下是可以运行在多个系统上的,原因很简单:Java编译器编译成中间码,中间码与平台无关,搭配相应解释器自然可以跨平台了。我们换一个编译器,比如gcc,在编译过程的最后一步汇编,自然就与机器有关了,所以gcc编译而来的类库不能用在和原始gcc不同系统的机器上,也不能在非gcc编译器上进行链接。

再说说安卓项目,我们可以想象项目编写完发布到手机上所经历的过程:我们点击run as android application后,这个项目和它的类库(包括support-v7)先在IDE上编译成目标文件(未链接),再发送到手机上运行。运行时,源码目标文件先调用导入的类库文件执行相应的操作中,链接系统API(动态链接),需要什么库文件就把什么加载到虚拟内存中,当前页面的库文件建立完毕后建立虚拟内存与物理内存之间的映射,接着这个页面就显示出来了。
----------------------------------------------------------------------------------
【编程语言执行方式】
<1>C,C++,VisualBasic,直接编译。 缺点:只能使用适用于windows系统的编译器;优点:执行速度快。
<2>Html,JavaScript,直接解释。  缺点,不会编译,只能解释,容易暴露;优点:跨平台。
<3>Java,.Net。一次编译,到处解释。 缺点:执行速度慢;优点:安全,跨平台,垃圾回收等。
----------------------------------------------------------------------------------
附上:
 C语言32个关键字
  关键字就是已被C语言本身使用,不能作其它用途使用的字。例如关键字不能用作变量名、函数名等
  由ANSI标准定义的C语言关键字共32个:
  auto double int struct break else long switch
  case enum register typedef char extern return union
  const float short unsigned continue for signed void
  default goto sizeof volatile do if while static
  根据关键字的作用,可以将关键字分为数据类型关键字和流程控制关键字两大类。
 

你可能感兴趣的:(编译)