首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言進階核心知識體系總覽

     編輯:關於C語言

C語言進階核心知識體系總覽

本文系統梳理 C 語言進階開發必備的核心知識點,涵蓋記憶體模型、位運算、指針精髓、複雜表達式、聚合類型、巨集與預處理、函數庫機制、儲存類別與鏈結屬性、資料結構基礎等模組,旨在為開發者構建清晰的知識地圖。

一、記憶體大話題:程序的立足之地

1.1 物理記憶體組織

記憶體晶片由多個 Bank 組成,每個 Bank 為行列陣列。容量計算:存儲單元數 = 行數 × 列數(位寬) × Bank 數。常用 M×W 表示規格,如 8M×8 表示 8M 單元、8 位寬,總容量 64 Mbit(8 MB)。

1.2 C 語言無原生布林型別

C 語言以 0 表示假、非 0 表示真,儲存時實際佔用 int 或 char 空間。C++ 才引入 bool。

1.3 記憶體對齊

為提高存取效率,編譯器預設 4 位元組對齊(32 位系統)或 8 位元組對齊(64 位系統)。結構體成員、結構體整體、首地址均需滿足對齊要求。可用 #pragma pack(n) 或 GCC 屬性 __attribute__((packed))、__attribute__((aligned(n))) 控制。

1.4 基本型別在記憶體中的長度與解析

char/int/short/long/float/double 佔用不同位元組,同一組位模式可按不同型別解析(如 0000111010101 可作 int 或 float)。

1.5 用結構體模擬面向物件

C 語言是面向過程,但可在結構體內嵌函數指針實現封裝:

struct student {
    int age;
    char *name;
    void (*eat)(void);  // 函數指針
};

1.6 堆疊 深度解析

  • 自動管理:編譯器自動生成入棧/出棧彙編碼,程序員無需干預。
  • 反覆使用:同一塊記憶體區域循環利用,硬體棧指針追蹤棧頂。
  • 髒記憶體:釋放後不清零,殘留舊值。
  • 臨時性:嚴禁返回棧上變量指針,函數返回即失效。
  • 棧溢出:OS 限制棧大小,無限遞歸或大量區域變量會耗盡。
  • 增減棧模式:滿增、滿減、空增、空減四種,由編譯器/架構決定(如 S5PV210 為滿減棧)。

1.7 堆 深度解析

  • OS 堆管理器:按需分配,靈活但需手動 malloc/free。
  • 大塊記憶體池:進程共享,用完必須釋放。
  • 同樣是髒記憶體:釋放前不清零。
  • 生命週期:僅在 malloc 到 free 之間有效。
  • 常用 API:malloc、calloc、realloc、free。分配後必檢查 NULL,釋放後指針置 NULL。
  • realloc 原理:重新分配 → 複製舊數據 → 釋放舊塊 → 返回新指針。
  • malloc(0) 行為:系統會分配最小塊(如 16 或 32 位元組)。

1.8 記憶體區段分佈

區段內容特點
代碼段二進制指令、字串常量、const 全域變量唯讀,不可修改
資料段已初始化非零全域/靜態區域變量讀寫,程式啟動分配,結束釋放
BSS 段未初始化或初始化為 0 的全域/靜態變量載入前不佔空間,啟動時清零
堆動態分配記憶體程式員手動管理,向上增長
棧區域變量、函數參數、返回地址編譯器自動管理,向下增長

const int a=9; 可能存放在代碼段(嵌入式)或資料段(編譯器判斷),具體視平台而定。

1.9 可執行檔案與進程映射

  • 源檔 → 編譯連結 → a.out(含代碼段、資料段、BSS、雜段)。
  • 載入時:雜段丟棄,各段映射到進程虛擬地址空間對應區域。
  • 裸機:工具剝離符號生成 .bin 燒錄,代碼/資料/BSS 連續,無堆、無 malloc。
  • 多進程:各進程獨佔 4GB 虛擬空間,通過頁表(TTB)映射不同物理地址,互不干擾。

