程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C的日記-編譯和執行,日記編譯

C的日記-編譯和執行,日記編譯

編輯:關於C語言

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的日記-靜態和動態鏈接】

 

-----------------------------我們來總結下(靜態鏈接和動態鏈接只能存在一種)---------------------------------

 

【編譯】
   編譯系統(不同系統上不同)是把源代碼(不同系統上相同)->通過編譯先轉換成目標程序(.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
  根據關鍵字的作用,可以將關鍵字分為數據類型關鍵字和流程控制關鍵字兩大類。
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved