編譯四階段| 直譯語言 | 陣列概念 | 除錯三妙招

更新 發佈閱讀 2 分鐘
程式並不是寫完就可以自動運行,就跟不是買了水龍頭回家就有水的意思一樣。部分的程式語言還需要經過「編譯」的階段,轉成 0 和 1,本篇會講解與示範編譯的四個階段。另外帶入「陣列」的觀念,讓你了解資料儲存結構的形式,再分享三個除錯的方法。

真正的 C 語言運行需要四個編譯階段,第一篇提到的 make 檔名 是在 CS50 的平台中只是其中方便初學者理解有編譯這個過程存在,真正的編譯需要有四個階段。下面是這四個階段的簡單說明:

編譯四階段總覽

  1. Preprocessing

這個階段會處理程式中的指令,比如 #include,用來引入外部函式庫。編譯器會找到這些函式庫並將內容加入程式,確保函式庫功能可以被正確編譯。

  1. Compling

編譯成 Assembly language ,更難直觀看懂的語言。不同的編譯器適用不同的指令,Linux C 語言常用 gcc 當作編譯器。在 CS50 的編譯器中使用 Clang 編譯

  1. Assembling

轉成電腦能看懂的 0 和 1,每個標頭檔函示庫的功能會個別編譯。

  1. Linking

將個別已轉成 0 和 1 的標頭檔內容連結,讓程式可運行。

Preprocessing

這次我們改用 clang 指令進行編譯:

使用 clang 編譯

使用 clang 編譯

左邊出現 ./a.out 的預設檔名編譯檔案

左邊出現 ./a.out 的預設檔名編譯檔案

此時檔名尚未更新,需要運行./a.out的預設檔名才有結果

此時檔名尚未更新,需要運行./a.out的預設檔名才有結果

如何自訂檔名?

clang -o 自定義檔名 檔案名稱.c

raw-image
顯示錯誤,原因是沒有編譯到 <cs50.h> 這個函式庫的功能,電腦就不知道 get_string 是什麼意思。

顯示錯誤,原因是沒有編譯到 <cs50.h> 這個函式庫的功能,電腦就不知道 get_string 是什麼意思。

如果程式有使用其他的 library ,就要另外編譯。

clang -o 自定義檔名 檔案名稱.c -l函式庫

raw-image

補充說明:gcc 也是一種編譯模式,你也可以試試。

gcc 是 C 語言常用的編譯方式

gcc 是 C 語言常用的編譯方式


Compling 編譯

編譯器將 C 語言的原始碼轉換為組合語言(assembly language),這是比 C 語言更接近電腦用語的語言。不同的編譯器,組合語言的指令可能有所不同。以下的組合語言由 ChatGPT 提供,一般人類很難直接寫出組合語言。

section .data
hello db 'Hello, World!', 0xA ; The string to print (0xA is a newline character)

section .text
global _start ; Entry point for the program

_start:
; System call to write (sys_write)
mov rax, 1 ; System call number for sys_write (1)
mov rdi, 1 ; File descriptor (1 = stdout)
mov rsi, hello ; Pointer to the string
mov rdx, 13 ; Length of the string
syscall ; Call kernel

; System call to exit (sys_exit)
mov rax, 60 ; System call number for sys_exit (60)
xor rdi, rdi ; Exit code 0
syscall ; Call kernel

Assembling

轉成 0 和 1 的二進位語言,即機器語言(machine code)。

10111000 00000001 00000000 00000000 00000000  //節錄 hello,world 部分machine code

Linking

將多個機器語言輸入:

  • main.o(主程式的機器語言)。
  • 標頭檔中 printf 函式所屬的函式庫機器語言。
  • 如果有 <cs50.h>get_string 功能也會在這個階段連結成完整的程式。

每個程式語言都要編譯嗎?

不一定。世界上也有「直譯語言」,也有介於兩者中間的型態。

Compiled Language 編譯語言:

知名的 C 語言 C++ 都屬於編譯語言,編譯語言的概念如下圖:

raw-image

左邊是你的電腦,右邊是我的電腦,而編譯語言會先將程式碼編譯成0和1並儲存成「新的檔案」,再把這個檔案給我的電腦執行。此圖片截圖自,影片版請點以下連結:

raw-image

Compiler and Interpreter: Compiled Language vs Interpreted Programming Languages



Interpreted Language 直譯語言:

網頁常用的 JavaScript 就屬於直譯語言。

raw-image

直譯語言直接把原始的程式碼,傳送到我的電腦,直接在我的電腦逐條直譯。相較於編譯語言,直譯語言需要給對方「原始程式碼檔案」。


raw-image






兩者比較:

raw-image

稍微了解編譯的過程後,我們來看另一個常用的功能:陣列

沒有陣列的程式

老師希望計算每個學生的平均成績,如果學生人數不多,可能會這樣寫:

#include <stdio.h>

int main()
{
int score1 = 72; // 定義每個變數表示一個成績
int score2 = 73;
int score3 = 33;
printf("Average: %f\n", (score1 + score2 + score3) / 3.0); // 計算平均分數
}

問題: 這個方法可以處理少數學生的成績,但如果有 100 位學生,這樣寫程式就非常不方便,因為你需要定義 100 個變數,並在計算平均時把它們一個個相加。萬一第 50 位學生轉學,你還必須手動去修改變數名稱,甚至重寫部分程式碼。

此外,若還需要儲存學生的姓名、學號等資訊,這樣的寫法會讓程式變得更複雜且難以維護。因此,我們需要使用「陣列」來簡化這些操作。

陣列(Array)是什麼?

每個數值都需要占用一些儲存空間,而這個空間的大小取決於數值的型態(例如,整數、浮點數等)。當我們使用陣列時,電腦可以根據每個數值的位置快速找到它,而不是從頭一個一個地搜尋。

可以把陣列想像成一列火車,每個「車廂」就是一個存放數值的位置。我們提前規劃好車廂的位置,這樣當我們需要找到某個特定車廂(數值)時,不需要從火車頭開始搜尋,而是可以直接跳到那個車廂去取東西,而這展現陣列的高效率。

記憶體在硬體上呈現的樣子 ( 本圖截圖自 CS50 Lecture 2)

記憶體在硬體上呈現的樣子 ( 本圖截圖自 CS50 Lecture 2)


raw-image


raw-image
#include <stdio.h>

int main()
{
int score[3]; // 我需要有存放3個數值的空間
int score[0] = 72; // 注意第一個位置是0
int score[1] = 73;
int score[2] = 33;
printf("Average:%f\n", (score[0] + score[1] + score[2]) / 3.0);
}
  • 首先,告訴陣列需要為你準備多少空間。陣列中的每個位置都可以存放一個成績,使用 score[3] 表示我們需要 3 個整數的空間,並且它們的索引(位置)會是 0 到 2。
  • 需要注意的是,陣列的索引是從 0 開始的,所以即使陣列可以存 3 個值,它們的索引分別是 012

優化程式,使用迴圈:

如果我們有 100 位學生,老師不可能手動為每個學生輸入成績 100 次。所以,當我們發現有重複行為時,應該想到使用「迴圈」來自動化這些操作。這樣可以減少複製貼上,讓程式更有效率。

#include <stdio.h>
#include <cs50.h>

int main()
{
int score[3]; // 我需要有存放3個數值的空間
int score[0] = get_int("Score: ");
for(int i = 0; i < 3; i++)
{
score[i] = get_int("Score: ");//請使用者輸入分數,分數會存入記憶體
}
printf("Average:%f\n", (score[0] + score[1] + score[2]) / 3.0);
}

優化程式,使用變數:

當你發現有「固定數字」重複出現在程式裡(例如 int score[3];(score[0] + score[1] + score[2]) / 3.0);),代表可以使用變數來替代。這樣當學生人數改變時,只需要改變一個地方,程式就能處理不同的人數。