二、位運算:底層控制的利器

  • ~(0u) 得到全 1 掩碼。
  • 基本運算:&(與)、|(或)、~(非)、^(異或)。
  • 特性口訣:
    • 與 1 不變,與 0 歸零。
    • 或 1 置一,或 0 不變。
    • 異或 1 翻轉,異或 0 不變。
  • 移位規則:
    • 無符號:左移右補 0,右移左補 0(邏輯移位)。
    • 有符號:左移右補 0,右移左補符號位(算術移位)。
  • 常用技巧:配合 1 進行置位、清位、取反、移位。
  • 巨集範例(最右為第 1 位):
#define SET_NTH_BIT(x, n)   (x | ((1U) << (n-1)))
#define CLEAR_NTH_BIT(x, n) (x & ~((1U) << (n-1)))

三、指針 —— C 語言的靈魂

  • %p 輸出指針地址。
  • int *p1, p2; 僅 p1 是指針,p2 是 int。
  • 規範:定義即初始化為 NULL,使用前檢查;free 後置 NULL。
  • NULL 定義差異:C++ 為 0,C 為 (void*)0。
  • const 修飾指針四種形式:const int *p、int const *p(指向常量)、int * const p(常量指針)、const int * const p(雙重常量)。
  • 陣列名 a 是首元素地址,&a 是整個陣列地址(陣列指針),值相同但類型不同。
  • sizeof 是運算元(編譯期計算大小),strlen 是函數(運行期尋找 \0)。
  • 陣列作參數退化為指針,大小資訊丟失,需額外傳遞長度。
  • 結構體傳參建議用指針,避免大對象拷貝開銷。
  • 函數指針:int (*p)(int, int); p = max; p(); 等同 max()。函數指針陣列 int (*p[4])(int, int);。
  • printf 行緩衝,遇 \n 刷新。\r 回車,\n 換行,Windows \r\n,Unix \n,Mac \r。
  • 頭文件防重複包含:#ifndef _GUARD_ #define _GUARD_ ... #endif。
  • typedef 別名:typedef int *intType; const intType p; 表示 p 為常量指針。跨平台移植常用 int32_t、int16_t 等。
  • 多重指針與多維陣列關係:
    • 一重指針 ↔ 一維陣列
    • 二重指針 ↔ 指針陣列
    • 陣列指針 int (*p)[5] ↔ 二維陣列 int a[2][5]
    • 函數指針 ↔ 普通函數

四、C 語言複雜表達式解析

  • 運算子優先級與結合性決定求值順序。
  • 記憶體地址從低向高增長。
  • int *p; 定義指針;int (*p)[4]; 定義指向 4 元素陣列的指針;int (*p)(int, int); 定義函數指針。見到 (*p) 即為指針變量。

五、聚合類型:陣列、字串、結構體、共用體、枚舉

  • 字串定義三種:char a[]={'l','i','n','u','x','\0'}、char a[]="linux"、char *p="linux"(指向唯讀區)。
  • sizeof 含 \0,strlen 不含。指針 sizeof 永遠是指針大小(4/8 位元組)。
  • 結構體成員存取 . 或 -> 本質是指針運算:s1.a 等價 *(int*)&s1,s1.b 需按位移計算地址。
  • 結構體對齊三原則:首地址對齊、成員對齊、整體對齊。可用 offsetof 宏計算成員偏移:#define offsetof(TYPE, MEMBER) ((int)&(((TYPE*)0)->MEMBER))。
  • container_of 宏:從成員指針反推結構體指針,Linux 核心鏈表核心技術。typeof 取得變量類型。
  • 指針運算:(int)p+6 按 int 大小跳躍,(char*)p+6 按位元組,(int*)p+6 相當於 p+24。
  • 大小端模式:小端低位在低地址,大端高位在低地址。測試用共用體或指針強轉 char* 觀察 int a=1 低位元組。
  • 枚舉本質是 int,預設從 0 遞增,可手動指定值。同編譯單位內枚舉常量名不可重複,typedef 別名也不可衝突。

六、巨集、預處理、函數與函數庫

6.1 編譯流程

