嵌入式C語言自我修養 01:Linux 內核中的GNU C語言語法擴展

来源:https://www.cnblogs.com/wanglitao2019/archive/2019/03/12/10518589.html
-Advertisement-
Play Games

1.1 Linux 內核驅動中的奇怪語法 大家在看一些 GNU 開源軟體,或者閱讀 Linux 內核、驅動源碼時會發現,在 Linux 內核源碼中,有大量的 C 程式看起來“怪怪的”。說它是C語言吧,貌似又跟教材中的寫法不太一樣;說它不是 C 語言呢,但是這些程式確確實實是在一個 C 文件中。此時, ...


1.1 Linux 內核驅動中的奇怪語法

大家在看一些 GNU 開源軟體,或者閱讀 Linux 內核、驅動源碼時會發現,在 Linux 內核源碼中,有大量的 C 程式看起來“怪怪的”。說它是C語言吧,貌似又跟教材中的寫法不太一樣;說它不是 C 語言呢,但是這些程式確確實實是在一個 C 文件中。此時,你肯定懷疑你看到的是一個“假的 C 語言”!

比如,下麵的巨集定義:

#define mult_frac(x, numer, denom)(           \
{                           \
   typeof(x) quot = (x) / (denom);         \
   typeof(x) rem = (x) % (denom);         \
   (quot * (numer)) + ((rem * (numer)) / (denom));   \
}                           \
)
​
#define ftrace_vprintk(fmt, vargs)                   \
do {                                   \
   if (__builtin_constant_p(fmt)) {               \
       static const char *trace_printk_fmt __used     \
     __attribute__((section("__trace_printk_fmt"))) = \
           __builtin_constant_p(fmt) ? fmt : NULL;     \
                                   \
       __ftrace_vbprintk(_THIS_IP_, trace_printk_fmt, vargs); \
   } else                             \
       __ftrace_vprintk(_THIS_IP_, fmt, vargs);       \
} while (0)


字元驅動的填充:

static const struct file_operations lowpan_control_fops = {
  .open        = lowpan_control_open,
  .read        = seq_read,
  .write        = lowpan_control_write,
  .llseek        = seq_lseek,
  .release    = single_release,
  };


內核中實現列印功能的巨集定義:

#define pr_info(fmt, ...)   __pr(__pr_info, fmt, ##__VA_ARGS__)
#define pr_debug(fmt, ...)   __pr(__pr_debug, fmt, ##__VA_ARGS__)


你沒有看錯,這些其實也是 C 語言,但並不是標準的 C 語言語法,而是我們 Linux 內核使用的 GNU C 編譯器擴展的一些 C 語言語法。這些語法在 C 語言教材或資料中一般不會提及,所以你才會似曾相識而又感到陌生,看起來感覺“怪怪的”。我們在做 Linux 驅動開發,或者閱讀 Linux 內核源碼過程中,會經常遇到這些“稀奇古怪”的用法,如果不去瞭解這些特殊語法的具體含義,可能就對代碼的理解造成一定障礙。

本教程,就是帶領大家一起去瞭解 Linux 內核或者 GNU 開源軟體中,常用的一些 C 語言特殊語法擴展,掃除閱讀 Linux 內核或 GNU 開源軟體時,這些擴展特性帶給我們的語法閱讀障礙和困惑。

 

1.2 C 語言標準和編譯器

在進入正式課程之前,先給大家普及一下 C 標準的概念。在學習 C 語言時,大家在教材或資料上,或多或少可能見到過“ANSI C”的字眼。可能當時沒有太在意,其實“ANSI C” 表示的就是 C 語言標準。

什麼是 C 語言標準呢?我們生活的現實世界,就是由各種標準構成的,正是這些標準,我們的社會才會有條不紊的運行。比如我們過馬路,遵循的交通規則就是一個標準:紅燈停,綠燈行,黃燈亮了等一等。當行人和司機都遵循這個預設的標準時,我們的交通系統才會順暢運行。電腦中的 USB 介面也是一種標準,當大家生產的 USB 產品都遵循 USB 協議這種通信標準時,我們的手機、U 盤、USB 攝像頭、USB 網卡才可以在各種電腦設備上互插互拔。2G、3G、4G 也是一種標準,當不同廠家生產的基帶晶元都遵循這種通信標準,我們所用的不同品牌、不同操作系統的手機才可能互相打電話、互相發微信、互相給對方點贊。

