Way on c & c++ 小記 [三]
繼續不簡單的數據類型。
昨天沒有繼續“動筆”,倒是萌發了閱讀《東周列國誌》的念頭,並且看了兩章Joel寫的《軟件隨想錄》,頗有幾分感觸。今天再次回到鞏固所學的路途上。
在操作係統中,有一種思想或者技術叫“覆蓋”,利用的是某個運行時間段內函數(或其它占據內存的載體)不共存的現象。比如在下表中,main可以調用l-fun()或者r-fun(),但不會同時調用二者;同樣地,ll-fun()和rr-fun()也不會共存在同一時間段內。也就是說l-fun()和ll-fun()是main的左子孫,而r-fun()和rr-fun()是main的右子孫,形成了一棵活動樹,就有了一種結點間消亡順序關係:某結點的左兄弟(如果有的話)總是先於該結點消亡,對應於活動記錄先出棧(見篇一https://blog.csdn.net/jasonblog/archive/2010/01/27/5261634.aspx)。
主函數main |
|
l-fun(); |
r-fun(); |
ll-fun(); |
rr-fun(); |
既然l-fun和r-fun不共存,那麼取memSize = max(l-fun.memSize, r-fun.memSize),即取二者中占內存更大者的內存所需大小,就可以使得二者共用一片內存;ll-fun和rr-fun也同理。
這種思想讓我不由自主地聯想起了共用體和共享內存。
共用體與結構體類似,但是結構體內的成員變量都有獨立的內存空間,而共用體是所有成員變量公用一片內存空間,其大小與內存需求最大的成員變量所需相等,並且每次對共用體成員進行訪問,都是取其首地址。二者區別見下表:
結構體:
…… |
num |
score |
str |
…… |
共用體:
…… |
num,score,str |
…… |
也可以換種方式,用代碼來看:
#include <stdio.h> struct sData { int num; floatscore; charstr[20]; }; union uData { int num; floatscore; charstr[20]; }; int main(int argc, char *argv[]){ structsData sVar1; unionuData uVar1; printf("%d,%d/n",sizeof(sVar1) ,sizeof(uVar1)); printf("%02x/t",&sVar1.num); printf("%02x/t",&sVar1.score); printf("%02x/n",sVar1.str); printf("%02x/t",&uVar1.num); printf("%02x/t",&uVar1.score); printf("%02x/n",uVar1.str); return 0; }
上述代碼的輸出如下:
28,20
12f3a4 12f3a8 12f3ac
12f3c0 12f3c0 12f3c0
從輸出可以看出,一是在多成員變量的情況下,共用體所需的內存空間大小相對結構體來講要小;二是結構體每個成員都有獨立的內存空間(即有獨立的首地址),而共用體所有成員變量的首地址是一樣的,如在上例中都為12f3c0。
共用體的一個應用讓我印象十分深刻,那就是PHP弱類型的實現。以下兩段代碼形成了一定的聯係和對比:
代碼一:
#include <stdio.h> #include <string.h> union uData { int num; floatscore; charstr[20]; }; int main(int argc, char*argv[]){ unionuData var; var.num = 1; var.score = 1.1; strcpy(var.str, "hello"); return 0; }
代碼二:
<?php $var = 1; $var = 1.1; $var= "hello";
我是通過這樣的比較來初步在自己的腦海裏建立起如何用強類型的C語言來實現弱類型的PHP語言。若要繼續深入一點,則要揭開一點點zend.h的幕紗。
在php中,變量的信息統一用一個結構體_zval_struct來保存,以下是摘自5.3.1源碼的代碼片段:
struct_zval_struct { /* Variableinformation */ zvalue_value value; /* value */ zend_uint refcount__gc; zend_uchar type; /* active type */ zend_uchar is_ref__gc; };
而由注釋可以知道保存變量值的是value,它是一個zvalue_value類型的變量。該類型的定義在zend.h中的位置剛好是在_zval_struct上方:
typedef union _zvalue_value { longlval; /* long value */ doubledval; /* double value */ struct { char*val; intlen; } str; HashTable *ht; /* hash tablevalue */ zend_object_value obj; }zvalue_value;
顯然可以知道zvalue_value是一個共用體類型的變量。這就是關於強類型C語言如何實現弱類型PHP語言的一點點粗淺知識,但是就是這點粗淺的知識給我的印象卻是十分深刻,讓我覺得設計思想十分重要,也是我十分欠缺的。
共用體就暫時告一段落,轉向C語言中經典類型:指針。
在我的理解中,內存就像一棟樓房,每間樓房就好比內存單元,門牌號好比內存地址,樓房內的事物就是內存單元存儲的內容,而指針,便如門牌號。舉個例子,比如我們有這麼一句聲明:int *p; 這就表示我們向物業要了一間專門存放門牌號的房間,並且這個房間裏麵存放的門牌號都必須指向存放int類型數據的房間。
以下是幾種指針類型:
int *p; 表示p是指針,該指針指向int類型的變量。
int * *p; 表示p是指針,該指針指向指針變量,且後者為指向int類型變量的指針。
int *p[5]; 表示數組p中的元素為指針。
int (*p)[5];表示p為指針,指向類似a[][5]這樣的二維數組。
int *f(); 表示f是一個函數,該函數返回一個指針類型的值。
int (*f)(); 表示f是一個指針,該指針指向一個函數入口。
相對於共用體給我那個印象深刻的應用,指針也留下了一個不淺的足跡,那就是一堆括號、void、函數以及指針的結合:(*(void(*)() )0)() 。第一次見到它那種被震撼到的心情此時仍記憶猶新,真是令人頭皮發麻啊!在此不妨先引入一個法則:
The right-left rule:
Start reading the declarationfrom the innermost parentheses, go right, and then go left. When you encounterparentheses, the direction should be reversed. Once everything in theparentheses has been parsed, jump out of it. Continue till the wholedeclaration has been parsed.
以上是判斷指針類型的有效手段,大致意思就是從最裏的括號開始,先右後左開始解析,遇到括號則轉向。我們以上述例子來進行分析。
首先,往最裏麵的括號看可以看到“(*)”,我們可以初步知道這個括號表示一個指針;接著跳出去,先往右再往左瞧可以分析出“(*)()”,對照上麵幾種指針類型,我們可以進一步知道這個指針指向一個函數入口;繼續right-left,可以匹配出“(void (*)())”來,可以了解到指針指向的函數返回值為void類型;接下來的一步我們小心點地向右看,可以看到數字0,這意味著將數字0強製轉換為一個指向返回值為void類型的函數入口的指針(有點拗口^_^),遇到括號後我們再返回來往左看,可以看到星號“*”,而星號放在指針前麵是表示取指針變量指向的內容,於是到這個時候我們獲得了函數入口地址(這個入口地址為0);繼而在最後,我們可以知道整個表達式的功能是調用入口地址為0的函數!為了更深刻地理解,依舊以代碼形式進行了解。
首先,看如下代碼:
#include <stdio.h> void greet(){ printf("hello/n"); } int main(int argc, char*argv[]){ void(*fp)(); fp = greet; (*fp)(); //縮寫為:fp(); printf("%02x/n",*fp); //同上,這裏也可以縮寫為:printf("%02x/n", fp); return 0; }
上述fp是一個指向函數入口的指針,存放的是函數的入口地址,所以*fp為函數的入口地址。接著模仿那個令人發麻的表達式修改一下代碼:
#include <stdio.h> void greet(){ printf("hello/n"); } int main(int argc, char*argv[]){ void(*fp)(); fp = greet; (*fp)(); //縮寫為:fp(); printf("%02x/n",*fp); //這裏輸出函數的入口地址,即*fp的內容 (*(void(*)())(*fp))(); //!!注意這裏 return 0; }
運行的結果如下:
hello
401050
hello
可見程序調用了兩次greet函數,一次是通過fp指針獲取入口地址進而調用,另一次則是仿造那個“神奇”的表達式。^_^
2010-1-29 晚
----------------------------------------cuttingline----------------------------------------
最後更新:2017-04-02 04:01:45