.c → (預處理) → .i → (編譯) → .S → (彙編) → .o → (鏈接) → ELF 可執行檔
工具鏈:預處理器、編譯器、彙編器、鏈接器 → GCC 整合所有。
  • 預處理器處理:#include、註釋、條件編譯、巨集展開。
  • GCC 參數:-E 僅預處理、-c 僅編譯、-o 指定輸出、-I 指定頭文件路徑。
  • 鏈接器將目標檔函數有序組合,非簡單拼接。可執行檔含符號表,strip 可剝離生成 Release 版,objcopy 生成 .bin 燒錄映像。

6.2 預處理細節

  • #include "xxx.h":當前目錄優先,再搜尋系統路徑。#include <xxx.h>:僅搜尋系統路徑。-I 指定額外路徑。
  • 條件編譯:#ifdef DEBUG #define debug(x) printf(x) #else #define debug(x) #endif 實現調試開關。

6.3 巨集定義

  • 物件巨集:#define YEAR (365*24*60*60UL) 需加 UL 避免溢位。
  • 函數巨集:參數必須加括號 #define MAX(a,b) ((a)>(b)?(a):(b))。
  • 巨集不佔記憶體、無類型檢查、直接替換;函數有類型檢查、有調用開銷。
  • 內聯函數 inline:結合兩者優點,適合短小函數。

6.4 函數設計原則

  • 單一職責:一個函數只做一件事,長度適中(一屏幕)。
  • 參數不超過 4 個(ARM 呼叫慣例),超過用結構體打包。
  • 少用全域變量,用參數/返回值交互數據。
  • 函數被調用的本質:編譯期看見聲明,而非定義。

6.5 遞歸函數

  • 必須有收斂條件(終止條件),否則棧溢出。
  • 經典例:階乘、費波納契。

6.6 函數庫:靜態 vs 動態

特性靜態庫 (.a)動態庫 (.so)
鏈接時機編譯鏈接期,代碼複製進可執行檔運行期,僅記錄符號引用
記憶體佔用多進程各自擁有副本,佔較大多進程共享同一份記憶體
更新部署需重新編譯鏈接替換 .so 即可,無需重編應用
GCC 預設需 -static 強制預設動態鏈接
  • 製作靜態庫:gcc -c src.c -o src.o; ar -rc libname.a src.o(前綴必為 lib)。
  • 製作動態庫:gcc -fPIC -c src.c -o src.o; gcc -shared -o libname.so src.o。
  • 使用:-lname -L/path -I/path。運行時需設定 LD_LIBRARY_PATH 或複製到 /usr/lib。ldd 檢查依賴。

6.7 常用庫函數

  • string.h:memcpy(不重疊高效)、memmove(重疊安全)、memset、strcpy、strcat、strcmp 等。
  • math.h:需鏈接 -lm。鏈接器預設僅搜尋常用庫,非常用庫需顯式 -lxxx。

七、儲存類、作用域、生命週期、鏈結屬性

關鍵字用途儲存區作用域生命週期鏈結屬性
auto區域變量(可省略)棧塊內函數調用期間無
static1.區域靜態 2.全域/函數限定本檔資料段/BSS1.塊內 2.檔案級程序全程1.無 2.內部
register建議放入寄存器寄存器/棧塊內函數調用期間無
extern聲明外部全域變量/函數資料段/BSS全程序(跨檔)程序全程外部
volatile禁止編譯器優化,硬體/中斷/多執行緒可見視原定義視原定義視原定義視原定義
restrictC99,指針獨佔訪問,允許積極優化視原定義視原定義視原定義視原定義
  • volatile 典型場景:中斷服務程式修改變量、多執行緒共享變量、硬體寄存器映射。寧可誤加,不可漏加。
  • restrict 用於 memcpy 等標準庫原型,告知編譯器源/目標不重疊。

八、雜項深度探討

8.1 作業系統本質

資源管理者(CPU、記憶體、硬體),提供 API 供應用程式申請服務。裸機適合極簡產品,複雜產品必上 OS。

8.2 C 庫函數與 API 關係

