日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

引言

在學習C語言或者其他編程語言的時候,我們編寫的一個程序代碼,基本都是在屏幕上打印出 hello world ,開始步入編程世(深)界(坑)的。C 語言版本的 hello world 代碼:

#include <stdio.h>

int main()
{
  printf("hello worldn");
  return 0;
}

不用多說,這段程序在運行時,會在顯示終端上打印出 hello world 。

那么,這段程序背后關聯的內容,你是否真正梳理明白了呢?

  • 源程序代碼是如何編譯成可執行程序的?
  • #include<stdio.h> 的作用是什么?
  • hello world 程序是怎樣運行起來的?
  • printf 是怎樣將字符串 "hello world" 輸出到終端的?
  • hello world 程序在運行時,它在內存中是什么樣子的?
  • 程序的執行入口為什么是 main 函數?
  • 可執行文件的內部結構是怎么樣的?

閑話少說,讓我們進入正題,扒一扒 hello world 背后的內幕。

注:本文是在 Ubuntu 環境下對程序的編譯和運行進行實驗,相關內容以 linux 系統為主。

程序編譯

在 Linux 系統或者其他環境下,將源碼編程成可執行程序,很簡單。點擊編譯按鈕或者輸入編譯指令即可完成。例如,在 Linux 下,用 gcc 編譯此程序代碼,然后運行:

$ gcc hello.c -o hello
$ ./hello

hello world

但是,你知道編譯器干了哪些工作嗎?編譯器將源代碼文件編程成可執行程序,經歷了四步:編譯預處理、編譯、匯編、鏈接。

深入理解 C 語言的 hello world

編譯過程

1. 編譯預處理

編譯預處理過程主要是處理源代碼文件中,以 “#” 開頭的預編譯指令。例如,“#inlude”、“#define”等。

預處理器根據以字符 “#” 開頭的指令,修改原始的 C 程序文件,生成一個以 .i 為擴展名的程序文件。

本例中,#include<stdio.h> 命令告訴預處理器,讀取系統頭文件 stdio.h 的內容,并把它插入到源程序文本中。

在 Linux 環境下,可以通過如下指令得到預處理完成后的 .i 文件

$ gcc -E hello.c -o hello.i

這個文件內容比較長,如果有興趣的話可以自己進行實驗,查看一下。

2. 編譯

編譯的過程就是把預處理完的文件,進行一系列的詞法分析、語法分析、語義分析以及優化后,生成相應的匯編代碼文件。這個過程往往是整個程序構建的核心部分。

將 hello.i 文件翻譯成文本文件 hello.s,其內部是一個匯編語言的程序。

通過如下指令可以得到匯編文件

$ gcc -S hello.i -o hello.s

3. 匯編

匯編器將上一步生成的匯編代碼翻譯成機器可以執行的指令,把這些指令打包成可重定位目標程序,保存在目標文件 hello.o 中。

可以通過下邊的指令生成:

$ gcc -c hello.s -o hello.o

文件 hello.o 是一個二進制文件。

4. 鏈接

hello 程序調用了 printf 函數,這是 標準 C 庫中的一個函數。printf 函數存儲在一個預編譯好的目標文件 printf.o 中,鏈接器負責將這個文件以某種方式合并到 hello.o 程序中。

合并處理后,得到一個可執行目標文件 hello,這個可執行文件可以由系統加載運行。

程序運行

hello.c 程序已經被編譯可執行的目標文件 hello,且存在磁盤上。那這個程序是如何運行起來的呢?

當然,你可以說,通過如下指令可以運行程序:

$ ./hello

hello world

但是,從計算機角度來說,運行這個程序需要做哪些工作呢?

當輸入 “./hello” 后,shell 開始處理這條指令。

首先,shell 加載可執行文件 hello,復制目標文件 hello 中的代碼和數據到內存中。

數據和指令加載完成后,處理器開始執行 hello 程序中 main 函數的機器指令。這些指令將 “hello world” 字符串中的字節復制到寄存器文件,再從寄存器文件中復制顯示設備上,最終在屏幕上顯示出來。

深入理解 C 語言的 hello world

程序執行過程

其實,操作系統在加載程序后,還做了一些工作,用于準備 main 函數執行需要的環境,然后調用 main 函數。

可執行程序文件

在 Linux 下,可執行文件的存儲格式為 ELF(Executable Linkable Format)。那么其內部結構是什么樣的呢?

典型的 ELF 可執行文件的布局情況如下:

深入理解 C 語言的 hello world

可執行文件布局

ELF 頭部描述了整個文件的屬性,包括,文件是否可執行、目標硬件、目標操作系統、入口點等信息。

.init 定義了一個小函數,叫做 _init,程序的初始化代碼會調用它。

.text 為已編譯程序的機器代碼。 .rodata 為只讀數據,比如 printf 語句中格式串。.data 為已初始化的全局和靜態 C 變量。

.bss 存放未初始化的全局變量和局部靜態變量,以及所有被初始化為 0 的全局或靜態變量。不占用實際的空間,只是一個占位符。

.symtab 是一個符號表,存放在程序中定義和引用的函數和全局變量的信息。

.debug 一個調試符號表,內部是程序定義的局部變量和類型定義,程序定義和引用的全局變量,以及原始的 C 源文件。

.line 源程序中的行號和 .text 節中機器指令之間的映射。

.strtab 一個字符串表,內容包括 .symtab 和 .debug 節中的符號表,以及節頭部中的節名字。

總體來說,將程序源碼編譯之后生成的目標文件,主要分成兩種段:程序指令和程序數據。代碼段屬于程序指令,數據段和 .bss 段屬于程序數據。

加載可執行程序

可執行程序被加載器加載到內存,即從磁盤內復制可執行文件中的代碼和數據到內存中,然后跳轉到程序的入口點來運行該程序。將程序復制到內存并運行的過程就叫做加載

在 Linux 系統中,每個程序都有一個運行時的內存映像。

深入理解 C 語言的 hello world

程序加載后內存布局

代碼段后邊是數段,運行時,堆在數據段之后,通過調用 malloc 庫向上增長。

用戶棧總是從最大的合法用戶地址開始,向較小內存地址增長。

用戶棧以上的區域,是為內核中的代碼和數據保留的。

程序加載運行時,會創建類似上圖所示的內存映像,在程序頭部的引導下,加載器將可執行文件復制到代碼段和數據段,然后加載器跳轉到程序的入口點。

入口點的函數調用啟動函數,初始化執行環境,然后調用用戶層的 main 函數,處理 main 函數的返回值,并在需要的時候把控制權返回給內核。

main 函數為作為用戶可執行程序的入口,是由系統啟動函數內部定義的。在環境準備好后,調用 main 函數,開始執行用戶程序。

總結

沒想到,這么簡單的程序背后,涉及到這么多知識內容。

  • 源碼文件編譯成可執行文件具體過程。
  • 可執行目標程序加載和執行的詳細過程。
  • 可執行目標文件內部結構布局。
  • 目標文件加載到內存后的布局情況。

分享到:
標簽:語言
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定