同樣,C 語言也有它自己的標準。我們知道,C 語言程式需要通過編譯器,編譯生成二進位指令,才能在我們的電腦上運行。在 C 語言剛發佈的早期,各大編譯器廠商開發自己的編譯器時,各自開發,各自維護,時間久了,就會變得比較混亂。這就會造成這樣一種局面:程式員寫的程式,在一個編譯器上編譯通過,在另一個編譯器編譯通不過。大家按各自的習慣來,誰也不服誰,就像春秋戰國時代:不同的貨幣、不同的度量衡,不同的文字,都是中國人,因為標準不統一,所以交流起來很麻煩,這樣下去也不是辦法啊。

後來 ANSI(AMERICAN NATIONAL STANDARDS INSTITUTE: 美國國家標準協會,簡稱 ANSI)出山了,聯合 ISO(國際化標準組織)召集各個編譯器廠商大佬,各種技術團體,一起喝個茶、開個碰頭會,開始啟動 C 語言的標準化工作。期間各種大佬之間也是矛盾重重,充滿各種爭議,但功夫不負有心人,經過艱難的磋商,終於在1989年達成一致,發佈了 C 語言標準,後來第二年又做了一些改進。於是,就像秦始皇統一六國、統一文字和度量衡一樣,C 語言標準終於問世了!因為是在 1989 年發佈的,所以人們一般稱其為 C89 或 C90 標準,或者叫做 ANSI C。

 

1.3 C 標準內容

C 標準里主要講了什麼?

C 標準英文文檔,洋洋灑灑幾百頁,講了很多東西,但總體歸納起來,主要就是 C 語言編程的一些語法慣例,比如:

  • 定義各種關鍵字、數據類型
  • 定義各種運算規則
  • 各種運算符的優先順序和結合性
  • 數據類型轉換
  • 變數的作用域
  • 函數原型
  • 函數嵌套層數
  • 函數參數個數限制
  • 標準庫函數

C 標準發佈後,大家都遵守這個標準:程式員開發程式時,按照這種標準寫;編譯器廠商開發編譯器時,也按照這種標準去解析、翻譯程式。不同的編譯器廠商支持統一的標準,這樣大家寫的程式,使用不同的編譯器,都可以正確編譯、運行,大大提高程式的開發效率,推動了 IT 行業的發展。

 

1.4 C 標準的發展過程

C 標準並不是永遠不變的,就跟移動通信一樣,也是從 2G、3G、4G 到 5G 不斷發展變化的。C 標準也經歷了下麵四個階段:

  • K&R C
  • ANSI C
  • C99
  • C11

K&R C

K&R C 一般也稱為傳統 C。在 C 標準沒有統一之前,C 語言的作者 Dennis Ritchie 和 Brian Kernighan 合作寫了一本書《C 程式設計語言》。早期程式員編程,這本書可以說是絕對權威。這本書很薄,內容精煉,主要介紹了 C 語言的基本使用方法。後來《C 程式設計語言》第二版問世,做了一些修改:比如新增 unsigned int、long int、struct 等數據類型;把運算符 =+/=- 修改為 +=/-=,避免運算符帶來的一些歧義和 Bug。這本書可以看作是 ANSI 標準的雛形。但早期的 C 語言還是很簡單的,比如還沒有定義標準庫函數、沒有預處理命令等。

ANSI C

ANSI C 是 ANSI(美國國家標準協會)在 K&R C 的基礎上,統一了各大編譯器廠商的不同標準,並對 C 語言語法和特性做了一些擴展,而發佈的一個標準。這個標準一般也叫做 C89/C90,也是目前各種編譯器預設支持的 C 語言標準。ANSI C 主要新增了以下特性:

  • 增加 signed、volatile、const 關鍵字
  • 增加 void* 數據類型
  • 增加預處理器命令
  • 增加寬字元、寬字元串
  • 定義了 C 標準庫
  • ……

C99 標準

C99 標準是 ANSI 1999 年在 C89 標準的基礎上新發佈的一個標準,該標準對 ANSI C 標準做了一些擴充,比如新增一些關鍵字,支持新的數據類型:

  • 布爾型:_Bool
  • 複數:_Complex
  • 虛數:_Imaginary
  • 內聯:inline
  • 指針修飾符:restrict
  • 支持long long、long double數據類型
  • 支持變長數組
  • 允許對結構體特定成員賦值
  • 支持16進位浮點數、float _Complex等數據類型
  • ……

除此之外,C99 標準也借鑒其它語言的一些優點,對語法和函數做了一系列改進,大大方便了程式員開發程式,比如:

  • 變數聲明可以放代碼塊的任何地方。ANSI C 規定變數的聲明要全部寫在函數語句的最前面,否則就會報編譯錯誤。現在不需要這樣寫了,哪裡需要使用變數,在哪裡直接聲明使用即可;
  • 源程式每行最大支持4095個位元組。這個貌似足夠用了,沒有什麼程式能複雜到一行程式有4KB個字元;
  • 支持//單行註釋。ANSI C使用/**/沒有C++的//註釋方便,所以 C99 新標準借鑒過來了,也開始支持這種註釋方式;
  • 標準庫新增了一些頭文件:如 stdbool.h、complex.h、stdarg.h、fenv.h 等。大家在 C 語言中經常返回的 true、false,其實這也是 C++ 裡面定義的 bool 類型。那為什麼我們經常這樣寫,而編器編譯程式時沒有報錯呢,這是因為早期大家編程使用的都是 VC++6.0 系列,是 C++ 編譯器。還有一種可能就是有些 IDE 對這個數據類型的數據做了封裝。

C11 新標準

C11 標準是2011年發佈的最新 C 語言標準,修改了 C 語言標準的一些 Bug、新增了一些特性:

  • 增加 _Noreturn,聲明函數無返回值;
  • 增加_Generic:支持泛型編程;
  • 修改了標準庫函數的一些 Bug:如 gets( )函數被 gets_s() 函數代替;
  • 新增文件鎖功能;
  • 支持多線程;
  • ……

從 C11 標準的修改內容來看,也慢慢察覺到 C 語言未來的發展趨勢:C 語言現在也在借鑒現在編程語言的優點,不斷添加到自己的標準裡面。比如現代編程語言的多線程、字元串、泛型編程等,C 語言最新的標準都支持。但是這樣下去,C 語言是不是還能保持她“簡單就是美”的優雅特色呢,我們只能慢慢期待了。但至少目前我們不用擔心這些,因為 C11 新發佈的標準,目前絕大多數編譯器還不支持,所以我們暫時還用不到。

 

1.5 編譯器對 C 標準的支持

標準是一回事,各種編譯器支不支持是另一回事,這一點,大家要搞清楚。這就跟手機一樣,不同時期發佈的手機對通信標準支持也不一樣。早期的手機可能只支持 2G 通信,後來支持 3G,現在發佈的新款手機基本上都支持 4G了,而且可以相容 2G/3G。

現在 5G 標準正在研發,快發佈了,據說 2019 年發佈,2020 年商用。但是目前還沒有手機支持 5G 通信,就跟現在沒有編譯器支持 C11 標準一樣。

不同編譯器,甚至對 C 標準的支持也不一樣。有的編譯器只支持 ANSI C,這是目前預設的 C 標準。有的編譯器可以支持 C99,或者支持 C99 標準的部分特性。目前對 C99 標準支持最好的是 GNU C 編譯器,據說可以支持 C99標準99%的新增特性。

 

1.6 編譯器對 C 標準的擴展

不同編譯器,出於開發環境、硬體平臺、性能優化的需要,除了支持 C 標準外,還會自己做一些擴展。

在51單片機上用 C 語言開發程式,我們經常使用 Keil for C51 集成開發環境。你會發現 Keil for C51 或其他 IDE 里的 C 編譯器會對 C 語言標準作很多擴展。比如增加各種關鍵字:

  • data:RAM 的低128B空間,單周期直接定址;
  • code:表示程式存儲區;
  • bit:位變數,常用來定義單片機的 P0~P3 管腳;
  • sbit:特殊功能位變數;
  • sfr:特殊功能寄存器;
  • reentrant:重入函數聲明。

如果你在程式中使用以上這些關鍵字,那麼你的程式就只能使用51編譯器來編譯運行,你使用其它的編譯器,比如 VC++6.0,是編譯通不過的。

同樣的道理,GCC 編譯器,也對 C 標準做了很多擴展:

  • 零長度數組
  • 語句表達式
  • 內建函數
  • __attribute__特殊屬性聲明
  • 標號元素
  • case 範圍
  • ...

比如支持零長度數組。這些新增的特性,C 標準目前是不支持的,其它編譯器也不支持。如果你在程式中定義一個零長度數組:

int a[0];
只能使用 GCC 編譯器才能正確編譯,使用 VC++ 6.0編譯器編譯可能就通不過,因為微軟的 C++ 編譯器不支持這個特性。

 

1.7 本教程主要內容

在 GNU 開源軟體、Linux 內核中會大量使用 GCC 自己擴展的語法,這會對我們理解開源軟體、Linux 內核代碼帶來一定障礙和困擾。本教程主要介紹 GNU C 對 C 標準擴展的一些常用語法和使用。終極目標是看懂 Linux 內核驅動、GNU 開源軟體中這些特殊語法的應用,掃除這些特殊語法對我們理解內核代碼帶來的困擾和障礙。

 

1.8 本教程需要的學習環境

在本教程講解中,會使用一些 arm-linux-gnueabi-gcc 等命令用來編譯和反彙編程式。所以在學習本教程之前,確保你的電腦上有如下 Linux 環境或源代碼:

  • Linux學習環境:Ubuntu、Fedora等皆可;
  • arm-linux-gnueabi-gcc 交叉編譯工具;
  • Linux 內核源碼:Linux 4.4.x
  • U-boot-2016.09 源代碼

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • string類型不能被繼承,它是密封類,sealed。 一、字元串的特性。 1、不可變性。 2、字元串池。 二、常用方法。 方法不一一寫出來了。 tringbuilder的使用。 大量拼接字元串的時候用。效率比string高。使用: ...
  • 一. MVC介紹 MVC架構模式有助於實現關註點分離。視圖和控制器均依賴於模型。 但是,模型既不依賴於視圖,也不依賴於控制器。 這是分離的一個關鍵優勢。 這種分離允許模型獨立於可視化展示進行構建和測試。ASP.NET Core MVC 包括以下功能: 路由、模型綁定、模型驗證、依賴關係註入、篩選器、 ...
  • 通過前面的教程學習,你可以實現一個簡單的書籍管理系統。 在本教程將向書籍索引頁中添加分頁功能。 ...
  • 0X00 前言 Newtonsoft.Json,這是一個開源的Json.Net庫,官方地址:https://www.newtonsoft.com/json ,一個讀寫Json效率非常高的.Net庫,在做開發的時候,很多數據交換都是以json格式傳輸的。而使用Json的時候,開發者很多時候會涉及到幾個 ...
  • 我基本上從0開始學習編程,運算符基本上跳過,因為知道了 “=”這個符號相當於賦值,然後“==”才是等於,其他和普通運算符號差不都,也就跳過了。 最基礎的賦值那種,我看了下代碼,似乎沒什麼難度,估計新手和我一樣,有一本書,大概看看就懂了,我從我遇到的問題開始。 我學習時候,發現C#接收用戶輸入的都是字 ...
  • 一 互聯網應用質量概述1.1 互聯網應用質量互聯網應用質量指標——QoE,其主要指標:服務成功率:指用戶所請求的服務成功完成的幾率。服務建立時間:指從服務請求到服務呈現所花費的時間,並且會因為用戶請求服務內容的不同而表現出微妙到秒級的區別。時延:指用戶從發出請求到獲得結果的時間。視聽播放卡頓:指播放... ...
  • 日常工作中我們往往有需要導出當前共用環境或磁碟文件目錄層級結構等的需求,最早在目錄少的情況下我們使用CMD下tree 命令可以很清晰的看到目錄、文件層級結構,那麼我們又如何通過powershell直觀顯示或導出某文件目錄或盤符目錄層級結構呢?DOS下查看目錄、文件結構:tree /?以圖形顯示驅動器... ...
  • 製作這塊51單片機的還是蠻艱辛的,應該是我水平太差,現在這塊51單片機已經穩定了,也把這塊板子製作過程中的一些問題及經驗總結記錄下來。 這塊板子製作出了很大問題很大原因是因為我對Altium Designer16這個軟體的操作不熟悉,畢竟畫這塊板子的很大一部分原因就是為了熟悉這個軟體。 首先是從原理 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...