#include <stdio.h>
#include <cs50.h>

int main()
{
const int N = 3; // 指定 N 為常數,表示這裡 N 只能等於 3,無法更改
int score[N]; // 使用 N 來宣告一個大小為 N 的陣列,儲存成績
// 使用迴圈讓使用者輸入 N 個學生的成績
for(int i = 0; i < N; i++)
{
score[i] = get_int("Score: "); // 使用者輸入的成績依次存入陣列中
}
// 計算並輸出成績的平均值
printf("Average: %f\n", (score[0] + score[1] + score[2]) / (float) N);
// 用 (float) 將 N 轉成浮點數,確保結果是小數
}

使用 const 宣告常數,使學生人數固定

在 C 語言中,我們可以使用 const 關鍵字來宣告一個常數,這樣變數的值在程式運行期間不能被修改。適合用在不能被改變的數值,像是固定的學生人數、圓周率等等。在專案協作中,使用 const 有助於工程師溝通時了解這是不能被改變的數值,增加程式易讀性和安全性。

不使用固定常數,讓老師自行輸入

以下提供,不固定學生人數的寫法 (節錄版):

#include <stdio.h>
#include <cs50.h>

int main()
{
int n = get_int("Number of students: "); // 讓使用者輸入學生人數
int score[n]; // 根據使用者輸入的學生人數來設定陣列大小

最後還是有 score[0] + score[1] + score[2] 更聰明的寫法如下:

  • 寫一個函數計算功能:透過將計算邏輯封裝在 average 函數中。
  • 迴圈計算總合。
#include <cs50.h>
#include <stdio.h>

const int N = 3; // 適用於全域,其他程式編輯者可在一開始改變陣列長度
float average(int length, int array[]);//prototype宣告等一下使用此函數

int main()
{
int scores[N]; // 我需要有存放N個數值的空間
for (int i = 0; i < N; i++)
{
scores[i] = get_int("Score: "); // 請使用者輸入分數,分數會存入記憶體
}
printf("Average:%f\n", average(N, scores)); // float使 N 轉浮點數
}

float average(int length, int array[])// 定義新函數,平均的算法。
{
int sum = 0;
for (int i = 0; i < length; i++)
{
sum = array[i] + sum;
}
return sum / (float) length; // 變浮點數,平均才會有小數
}



鏈結串列(Linked List):

另外一種資料結構是鏈結串列,儲存的資料不必是連續的,電腦要找資料必須從第一個開始查找。鏈結串列會談到指標(Pointer)的概念,之後的章節會再深入探討。

raw-image



除錯 (debug)三妙招

  1. 邊打邊念,或是先寫下思考邏輯。

範例:計算 5 位學生的成績總和。

  • 寫下一般數學運算總和如何表示,再一邊寫程式一邊模擬教學講出此程式碼代表的意義。
  1. 善用 printf() 打印出每一行程式,觀察變化。

範例:寫 1 加到 50 ,發現輸出錯誤,可以在中間使用printf() 檢查每一次的輸出。

raw-image
  1. 外掛程式偵錯。在 cs50 中可以使用 debug50 ./檔名 來偵錯,而其他的介面平台也會有適合的外掛程式。





留言
avatar-img
留言分享你的想法!
avatar-img
越南放大鏡 X 下班資工系
46會員
92內容數
雙重身份:越南放大鏡 X 下班資工系 政大東南亞語言學系是我接觸越南語的起點,畢業後找越南外派工作的生活跟資訊時,發現幾乎都是清單式的分享,很難身歷其境。所以我希望「越南放大鏡」可以帶讀者看到更多細節和深入的觀察。 - 下班資工系則是自學資工系的課程內容,記錄實際操作的過程,學習理論的過程。希望可以跟讀者一起成長。
2025/04/24
本系列文章將循序漸進地介紹 JavaScript 的核心概念,從基礎語法到進階應用,例如非同步程式設計和 React 基礎。內容淺顯易懂,並使用生活化的比喻幫助讀者理解,搭配程式碼範例,適合 JavaScript 初學者學習。
Thumbnail
2025/04/24
本系列文章將循序漸進地介紹 JavaScript 的核心概念,從基礎語法到進階應用,例如非同步程式設計和 React 基礎。內容淺顯易懂,並使用生活化的比喻幫助讀者理解,搭配程式碼範例,適合 JavaScript 初學者學習。
Thumbnail
2025/04/21
本文介紹行動通訊網路的演進歷史,從1G到5G,並說明ITU與3GPP在制定通訊規格上的重要角色,以及5G的三大關鍵應用場景:URLLC、eMBB和mMTC。
Thumbnail
2025/04/21
本文介紹行動通訊網路的演進歷史,從1G到5G,並說明ITU與3GPP在制定通訊規格上的重要角色,以及5G的三大關鍵應用場景:URLLC、eMBB和mMTC。
Thumbnail
2025/04/11
這篇文章說明網路的七層模型、IP 位址、通訊埠、TCP/UDP 協定、HTTP 協定、HTTP 狀態碼以及 WebSocket,並解釋它們之間的關係與互動方式。文中包含許多圖表和範例,幫助讀者理解這些網路概念。
Thumbnail
2025/04/11
這篇文章說明網路的七層模型、IP 位址、通訊埠、TCP/UDP 協定、HTTP 協定、HTTP 狀態碼以及 WebSocket,並解釋它們之間的關係與互動方式。文中包含許多圖表和範例,幫助讀者理解這些網路概念。
Thumbnail
看更多
你可能也想看
Thumbnail
在小小的租屋房間裡,透過蝦皮購物平臺採購各種黏土、模型、美甲材料等創作素材,打造專屬黏土小宇宙的療癒過程。文中分享多個蝦皮挖寶地圖,並推薦蝦皮分潤計畫。
Thumbnail
在小小的租屋房間裡,透過蝦皮購物平臺採購各種黏土、模型、美甲材料等創作素材,打造專屬黏土小宇宙的療癒過程。文中分享多個蝦皮挖寶地圖,並推薦蝦皮分潤計畫。
Thumbnail
小蝸和小豬因購物習慣不同常起衝突,直到發現蝦皮分潤計畫,讓小豬的購物愛好產生價值,也讓小蝸開始欣賞另一半的興趣。想增加收入或改善伴侶間的購物觀念差異?讓蝦皮分潤計畫成為你們的神隊友吧!
Thumbnail
小蝸和小豬因購物習慣不同常起衝突,直到發現蝦皮分潤計畫,讓小豬的購物愛好產生價值,也讓小蝸開始欣賞另一半的興趣。想增加收入或改善伴侶間的購物觀念差異?讓蝦皮分潤計畫成為你們的神隊友吧!
Thumbnail
在這篇文章中,我們將介紹工作與以前念書時期在開發流程上的差異,並深入瞭解CI/CD、Travis CI以及加解密的應用。 CI/CD是自動化的軟體開發實踐,而加解密則是保護機密資料安全的重要技術。
Thumbnail
在這篇文章中,我們將介紹工作與以前念書時期在開發流程上的差異,並深入瞭解CI/CD、Travis CI以及加解密的應用。 CI/CD是自動化的軟體開發實踐,而加解密則是保護機密資料安全的重要技術。
Thumbnail
C#程式由一或多個檔案組成,包含命名空間、類別、結構、介面、列舉和委派等型別。Main方法是C#應用程式的進入點。在C#中,註解用於在程式碼中添加說明,有單行和多行兩種類型。變數的定義需要指定變數的類型和名稱,可以一次為多個變數賦值。
Thumbnail
C#程式由一或多個檔案組成,包含命名空間、類別、結構、介面、列舉和委派等型別。Main方法是C#應用程式的進入點。在C#中,註解用於在程式碼中添加說明,有單行和多行兩種類型。變數的定義需要指定變數的類型和名稱,可以一次為多個變數賦值。
Thumbnail
在物件導向程式設計的進階階段,學生將學習繼承、介面、抽象類別等核心概念。繼承允許類別共享屬性和方法,介面確保實現類別提供特定的方法實現,而抽象類別定義了基本結構供子類別擴展。這些知識點有助於提升程式碼的重用性、擴展性和維護性。
Thumbnail
在物件導向程式設計的進階階段,學生將學習繼承、介面、抽象類別等核心概念。繼承允許類別共享屬性和方法,介面確保實現類別提供特定的方法實現,而抽象類別定義了基本結構供子類別擴展。這些知識點有助於提升程式碼的重用性、擴展性和維護性。
Thumbnail
列出一套完整的程式 程式設計有許多種方法,不過通常會先列出清單的再逐一執行,這樣會加快程式設計的速度。設計通常會採取順推的辦法。所以順推的程式設計方式就是經歷觀念溝通、系統分析、資料統合、權限管理、頻率與時間、後台管理、畫面設計等等階段後,將框架設計完了以後,先列出一套完整的程式,將所有使用者都確
Thumbnail
列出一套完整的程式 程式設計有許多種方法,不過通常會先列出清單的再逐一執行,這樣會加快程式設計的速度。設計通常會採取順推的辦法。所以順推的程式設計方式就是經歷觀念溝通、系統分析、資料統合、權限管理、頻率與時間、後台管理、畫面設計等等階段後,將框架設計完了以後,先列出一套完整的程式,將所有使用者都確
Thumbnail
介紹C++ 語法 資料型態,架構說明 程式語言為人類與電腦溝通的工具 程式設計流程: 定義問題 -> 問題分析 -> 撰寫演算法 ->程式撰寫 -> 程式執行及維護
Thumbnail
介紹C++ 語法 資料型態,架構說明 程式語言為人類與電腦溝通的工具 程式設計流程: 定義問題 -> 問題分析 -> 撰寫演算法 ->程式撰寫 -> 程式執行及維護
Thumbnail
每個專案開發,都是由多個工程師來完成,就算只有一個人,隨著專案增量,你便會與過去的你面對面,這時候程式碼的可讀性高低就會成為左右你開發效率的一塊石頭,這篇就來說說幾個程式碼管理的小撇步
Thumbnail
每個專案開發,都是由多個工程師來完成,就算只有一個人,隨著專案增量,你便會與過去的你面對面,這時候程式碼的可讀性高低就會成為左右你開發效率的一塊石頭,這篇就來說說幾個程式碼管理的小撇步
Thumbnail
這邊要來跟大家介紹C#,使用的軟體會是Visual Studio 2017版本 介紹內容架構可以分六大階段:入門級、基礎級、中低階級、中階級、中高階級、高階級 在正式開始寫程式前,要先了解一些基本概念哦! 就如同學中文前要先了解ㄅㄆㄇㄈ或羅馬拼音之類的,程式也是一樣道理,先把基本概念了解後再來開始
Thumbnail
這邊要來跟大家介紹C#,使用的軟體會是Visual Studio 2017版本 介紹內容架構可以分六大階段:入門級、基礎級、中低階級、中階級、中高階級、高階級 在正式開始寫程式前,要先了解一些基本概念哦! 就如同學中文前要先了解ㄅㄆㄇㄈ或羅馬拼音之類的,程式也是一樣道理,先把基本概念了解後再來開始
Thumbnail
本篇文章將會介紹C#的基礎構成和字串的使用,若想要更詳細的教學,可以參考前一篇文章,前往Microsoft官方教學學習。
Thumbnail
本篇文章將會介紹C#的基礎構成和字串的使用,若想要更詳細的教學,可以參考前一篇文章,前往Microsoft官方教學學習。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News