摘要:已有前人提出用C語言實現面向對象的方案,但它們都需要開發者人為提供支持面向對象的C語言框架,增加了開發者的負擔。該方案用類C++語法編寫初始代碼,享受面向對象特性,而內部把初始代碼自動轉換成為支持面向對象特性的C代碼,實現了面向對象理念和C語言的無縫對接。文章解析了用C語言實現面向對象的機制及代碼轉換機制,對比該方案和C++編程的效果,證實該方案的優越性。
關鍵詞:面向對象;代碼轉換;C/C++
中圖分類號:TP311文獻標識碼:A文章編號:1009-3044(2008)35-2181-03
An Automated Solution to Develop Object-Oriented C Program
RAO Jun-wen, ZHU Hong-ming
(Software School, Tongji University, Shanghai 201804, China)
Abstract: There are already solutions to make C language support Object-Oriented feature, but in those solutions the code framework supporting that feature needs to be provided by developers. In this solution codes will be written in C++-like grammar, while inside codes will be converted into Object-Oriented C language code, so no gap between Object-Oriented feature and C language exists. This paper analyzed the mechanisms of supporting Object-Oriented feature by using C language and codes converting, and then compared performance of this solution to that of using C++, so its advantage would be proved.
Key words: Object Oriented; codes converting; C/C++
1 方案概述
1.1 當前情況
嵌入式系統是當前最熱門最有發展前景的IT應用領域之一,然而不得不承認的是,由于嵌入式領域硬件資源的限制,絕大多數的嵌入式軟件還是用C語言或匯編來實現的,這一現狀極大影響了開發人員的工作效率和質量。眾所周之,C語言是一種不直接支持面向對象特性的語言,相對于C++等語言來說,其代碼編寫效率低,組織層次紛亂,封裝性差,不利于代碼重用。這樣的代碼,可讀性差,維護難度也很大。如果為避免這種情況而采用C++等支持面向對象特性的編程語言又會顧此失彼。C++雖然支持面向對象特性,但它所具有的其他特性,如虛函數、多重繼承、模板、異常、RTTI等,增加了代碼的復雜性,使其占用更多硬件資源,降低代碼運行的效率,這些都會極大的影響嵌入式系統的性能和成本。因此,在實際應用中使用C++開發嵌入式軟件的情況也較少。
下面本文將介紹一種不依賴編譯器的編程方案,它既具有C語言的執行效率,又支持面向對象特性。
1.2 特點
享受基本的面向對象特性,同時享受C語言的效率。
1) 本方案支持“類”的概念,包括私有成員變量和成員函數,并支持成員函數繼承和多態。
2) 類的定義是通過特殊的hpp頭文件來實現的,這個頭文件的編寫格式和C++基本相同。
3) 方案的重要部分是一個自動代碼轉換工具,它負責將hpp頭文件轉換成純C代碼。最終參加編譯的代碼都是純C代碼,因此,這個方案具有C語言所具有的代碼容量小和執行效率高等特點。
4) 方案內部使用C代碼框架實現類封裝,繼承和多態。
1.3 開發主要流程
(注:本文使用“demoClass”代表用戶定義的某一個“類”。“parentClass”代表這個類的父類。)
1) 編寫頭文件demoClass.hpp和源文件demoClass.c;
2) 轉換hpp頭文件代碼,生成中間C框架文件;
3) 編譯C文件,生成二進制文件。
2 方案設計
2.1框架
demoClass.hpp:頭文件,用于存放類的定義信息。
demoClass.c:源文件,用于存放類成員函數的實現。
demoClass.h:公共頭文件,向外提供函數接口和數據定義。
demoClass_priv.h:私有頭文件,僅供類內部的源文件使用,存放私有數據成員的定義及私有成員函數的聲明。
demoClass_info.c:源文件,用于存放類的信息結構體,以及類的構造函數定義。
testMain.c:測試文件,使用類的對象實例完成特定功能。
2.2源代碼編寫結構設計
2.2.1頭文件 .hpp VS C++頭文件.h
1) 所有public函數須都是虛函數,除了構造和析構函數。
2) 只支持private數據成員。
3) 與本類虛函數對應的實體函數在private下聲明。
4) 用以重載父類函數的實體函數在private下聲明。
2.2.2源文件 .c VS .cpp
1) 添加#include \"demoClass_priv.h\"私有成員內容。
2) 對象初始化函數和析構函數:
demoClassInit():對象初始化函數,各函數指針初始化。
demoClassDelete():析構函數,完成“析構”動作。
3) this: 成員函數指針;thisd: 數據成員指針
2.2.3 代碼范例
2.3 中間c代碼設計及其機制
代碼轉換工具將通過給予的hpp文件,自動生成C語言代碼框架,由他們實現各項面向對象的特性。
2.3.1 類結構
經過轉換后的C代碼分別使用兩個結構體存儲類的數據如圖2、圖3。
1) 使用demoClass 結構體存儲函數指針集以及私有數據成員結構體對象的地址。
Typedef struct {
void * pData;
errCode ( *Delete )(CMPHANDLE);
errCode ( *virtualFunc_parent1 )(CMPHANDLE);
errCode ( *virtualFunc_demo1 )(CMPHANDLE);} demoClass;
在成員函數內部,“this”指針指向demoClass對象。而thisd 的內容就是 this->pData。
2) 使用demoClass_data結構體存儲私有數據成員,由于其定義只在demoClass.c中,它并不暴露給外部類。
Typedef struct {
int privParentData1;
int privDemoData1;} demoClass_data;
雖然在對象demoClass中保存了對象demoClass_data的地址,但由于后者的定義并未公開,外部并不知道這個地址空間存儲了哪些數據,而成員函數是在demoClass.c中定義的,他們是“看”的見這張定義表的,所以只有本類的成員函數知道如何“按圖索驥”,這從一定程度上實現了隱藏類的私有成員。
3) 使用結構體ClassInfo的對象來存儲每一個類的信息,包括類名,父類ClassInfo對象指針,demoClass和demoClass_data結構體大小。每個類對應的ClassInfo對象是這個類唯一的全局變量,任何時候用戶可以調用類對應的“構造函數”制造出一個對象實例。
4) “this”和thisd指針
為了能使類的成員函數訪問本類的其他成員函數以及本類的私有數據,成員函數必須知道本對象的地址和數據成員地址,在C++中,這些是由C++編譯器內部提供this指針來實現的;在本方案中,每個成員函數都以第一個參數作為一個固定的輸入參數,來獲得本對象的地址,進而獲得數據成員的地址。this是demoClass型指針,thisd是demoClass_data型指針,它們指向各自的結構體對象。
5) 類的創建和消亡
① “構造函數”demoClassNew()
demoClassNew()是這個對象唯一暴露在外的全局函數。用戶可以通過調用它來創建對象。
demoClass* demoClassNew()
{demoClass* h= (demoClass*)ObjCreateAlloc(demoClassInfo);
demoClassInit(h);
return h;}
a. 所有的類都通過調用ObjCreateAlloc (ClassInfo *)來實際創建對象,它依據給定的ClassInfo數據,獲得demoClass和demoClass?_data的結構體大小,分別給它們分配內存空間,并返回demoClass地址,而demoClass_data的地址則保存在demoClass之中。
ObjCreateAlloc()函數將在后文中介紹。
b. 類被創建之后,由demoClassInit()來進行初始化,基本的動作包括函數指針和成員變量的初始化,還可以有用戶自定義的動作。
② “析構函數”負責清理用完的數據或內存,并最終釋放對象自己的內存空間。
2.3.2 繼承
1) 派生類的數據繼承:成員函數指針和成員變量
派生類通過照搬基類數據的方式來實現函數和數據繼承。這種照搬的工作是由代碼轉換工具依據繼承關系完成的。在生成的C代碼中,用于存儲派生類數據的結構體demoClass,其前端數據和基類結構體parentClass的數據完全一 致 ,新 增 加 的 函數指針緊接其后。demoClass_data新增加的成員變量也同理。這樣,派生類就完全包含了一份與基類一致的公有函數指針和數據拷貝。當然,這些函數指針可能指向與基類不同的函數實體,其數據成員也可能存儲不同的內容,這取決于構造函數的實現。
2) 構造和析構函數的繼承
派生類的構造函數在自己的初始化函數開頭,先調用基類的初始化函數,它負責初始化繼承自基類的成員,在這個階段,派生類被裝扮成基類的樣子。然后派生類執行自己的初始化動作,在這個階段,派生類給新增加的函數成員和數據成員賦值,也可以修改來自基類的成員。
派生類的析構函數也分成兩部分,先清理自己新增加的成員,然后在末尾調用基類的析構函數,來清理來自基類的成員,最終釋放自己占用的內存。
3) 成員函數的繼承
與上節類似,被重載的成員函數,其新的函數實體可以包含對基類成員函數的調用,也可以不調用,完全重寫。
2.3.3 封裝
1) 通過以上的結構設計,C語言中間代碼實現了封裝性:
任何一個對象創建之后,只能通過這個對象的句柄訪問其公開的函數接口,外界看不見內部的數據,也無法通過合法的手段直接操作它們。而在對象創建之前,與這個類相關的全局元素只有兩個:全局變量demoClassInfo和全局函數demoClassNew( )。
2) 公共根類
在這套方案中,任何一個類的對象,其創建和消亡都會涉及到這個類基礎數據的處理,包括內存的分配和內部數據結構的初始化,這些是中間代碼的內容,用戶應無需關心它們,因此我們建立一個公共的虛基類rootObj,用它來封裝這些內部的操作。用戶如果要建立新的基類customClass,只需繼承這個虛基類,填寫好它對應的初始化函數customInit( )和析構函數customDelete()。根據代碼的繼承機制,新的類會遞歸調用對應的構造、析構函數,最終調用到虛基類rootObj的構造、析構函數,從而最終完成類的創建和消亡。
① 前面已經提到,類的構造函數demoClassNew()分成ObjCreateAlloc()和demoClassInit()兩個部分。
ObjCreateAlloc()函數把所有的類都當作虛基類來創建,包括給兩個結構體分配內存,并初始化pData指針。
CMPHANDLE ObjCreateAlloc(const ClassInfo* ci)
{rootObj* this;
rootObj_data* thisd;
this=malloc(ci->isize); //給demoClass分配內存
//給demoClass_data分配內存并把地址保存在pData中
this->pData = malloc(ci->dsize) ;
return this;}
② 虛基類的析構函數,負責釋放類結構所占用的內存
errCode rootObjDelete(CMPHANDLE h)
{ THIS_PTR_DEF;
free(h);
return SUCCESS;}
2.3.4 多態
本方案把所有成員函數都設置成虛函數,在內部通過函數指針的方式來實現。當派生對象要重載基類對象的某個成員函數的時候,就把對應的函數指針指向新的函數實現實體,這樣,一個同名的成員函數,根據類的不同完成著不同的操作,從而實現了多態。
2.4 代碼轉換工具的工作
總結如下:根據給予的demoClass.hpp文件,
1) 生成demoClass.h文件:
① include 父類的頭文件parentClass.h。
② 生成demoClass結構體,其中,把所有的成員函數,轉換成同名的函數指針,如果有父類,則把父類的成員函數指針放在開頭。
③ 聲明demoClassInfo變量和demoClassNew()函數。
2) 生成demoClass_priv.h文件:
① include 父類的頭文件parentClass.h。
② 生成demoClass_data結構體,其成員就是類的所有私有成員,如果有父類,則把父類的成員變量放在開頭。
③ 聲明父類的初始化和析構函數,及成員實體函數。
④ 聲明本類的初始化函數和成員實體函數。
3) 生成demoClass_info.c文件
① include本類的兩個頭文件 demoClass.h和demoClass_priv.h。
② 定義demoClassInfo變量,并賦值。
③ 定義本類的構造函數demoClassNew()。
這個代碼轉換工具由perl語言編程實現,可以集成在makefile中成為代碼預處理的一部分。
3 此方案和C++方案的對比
在linux環境下,功能完全相同的兩個程序:
4 結束語
通過此方案的轉換手段,可以在基本保持C語言性能的基礎上,開發出支持面向對象的C程序,而且不增加開發人員的額外負擔。它大大提高了程序的可復用性和可維護性,為開發結構更加復雜、代碼更加清晰易讀的嵌入式軟件系統提供了可能。在實際應用中,采用此方案的軟件代碼明顯比用原C語言實現的同樣功能的代碼更加受開發人員的歡迎。
參考文獻:
[1] 章遠陽,楊芙清,邵維中.C++語言的面向對象特性分析[J].計算機工程與應用,1992(9).
[2] 張泰樂,肖孫圣,倪宏.面向對象基本特性的c語言實現[J].微計算機應用,23(6).
[3] Meyers S,Effective C++[M].武漢:華中科技大學出版社,2005.
[4] 劉宇,王煒,張見威.C++語言面向對象機制的底層實現[J].華南師范大學學報,1998(4).