內核 API 面向功能完備性,不顧易用性;C 庫封裝 API,優化介面(如 fopen 封裝 open)。純用戶態函數(strcpy、atoi)無需 API。

8.3 跨平台差異與可移植性

不同 OS API 不同,但 C 庫介面相似(人為屏蔽差異)。跨平台框架:Qt、Java。

8.4 main 函數深度解析

  • 標準簽名:int main(int argc, char **argv) 或 int main(int argc, char *argv[])(二者等價)。
  • 返回值傳給父進程(Shell 或啟動器),0 成功,非 0 失敗(可編碼錯誤類型)。
  • 父進程通過 fork/exec 創建子進程執行程式。Shell 執行 ./a.out 實為 Shell 進程 fork 子進程。
  • argc 含程式名(argv[0]),實際參數從 argv[1] 開始。

8.5 void 型別本質

「未知型別」而非「空型別」。void* 是通用指針,指向未知類型對象;void 不能定義變量。C 是強類型語言,所有變量必有明確類型以確定大小與解析方式。

8.6 NULL 與相關概念辨析

符號含義內存值佔用空間
'\0'字串結束符01 位元組
'0'字符 '0'48 (ASCII)1 位元組
0整數 00int 大小
NULL(void*)0,空指針0 (地址)指針大小

規範用法:int *p = NULL; if (p != NULL) *p = ...;

8.7 匿名臨時變量

複雜表達式中編譯器隱式創建臨時變量保存中間結果(如 (int)a、b/3),用完即銷毀。

8.8 除錯巨集演進

  • 學習級:#define debug(x) printf(x) 配合 DEBUG 開關。
  • 應用級:#define DBG(...) fprintf(stderr, "DBG(%s, %s(), %d): ", __FILE__, __FUNCTION__, __LINE__); fprintf(stderr, __VA_ARGS__) 含檔案、函數、行號、變參、錯誤流輸出(重定向不影響)。
  • 核心級:#define DEBUG(fmt, args...) printk(fmt, ##args)。

九、資料結構基礎:鏈表、狀態機、多執行緒

9.1 單鏈表

  • 節點 = 數據域 + next 指針。必須用堆記憶體(malloc),使用前清理髒數據。
  • 修改頭指針需傳遞指針的指針(**head)或返回新頭指針。
  • scanf 讀取結構體成員需取地址 &(s->age)。
  • 字串成員建議用定長陣列 char name[10],避免指向唯讀區導致段錯誤。
  • 頭節點可存長度等元信息,鏈表可無頭節點但不可無頭指針。
  • 刪除節點必須 free() 釋放記憶體。
  • 逆序操作:三指針法(prev、curr、next)。
  • 優點:動態擴容、利用碎片記憶體。缺點:單向遍歷、隨機存取慢、插入刪除需前驅節點。

9.2 核心鏈表思想

  • 分離「純鏈結結構」與「數據結構」:struct list_head { struct list_head *next, *prev; }; 嵌入業務結構體,配合 container_of 宏存取數據。
  • 內核提供 list_add 等無雙下劃線介面,雙下劃線為內部實現勿用。
  • 預設頭指針 + 頭節點模式。

9.3 狀態機

  • Moore 型:輸出僅依賴當前狀態。
  • Mealy 型:輸出依賴當前狀態與輸入信號。
  • 實現:while(1) { switch(state) { case S1: ...; if(input) state=S2; break; ... } }。

十、補充知識點

  • 字元集:1 位元組 256 碼位,0-127 ASCII,128-255 西歐擴展。char 相加自動提升為 int。
  • signed char (-128~127) vs unsigned char (0~255),記憶體位模式相同,解析不同。
  • 鏈接地址必要性:編譯期生成絕對地址指令(如 ldr r0, 0x12345678),運行時需重定位。
  • printf 可變參數實現依賴棧幀佈局與 va_list 機制。
  • ARM 交叉編譯器自帶庫(printf、malloc),但裸機無標準輸出設備、無堆定義,部分需移植或重寫。
  • BSS 段清零:編譯器通常處理,但重定位階段可能需手動清零。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved