




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
1、水滴石穿C語言之C語言的底層操作概述 HYPERLINK /key/1574/1574.html t _blank C語言的內(nèi)存模型基本上對應(yīng)了現(xiàn)在von Neumann(馮諾伊曼)計算機的實際存儲模型,很好的達到了對機器的 HYPERLINK /key/4883/14883.html t _blank 映射,這是C/C+適合做底層開發(fā)的主要原因,另外,C語言適合做底層開發(fā)還有另外一個原因,那就是C語言對底層操作做了很多的的支持,提供了很多比較底層的功能。下面結(jié)合問題分別進行闡述。問題:移位操作在運用移位操作符時,有兩個問題必須要清楚:(1)、在右移操作中,騰空位是填 0 還是符號位;(2)、
2、什么數(shù)可以作移位的位數(shù)。答案與分析:和移位的位數(shù) 左移: 變量名移位的位數(shù) 經(jīng)過移位后, 一端的位被擠掉,而另一端空出的位以0 填補,在C語言中的移位不是循環(huán)移動的。(1) 第一個問題的答案很簡單,但要根據(jù)不同的情況而定。如果被移位的是無符號數(shù),則填 0 。如果是有符號數(shù),那么可能填 0 或符號位。如果你想解決右移操作中騰空位的填充問題,就把變量聲明為無符號型,這樣騰空位會被置 0。(2) 第二個問題的答案也很簡單:如果移動 n 位,那么移位的位數(shù)要不小于 0 ,并且一定要小于 n 。這樣就不會在一次操作中把所有數(shù)據(jù)都移走。比如,如果整型數(shù)據(jù)占 32 位,n 是一整型數(shù)據(jù),則 n 31 和 n
3、 0 都合法,而 n 32 和 n 1 不可能為 0 。 問題:位段結(jié)構(gòu)struct RPR_ATD_TLV_HEADERULONG res1:6;ULONG HYPERLINK /key/4018/19018.html t _blank type:10;ULONG res1:6;ULONG length:10; ;位段結(jié)構(gòu)是一種特殊的結(jié)構(gòu), 在需按位訪問一個字節(jié)或字的多個位時, 位結(jié)構(gòu)比按位運算符更加方便。 位結(jié)構(gòu)定義的一般形式為: struct位結(jié)構(gòu)名 數(shù)據(jù)類型 變量名: 整型常數(shù); 數(shù)據(jù)類型 變量名: 整型常數(shù); 位結(jié)構(gòu)變量;其中: 整型常數(shù)必須是非負的整數(shù), 范圍是015, 表示二進制
4、位的個數(shù), 即表示有多少位。變量名是選擇項, 可以不命名, 這樣規(guī)定是為了排列需要。 例如: 下面定義了一個位結(jié)構(gòu)。struct unsigned incon: 8; /*incon占用低字節(jié)的07共8位*/ unsigned txcolor: 4;/*txcolor占用高字節(jié)的03位共4位*/ unsigned bgcolor: 3;/*bgcolor占用高字節(jié)的46位共3位*/ unsigned blink: 1; /*blink占用高字節(jié)的第7位*/ ch;位結(jié)構(gòu)成員的訪問與結(jié)構(gòu)成員的訪問相同。 例如: 訪問上例位結(jié)構(gòu)中的bgcolor成員可寫成: ch.bgcolor位結(jié)構(gòu)成員可以與其
5、它結(jié)構(gòu)成員一起使用。 按位訪問與設(shè)置,方便&節(jié)省例如:struct info char name8; int age; struct addr address; float pay; unsigned state: 1; unsigned pay: 1; workers;上例的結(jié)構(gòu)定義了關(guān)于一個工從的信息。其中有兩個位結(jié)構(gòu)成員, 每個位結(jié)構(gòu)成員只有一位, 因此只占一個字節(jié)但保存了兩個信息, 該字節(jié)中第一位表示工人的狀態(tài), 第二位表示工資是否已發(fā)放。由此可見使用位結(jié)構(gòu)可以節(jié)省存貯空間。 注意不要超過值限制問題:字節(jié)對齊讓偶們先來看下面這個結(jié)構(gòu)體:struct stu1 int a; char b
6、; 來看看sizeof(stu)的結(jié)果為多少? 怎么是8啊?你先別急,再來看下一個例子:struct stu2 char b; int a; 這個sizeof(stu2)是多少?怎么還是8啊?現(xiàn)在創(chuàng)建一個結(jié)構(gòu)體變量stu2 s2 a , 0 x12345678h; stu1 s1 0 x12345678, a 運行DEGUG,怎么樣發(fā)現(xiàn)了什么?在第一個結(jié)構(gòu)體中char b的后面內(nèi)存有三個字節(jié)是添了數(shù)據(jù)的.也就是這樣 78 56 34 12 61 cc cc cc 而在第二個結(jié)構(gòu)體中CHAR B的后面內(nèi)存中也添加了數(shù)據(jù).61 cc cc cc 78 56 34 12這又是怎么回事呢?需要字節(jié)對齊
7、當然有設(shè)計者的考慮了,原來這樣有助于加快計算機的存取速度,否則就得多花指令周期了。所以,編譯器通常都會對結(jié)構(gòu)體進行處理,讓寬度為2的基本數(shù)據(jù)類型(short等)都位于能被2整除的地址上,讓寬度為4的基本數(shù)據(jù)類型(int等)都位于能被4整除的地址上。正是因為如此兩個數(shù)中間就可能需要加入填充字節(jié),所以結(jié)構(gòu)體占的內(nèi)存空間就增長了。其實字節(jié)對齊的細節(jié)和具體編譯器實現(xiàn)相關(guān),但一般而言,滿足三個準則:結(jié)構(gòu)體變量的首地址能夠被其最寬基本類型成員的大小所整除;2) 結(jié)構(gòu)體每個成員相對于結(jié)構(gòu)體首地址的偏移量都是成員大小的整數(shù)倍,如有需要編譯器會在成員之間加上填充字節(jié);例如上面第二個結(jié)構(gòu)體變量的地址空間。 3)
8、結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最寬基本類型成員大小的整數(shù)倍,如有需要編譯器會在最末一個成員之后加上填充字節(jié)。例如上面第一個結(jié)構(gòu)體變量。(哎呀!知道!真多嘴?。┈F(xiàn)在就可以解釋上面的問題了,第一個結(jié)構(gòu)體變量中成員變量最寬為4(SIZEOF(INT) = 4),所以S1變量首地址必須能被整除。(不信你試試?。㏒1的大小也應(yīng)該為4的整數(shù)倍。但是現(xiàn)在s1中有 4 + 1 的空間,所以為了滿足第三個條件就在char b的后面在加上三個字節(jié)的空間以湊夠8個字節(jié)空間。第二個結(jié)構(gòu)體變量S2中 成員變量最大寬度為4,而且按照以前的理解int a 的地址和s2的地址相差5個字節(jié),但是為了滿足第而個條件(相差的距離-偏移地址
9、必須是4的整數(shù)倍)所以在char b的后面添加了三個字節(jié)的空間以保證int a的偏移地址是4的整數(shù)倍即為4。至于涉及到結(jié)構(gòu)體嵌套的問題,你也可以用上述方法總結(jié)的,只不過你把被嵌套的結(jié)構(gòu)體在原地展開就行了,不過在計算偏移地址的時候被嵌套的結(jié)構(gòu)體是不能原地展開的必須當作整體。嘿嘿!偶申明一點,上述三條建議不是偶說的,是做編譯器的工程師總結(jié)出來的,偶只是借用而已。我在使用VC編程的過程中,有一次調(diào)用DLL中定義的結(jié)構(gòu)時,發(fā)覺結(jié)構(gòu)都亂掉了,完全不能讀取正確的值,后來發(fā)現(xiàn)這是因為DLL和調(diào)用程序使用的字節(jié)對齊選項不同,那么我想問一下,字節(jié)對齊究竟是怎么一回事?答案與分析:為了能使CPU對變量進行高效快速
10、的訪問,變量的起始地址應(yīng)該具有某些特性,即所謂的“對齊”。例如對于4字節(jié)的int類型變量,其起始地址應(yīng)位于4字節(jié)邊界上,即起始地址能夠被4整除。關(guān)于字節(jié)對齊:1、 當不同的結(jié)構(gòu)使用不同的字節(jié)對齊定義時,可能導(dǎo)致它們之間交互變得很困難。2、 在跨CPU進行通信時,可以使用字節(jié)對齊來保證唯一性,諸如通訊協(xié)議、寫驅(qū)動程序時候寄存器的結(jié)構(gòu)等。三種對齊方式:1、 自然對齊方式( HYPERLINK /key/4870/19870.html t _blank Natural Alignment):與該數(shù)據(jù)類型的大小相等。2、 指定對齊方式 :#pragma pack(8) /指定Align為 8;#pra
11、gma pack() /恢復(fù)到原先值3、 實際對齊方式:Actual Align = min ( Order Align, Natual Align )對于復(fù)雜數(shù)據(jù)類型(比如結(jié)構(gòu)等):實際對齊方式是其成員最大的實際對齊方式:Actual Align = max( Actual align1,2,3,)編譯器的填充規(guī)律:1、 成員為成員Actual Align的整數(shù)倍,在前面加Padding。成員Actual Align = min( 結(jié)構(gòu)Actual Align,設(shè)定對齊方式)2、 結(jié)構(gòu)為結(jié)構(gòu)Actual Align的整數(shù)倍,在后面加Padding.例子分析:#pragma pack(8) /
12、指定Align為 8struct STest1char ch1; long lo1;char ch2; test1;#pragma pack()現(xiàn)在Align of STest1 = 4 , sizeof STest1 = 12 ( 4 * 3 )test1在內(nèi)存中的排列如下( FF 為 padding ):00 - - - 04 - - - 08 - - - 12 - - -01 FF FF FF 01 01 01 01 01 FF FF FF ch1 - lo1 - ch2#pragma pack(2) /指定Align為 2struct STest2char ch3;STest1 tes
13、t; test2;#pragma pack()現(xiàn)在 Align of STest1 = 2, Align of STest2 = 2 , sizeof STest2 = 14 ( 7 * 2 )test2在內(nèi)存中的排列如下:00 - - - 04 - - - 08 - - - 12 - - -02 FF 01 FF FF FF 01 01 01 01 01 FF FF FF ch3 ch1 - lo1 - ch2注意事項:1、 這樣一來,編譯器無法為特定平臺做優(yōu)化,如果效率非常重要,就盡量不要使用#pragma pack,如果必須使用,也最好僅在需要的地方進行設(shè)置。2、 需要加pack的地方一
14、定要在定義結(jié)構(gòu)的頭文件中加,不要依賴命令行選項,因為如果很多人使用該頭文件,并不是每個人都知道應(yīng)該pack。這特別表現(xiàn)在為別人開發(fā)庫文件時,如果一個庫函數(shù)使用了struct作為其參數(shù),當調(diào)用者與庫文件開發(fā)者使用不同的pack時,就會造成錯誤,而且該類錯誤很不好查。3、 在VC及BC提供的頭文件中,除了能正好對齊在四字節(jié)上的結(jié)構(gòu)外,都加了pack,否則我們編的Windows程序哪一個也不會正常運行。4、 在 #pragma pack(n) 后一定不要include其他頭文件,若包含的頭文件中改變了align值,將產(chǎn)生非預(yù)期結(jié)果。5、 不要多人同時定義一個 HYPERLINK /key/837/8
15、37.html t _blank 數(shù)據(jù)結(jié)構(gòu)。這樣可以保證一致的pack值。問題:按位運算符 C語言和其它高級語言不同的是它完全支持按位運算符。這與匯編語言的位操作有些相似。 C中按位運算符列出如下: 操作符 作用 & 位邏輯與 | 位邏輯或 位邏輯異或 - 位邏輯反 右移 左移 注意:1、按位運算是對字節(jié)或字中的實際位進行檢測、設(shè)置或移位, 它只適用于字符型和整數(shù)型變量以及它們的變體, 對其它數(shù)據(jù)類型不適用。 2、關(guān)系運算和邏輯運算表達式的結(jié)果只能是1或0。 而按位運算的結(jié)果可以取0或1以外的值。 要注意區(qū)別按位運算符和邏輯運算符的不同, 例如, 若x=7, 則x&8 的值為真(兩個非零值相與
16、仍為非零), 而x&8的值為0。 3、 | 與 |,&與&,與! 的關(guān)系&、| 和 操作符把它們的操作數(shù)當作一個為序列,按位單獨進行操作。比如:10 & 12 = 8,這是因為&操作符把 10 和 12 當作二進制描述 1010 和 1100 ,所以只有當兩個操作數(shù)的相同位同時為 1 時,產(chǎn)生的結(jié)果中相應(yīng)位才為 1 。同理,10 | 12 = 14 ( 1110 ),通過補碼運算,10 = -11 ( 11.110101 )。 &、| 和!操作符把它們的操作數(shù)當作真或假,并且用 0 代表假,任何非 0 值被認為是真。它們返回 1 代表真,0 代表假,對于&和|操作符,如果左側(cè)的操作數(shù)的值就可以
17、決定表達式的值,它們根本就不去計算右側(cè)的操作數(shù)。所以,!10 是 0 ,因為 10 非 0 ;10 & 12 是 1 ,因為 10 和 12 均非 0 ;10 | 12也是 1 ,因為 10 非 0 。并且,在最后一個表達式中,12 根本就沒被計算,在表達式 10 | f( ) 中也是如此。水滴石穿C語言之extern聲明辨析1 基本解釋 HYPERLINK /key/263/495263.html t _blank extern可以置于變量或者 HYPERLINK /key/1456/6456.html t _blank 函數(shù)前,以標示變量或者函數(shù)的定義在別的文件中,提示編譯器遇到此變量和函
18、數(shù)時在其他模塊中尋找其定義。另外,extern也可用來進行鏈接指定。2 問題:extern 變量在一個源文件里定義了一個數(shù)組:char a6;在另外一個文件里用下列語句進行了聲明:extern char *a;請問,這樣可以嗎? 答案與分析:1)、不可以,程序運行時會告訴你非法訪問。原因在于,指向類型T的指針并不等價于類型T的數(shù)組。extern char *a聲明的是一個指針變量而不是字符數(shù)組,因此與實際的定義不同,從而造成運行時非法訪問。應(yīng)該將聲明改為extern char a 。2)、例子分析如下,如果a = abcd,則外部變量a=0 x61626364 (abcd的ASCII碼值),*
19、a顯然沒有意義,如下圖:顯然a指向的空間(0 x61626364)沒有意義,易出現(xiàn)非法內(nèi)存訪問。3)、這提示我們,在使用extern時候要嚴格對應(yīng)聲明時的格式,在實際編程中,這樣的錯誤屢見不鮮。4)、extern用在變量聲明中常常有這樣一個作用,你在*.c文件中聲明了一個全局的變量,這個全局的變量如果要被引用,就放在*.h中并用extern來聲明。3 問題:extern 函數(shù)1常常見extern放在函數(shù)的前面成為函數(shù)聲明的一部分,那么, HYPERLINK /key/1574/1574.html t _blank C語言的關(guān)鍵字extern在函數(shù)的聲明中起什么作用?答案與分析:如果函數(shù)的聲明中
20、帶有關(guān)鍵字extern,僅僅是暗示這個函數(shù)可能在別的源文件里定義,沒有其它作用。即下述兩個函數(shù)聲明沒有明顯的區(qū)別:extern int f(); 和int f();當然,這樣的用處還是有的,就是在程序中取代include “*.h”來聲明函數(shù),在一些復(fù)雜的項目中,我比較習慣在所有的函數(shù)聲明前添加extern修飾。4 問題:extern 函數(shù)2當函數(shù)提供方單方面修改函數(shù)原型時,如果使用方不知情繼續(xù)沿用原來的extern申明,這樣編譯時編譯器不會報錯。但是在運行過程中,因為少了或者多了輸入?yún)?shù),往往會照成系統(tǒng)錯誤,這種情況應(yīng)該 HYPERLINK /key/4203/549203.html t _
21、blank 如何解決?答案與分析:目前業(yè)界針對這種情況的處理沒有一個很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供對外部接口的聲明,然后調(diào)用方include該頭文件,從而省去extern這一步。以避免這種錯誤。寶劍有雙鋒,對extern的應(yīng)用,不同的場合應(yīng)該選擇不同的做法。5 問題:extern “C”在C+環(huán)境下使用C函數(shù)的時候,常常會出現(xiàn)編譯器無法找到obj模塊中的C函數(shù)定義,從而導(dǎo)致鏈接失敗的情況,應(yīng)該如何解決這種情況呢?答案與分析: HYPERLINK /key/4186/504186.html t _blank C+語言在編譯的時候為了解決函數(shù)的多態(tài)問題,會將函數(shù)名
22、和參數(shù)聯(lián)合起來生成一個中間的函數(shù)名稱,而C語言則不會,因此會造成鏈接時找不到對應(yīng)函數(shù)的情況,此時C函數(shù)就需要用extern “C”進行鏈接指定,這告訴編譯器,請保持我的名稱,不要給我生成用于鏈接的中間函數(shù)名。下面是一個標準的寫法:/在.h文件的頭上#ifdef _cplusplus#if _cplusplusextern C#endif#endif /* _cplusplus */ /.h文件結(jié)束的地方#ifdef _cplusplus#if _cplusplus#endif#endif /* _cplusplus */水滴石穿C語言之static辨析1、概述static 聲明的變量在C語言中
23、有兩方面的特征:1)、變量會被放在程序的全局存儲區(qū)中,這樣可以在下一次調(diào)用的時候還可以保持原來的賦值。這一點是它與堆棧變量和堆變量的區(qū)別。2)、變量用static告知編譯器,自己僅僅在變量的作用范圍內(nèi)可見。這一點是它與全局變量的區(qū)別。2、問題:Static的理解關(guān)于static變量,請選擇下面所有說法正確的內(nèi)容:A、若全局變量僅在單個C文件中訪問,則可以將這個變量 HYPERLINK /key/4425/49425.html t _blank 修改為靜態(tài)全局變量,以降低 HYPERLINK /key/4819/54819.html t _blank 模塊間的耦合度;B、若全局變量僅由單個函數(shù)訪
24、問,則可以將這個變量改為該函數(shù)的靜態(tài)局部變量,以降低模塊間的耦合度;C、設(shè)計和使用訪問動態(tài)全局變量、靜態(tài)全局變量、靜態(tài)局部變量的函數(shù)時,需要考慮重入問題;D、靜態(tài)全局變量過大,可那會導(dǎo)致堆棧溢出。 答案與分析:對于A,B:根據(jù)本篇概述部分的說明b),我們知道,A,B都是正確的。對于C:根據(jù)本篇概述部分的說明a),我們知道,C是正確的(所謂的函數(shù)重入問題,下面會詳細闡述)。對于D:靜態(tài)變量放在程序的全局數(shù)據(jù)區(qū),而不是在堆棧中分配,所以不可能導(dǎo)致堆棧溢出,D是錯誤的。因此,答案是A、B、C。3、問題:不可重入函數(shù)曾經(jīng)設(shè)計過如下一個函數(shù),在代碼檢視的時候被提醒有bug,因為這個函數(shù)是不可重入的,為什
25、么?unsigned int sum_int( unsigned int base )unsigned int index;static unsigned int sum = 0; / 注意,是static類型的。 for (index = 1; index 1:int *(*a5)(int, char*);2:void (*b10) (void (*)();3. doube(*)() (*pa)9;答案與分析:對復(fù)雜變量建立一個類型別名的方法很簡單,你只要在傳統(tǒng)的變量聲明表達式里用類型名替代變量名,然后把關(guān)鍵字typedef加在該語句的開頭就行了。 (注:如果你對有些變量的聲明語法感到難以理
26、解,請參閱本系列第十篇的相關(guān)內(nèi)容)。1:int *(*a5)(int, char*);/pFun是我們建的一個類型別名typedef int *(*pFun)(int, char*); /使用定義的新類型來聲明對象,等價于int* (*a5)(int, char*);pFun a5; 2:void (*b10) (void (*)();/首先為上面表達式藍色部分聲明一個新類型typedef void (*pFunParam)();/整體聲明一個新類型typedef void (*pFun)(pFunParam);/使用定義的新類型來聲明對象,等價于void (*b10) (void (*)()
27、;pFun b10;3. doube(*)() (*pa)9; /首先為上面表達式藍色部分聲明一個新類型typedef double(*pFun)();/整體聲明一個新類型typedef pFun (*pFunParam)9;/使用定義的新類型來聲明對象,等價于doube(*)() (*pa)9;pFunParam pa;水滴石穿C語言之編譯器引出的問題本節(jié)主要探討C編譯器下面兩方面的特點所引發(fā)的一系列常見的編程問題。 對C文件進行分別編譯:C程序通常由幾個 HYPERLINK /key/173/15173.html t _blank 小程序(.c文件)組成,編譯器將這幾個小程序分別編譯,然后
28、通過鏈接程序?qū)⑺鼈兘M合在一起形成一個目標代碼。由于編譯器每次只能編譯一個文件,因此它不能立即檢查需要幾個源文件配合才能發(fā)現(xiàn)的錯誤。 對 HYPERLINK /key/1456/6456.html t _blank 函數(shù)的參數(shù)和返回值建立臨時變量C編譯器會對函數(shù)的參數(shù)建立臨時參數(shù),也可能會對函數(shù)的返回值隱含傳遞一個指針。因為這些臨時變量的隱含性存在,使得在某些情況下,特別是有指針存在的時候,會引發(fā)一系列的問題。 C文件中所包含的頭文件會和 HYPERLINK /key/1574/1574.html t _blank C語言一同編譯C語言中被包含的頭文件是和.c文件一起編譯的,頭文件中的問題會反映
29、到.c文件的編譯中。 問題:C文件的分別編譯我有一個數(shù)組a定義在f1.c中,但是我想在f2.c中計算它的元素個數(shù),用sizeof可以達到這個目的嗎? 答案與分析:答案是否定的,你沒有辦法達到目的,本質(zhì)原因是sizeof操作符只是在“編譯時(compile time)”起作用,而C語言的編譯單位是每次單個.c文件進行編譯(其它語言也都如此)。因此,sizeof可以確定同一個源文件中某個數(shù)組的大小,但是對于定義在另一個源文件中的數(shù)組它無能為力了,因為那已經(jīng)是“運行時(run time)”才能確定的事情了。一件事情要想做,總會有辦法的,下面提供有三種可選的辦法來解決這個問題:1)、定義一個全局變量,
30、讓它記住數(shù)組的大小,在另外一個.c文件中我們通過訪問這個全局變量來得到數(shù)組的大小信息(好像有點小題大做得不償失_)。2)、在某個.h文件中用宏定義數(shù)組的大小,例如#define ARRAY_SIZE 50,然后在兩個源文件中都包含這個.h文件,通過直接訪問ARRAY_SIZE來得到定義在不同.c文件中的數(shù)組的大小。3)、設(shè)置數(shù)組的最后一個元素為特殊值,例如0,-1,NULL等,然后我們通過遍歷數(shù)組來尋找這個特殊的結(jié)尾元素,從而判斷數(shù)組的長度(這個辦法效率低,也是笨笨的)。 問題:函數(shù)返回值隱含傳遞指針下面的代碼可以正常工作,但是在程序結(jié)束時會有一個致命錯誤產(chǎn)生。究竟是什么原因呢?struct
31、listchar *item;struct list *next;main (argc, argv).答案與分析:原因很簡單,稍微注意一點不難發(fā)現(xiàn),在定義結(jié)構(gòu)list的右花括弧后面加一個分號就可以解決這個問題:struct list char *item;struct list *next;;/缺了這個分號可不行!好了,問題是解決了,但,你知道這個錯誤究竟導(dǎo)致了什么致命問題嗎?問題不是表面上那么簡單的,OK,讓我們來看看事情背后的真相。首先看一看下面這段代碼:VOID Func ( struct HYPERLINK /key/3948/3948.html t _blank my_struct
32、stX).struct my_struct stY = .;Func (stY);當調(diào)用函數(shù)Func的時候,是把結(jié)構(gòu)變量stY的值拷貝一份到調(diào)用棧中,從而作為參數(shù)傳遞給函數(shù)FUNC的,這個叫做C語言的參數(shù)值傳遞。我相信這個你一定很清楚,那么,你應(yīng)該知道:如果函數(shù)的返回值是結(jié)構(gòu)變量的話,函數(shù)應(yīng)該如何將值返回給調(diào)用者呢?且看下面這段代碼:struct my_structFunc (VOID).struct my_struct stY = Func();此時函數(shù)Func的返回值是一個結(jié)構(gòu)類型的值,這個返回值被放在內(nèi)存中一個陰暗恐怖的地方,同時安排了一個指針指向這個地方(暫時稱為“神秘指針”),而這個
33、指針會由C語言的編譯器作為一個隱藏參數(shù)傳遞給函數(shù)Func。當函數(shù)Func返回時,編譯器生成的代碼將這個由隱藏指針指向的內(nèi)存區(qū)的值拷貝到返回結(jié)構(gòu)stY中,從而完成將結(jié)構(gòu)變量值返回給調(diào)用者。你明白了上述所講的東東,那么今天問題的真正原因也就呼之欲出了:因為struct list .的定義后面沒有加分號,導(dǎo)致主函數(shù)main (argc, argv)被編譯器理解為是一個返回值為結(jié)構(gòu)變量的函數(shù),從而期望得到除了argc和argv以外的第三個參數(shù),也就是我們上面提到的那個隱含傳入的“神秘指針”。可是,大家知道,這里函數(shù)是main函數(shù),main函數(shù)的參數(shù)是由程序中的啟動代碼(startup code)提供的
34、。而啟動代碼當然認為main()天生就應(yīng)該只得到兩個參數(shù),要“神秘指針”,當然沒有,如此一來, main()在返回時自作主張地去調(diào)用棧中訪問它的那個并不存在的第三個參數(shù)(即神秘指針),這樣導(dǎo)致非法訪問,產(chǎn)生致命問題。這才是這個問題的真正根源。建議: 1)、盡量將結(jié)構(gòu)變量的指針而不是結(jié)構(gòu)本身作為函數(shù)參數(shù),否則 HYPERLINK /key/696/450696.html t _blank 函數(shù)調(diào)用時內(nèi)存拷貝的開銷可不小,尤其是對那些調(diào)用頻繁、結(jié)構(gòu)體大的情況。 2)、結(jié)構(gòu)定義的后面一定要加分號,經(jīng)過上面我的大段講述,我相信你不會犯相同的錯誤問題:編譯器會給函數(shù)的參數(shù)隱含制造臨時副本 請問運行下面的
35、Test函數(shù)會有什么樣的結(jié)果?void GetMemory2(char *p, int num)*p = (char *)malloc(num);void Test(void)char *str = NULL;GetMemory(&str, 100);strcpy(str, hello);printf(str);答案與分析:這是林銳的C/C+高質(zhì)量編程指南上面的例子,拿來用一下。這樣調(diào)用會產(chǎn)生如下兩個后果:1)、能夠輸出hello2)、內(nèi)存泄漏 另一個相關(guān)問題:請問運行Test函數(shù)會有什么樣的結(jié)果?void GetMemory(char *p)p = (char *)malloc(100);v
36、oid Test(void) char *str = NULL;GetMemory(str);strcpy(str, hello world);printf(str);答案與分析:后果嚴重,運行的結(jié)果是程序崩潰,通過運行調(diào)試我們可以看到,經(jīng)過GetMemory后,Test函數(shù)中的 str仍舊是NULL??上攵徽{(diào)用strcpy(str, hello world);程序必然崩潰了事。原因分析: C編譯器總是會為函數(shù)的每個參數(shù)制作臨時副本,指針參數(shù)p的副本是 _p,編譯器使 _p = p。如果函數(shù)體內(nèi)的程序修改了_p的內(nèi)容,就導(dǎo)致參數(shù)p的內(nèi)容作相應(yīng)的修改。這就是指針可以用作輸出參數(shù)的原因。在本
37、例中,_p申請了新的內(nèi)存,只是把_p所指的內(nèi)存地址改變了,但是p絲毫未變。所以函數(shù)GetMemory并不能輸出任何東西,如果想要輸出動態(tài)內(nèi)存,請使用指向指針的指針,或者,使用指向引用的指針。問題:頭文件和包含它的.c文件一同編譯問 下面的代碼非常短小,看起來毫無問題,但編譯器會報告一個錯誤,請問問題可能出現(xiàn)在什么地方?#include someheader.hint myint = 0;答案與分析:不用盯著int myint = 0;看,這一句賦值應(yīng)該是C語言中最簡單的語句,問題肯定不會出在它身上,那么問題只可能出現(xiàn)在someheader.h中,最常見的就是該頭文件的最后一行的聲明(函數(shù)也好,
38、變量也好)沒有用分號;結(jié)尾,那么編譯器會將它和myint變量結(jié)合起來考慮,自然就會出錯了。這個問題主要是提醒你,在定位問題時思路要拓寬一點,可能要考慮一下所包含的頭文件是否有問題。結(jié)論:被包含的頭文件是和.c文件一起編譯的,頭文件中的問題會反映到.c文件編譯中去的,切記。水滴石穿C語言之可變參數(shù)問題 HYPERLINK /key/4647/354647.html t _blank 概述C語言中有一種長度不確定的參數(shù),形如:,它主要用在參數(shù)個數(shù)不確定的函數(shù)中,我們最容易想到的例子是printf函數(shù)。原型:int printf( const HYPERLINK /key/3784/213784.h
39、tml t _blank char *format , argument. );使用例:printf(Enjoy yourself everyday!n);printf(The value is %d!n, value);這種可變參數(shù)可以說是C語言一個比較難理解的部分,這里會由幾個問題引發(fā)一些對它的分析。注意:在C+中有 HYPERLINK /key/2073/202073.html t _blank 函數(shù)重載(overload)可以用來區(qū)別不同函數(shù)參數(shù)的調(diào)用,但它還是不能表示任意數(shù)量的函數(shù)參數(shù)。問題:printf的實現(xiàn)請問,如何自己實現(xiàn)printf函數(shù),如何處理其中的可變參數(shù)問題? 答案與分
40、析:在標準C語言中定義了一個頭文件專門用來對付可變參數(shù)列表,它包含了一組宏,和一個va_list的typedef聲明。一個典型實現(xiàn)如下:typedef char* va_list;#define va_start(list) list = (char*)&va_alist#define va_end(list)#define va_arg(list, mode)(mode*) (list += sizeof(mode)-1 自己實現(xiàn)printf:#include int printf(char* format, )va_list ap;va_start(ap, format);int n =
41、vprintf(format, ap);va_end(ap);return n;問題:運行時才確定的參數(shù)有沒有辦法寫一個函數(shù),這個函數(shù)參數(shù)的具體形式可以在運行時才確定?答案與分析:目前沒有正規(guī)的解決辦法,不過獨門偏方倒是有一個,因為有一個函數(shù)已經(jīng)給我們做出了這方面的榜樣,那就是main(),它的原型是:int main(int argc,char *argv);函數(shù)的參數(shù)是argc和argv。深入想一下,只能在運行時確定參數(shù)形式,也就是說你沒辦法從聲明中看到所接受的參數(shù),也即是參數(shù)根本就沒有固定的形式。常用的辦法是你可以通過定義一個void *類型的參數(shù),用它來指向?qū)嶋H的參數(shù)區(qū),然后在函數(shù)中根
42、據(jù)根據(jù)需要任意解釋它們的含義。這就是main函數(shù)中argv的含義,而argc,則用來表明實際的參數(shù)個數(shù),這為我們使用提供了進一步的方便,當然,這個參數(shù)不是必需的。雖然參數(shù)沒有固定形式,但我們必然要在函數(shù)中解析參數(shù)的意義,因此,理所當然會有一個要求,就是調(diào)用者和被調(diào)者之間要對參數(shù)區(qū)內(nèi)容的格式,大小, HYPERLINK /key/1912/271912.html t _blank 有效性等所有方面達成一致,否則南轅北轍各說各話就慘了。問題:可變長參數(shù)的傳遞有時候,需要編寫一個函數(shù),將它的可變長參數(shù)直接傳遞給另外的函數(shù),請問,這個要求能否實現(xiàn)?答案與分析:目前,你尚無辦法直接做到這一點,但是我們可
43、以迂回前進,首先,我們定義被調(diào)用函數(shù)的參數(shù)為va_list類型,同時在調(diào)用函數(shù)中將可變長參數(shù)列表轉(zhuǎn)換為va_list,這樣就可以進行變長參數(shù)的傳遞了??慈缦滤荆簐oid subfunc (char *fmt, va_list argp).arg = va_arg (fmt, argp); /* 從argp中逐一取出所要的參數(shù) */.void mainfunc (char *fmt, .)va_list argp;va_start (argp, fmt); /* 將可變長參數(shù)轉(zhuǎn)換為va_list */subfunc (fmt, argp); /* 將va_list傳遞給子函數(shù) */va_end
44、 (argp);.問題:可變長參數(shù)中類型為函數(shù)指針我想使用va_arg來提取出可變長參數(shù)中類型為函數(shù)指針的參數(shù),結(jié)果卻總是不正確,為什么?答案與分析:這個與va_arg的實現(xiàn)有關(guān)。一個簡單的、演示版的va_arg實現(xiàn)如下:#define va_arg(argp, type) (*(type *)(argp) += sizeof(type) - sizeof(type)其中,argp的類型是char *。如果你想用va_arg從可變參數(shù)列表中提取出函數(shù)指針類型的參數(shù),例如int (*)(),則va_arg(argp, int (*)()被擴展為:(*(int (*)() *)(argp) +=
45、sizeof (int (*)() -sizeof (int (*)()顯然,(int (*)() *)是無意義的。解決這個問題的辦法是將函數(shù)指針用typedef定義成一個獨立的數(shù)據(jù)類型,例如:typedef int (*funcptr)();這時候再調(diào)用va_arg(argp, funcptr)將被擴展為:(* (funcptr *)(argp) += sizeof (funcptr) - sizeof (funcptr)這樣就可以通過編譯檢查了。問題:可變長參數(shù)的獲取有這樣一個具有可變長參數(shù)的函數(shù),其中有下列代碼用來獲取類型為float的實參:va_arg (argp, float);這樣
46、做可以嗎?答案與分析:不可以。在可變長參數(shù)中,應(yīng)用的是加寬原則。也就是float類型被擴展成 HYPERLINK /key/3008/213008.html t _blank double;char, short被擴展成int。因此,如果你要去可變長參數(shù)列表中原來為float類型的參數(shù),需要用va_arg(argp, double)。對char和short類型的則用va_arg(argp, int)。問題:定義可變長參數(shù)的一個限制為什么我的編譯器不允許我定義如下的函數(shù),也就是可變長參數(shù),但是沒有任何的固定參數(shù)?int f (.).答案與分析:不可以。這是ANSI C 所要求的,你至少得定義一個
47、固定參數(shù)。這個參數(shù)將被傳遞給va_start(),然后用va_arg()和va_end()來確定所有實際調(diào)用時可變長參數(shù)的類型和值。水滴石穿C語言之內(nèi)存使用問題:內(nèi)存使用有人寫了一個將整數(shù)轉(zhuǎn)換為字符串的函數(shù): HYPERLINK /key/3784/213784.html t _blank char *itoa (int n)char retbuf20;sprintf(retbuf, %d, n);return retbuf;如果我調(diào)用這個函數(shù):char *str5 = itoa(5),str5會是什么結(jié)果呢?答案分析:答案是不確定,可以確定的是肯定不是我們想要的 “5”。 retbuf定義在
48、函數(shù)體中,是一個局部變量,它的內(nèi)存空間位于棧(stack)中的某個位置,其作用范圍也僅限于在itoa()這個函數(shù)中。當itoa()函數(shù)退出時,retbuf在調(diào)用棧中的內(nèi)容將被收回,這時,這塊內(nèi)存地址可能存放別的內(nèi)容。因此將retbuf這個局部變量返回給調(diào)用者是達不到預(yù)期的目的的。那么如何解決這個問題呢,不用擔心,方法不但有,而且還不止一個,下面就來闡述三種能解決這個問題的辦法:1)、在itoa()函數(shù)內(nèi)部定義一個static char retbuf20,根據(jù) HYPERLINK /key/385/185385.html t _blank 靜態(tài)變量的特性,我們知道,這可以保證函數(shù)返回后retbu
49、f的空間不會被收回,原因是函數(shù)內(nèi)的靜態(tài)變量并不是放在棧中,而是放在程序中一個叫“.bss”段的地方,這個地方的內(nèi)容是不會因為函數(shù)退出而被收回的。這種辦法確實能解決問題,但是這種辦法同時也導(dǎo)致了itoa()函數(shù)變成了一個不可重入的函數(shù)(即不能保證相同的輸入肯定有相同的輸出),另外, retbuf 中的內(nèi)容會被下一次的調(diào)用結(jié)果所替代,這種辦法不值得推薦。2)、在itoa()函數(shù)內(nèi)部用malloc() 為retbuf申請內(nèi)存,并將結(jié)果存放其中,然后將retbuf返回給調(diào)用者。由于此時retbuf位于堆(heap)中,也不會隨著函數(shù)返回而 HYPERLINK /key/1028/186028.html
50、 t _blank 釋放,因此可以達到我們的目的。但是有這樣一種情況需要注意:itoa()函數(shù)的調(diào)用者在不需要retbuf的時候必須把它釋放,否則就造成內(nèi)存泄漏了,如果此函數(shù)和調(diào)用函數(shù)都是同一個人所寫,問題不大,但如果不是,則比較容易會疏漏此釋放內(nèi)存的操作。3)、將函數(shù)定義為char *itoa(int n, char *retbuf),且retbuf的空間由調(diào)用者申請和釋放,itoa()只是將轉(zhuǎn)換結(jié)果存放到retbuf而已。這種辦法明顯比第一、二種方法要好,既避免了方法1對函數(shù)的影響,也避免了方法2對內(nèi)存分配釋放的影響,是目前一種比較通行的做法。擴展分析:其實就這個問題本身而言,我想大家都可
51、以立刻想到答案,關(guān)鍵在于對內(nèi)存這種敏感資源的正確和合理地利用,下面對內(nèi)存做一個簡單的分析:1)、程序中有不同的內(nèi)存段,包括: HYPERLINK /key/879/215879.html t _blank .data - 已 HYPERLINK /key/1942/201942.html t _blank 初始化全局/靜態(tài)變量,在整個軟件執(zhí)行過程中有效;.bss - 未初始化全局/靜態(tài)變量,在整個軟件執(zhí)行過程中有效;.stack - 函數(shù)調(diào)用棧,其中的內(nèi)容在函數(shù)執(zhí)行期間有效,并由編譯器負責分配和收回;.heap - 堆,由程序顯式分配和收回,如果不收回就是內(nèi)存泄漏。2)、自己使用的內(nèi)存最好還是
52、自己申請和釋放。這可以說是一個內(nèi)存分配和釋放的原則,比如說上面解決辦法的第二種,由itoa()分配的內(nèi)存,最后由調(diào)用者釋放,就不是一個很好的辦法,還不如用第三種,由調(diào)用者自己申請和釋放。另外這個原則還有一層意思是說:如果你要使用一個指針,最好先確信它已經(jīng)指向合法內(nèi)存區(qū)了,如果沒有就得自己分配,要不就是非法指針訪問。很多程序的致命錯誤都是訪問一個沒有指向合法內(nèi)存區(qū)的指針,這也包括空指針。問題:內(nèi)存分配 & sizeof 我使用sizeof來計算一個指針變量,我希望得到這個指針變量所分配的內(nèi)存塊的大小,可以嗎?Char *p = NULL;int nMemSize = 0;p = malloc(1
53、024);nMemSize = sizeof(p);答案與分析: 答案是達不到你的要求,sizeof只能告訴你指針本身占用的內(nèi)存大小。指針所指向的內(nèi)存,如果是malloc分配的,sizeof 是沒有辦法知道的。換句話說,malloc分配的內(nèi)存是沒有辦法向內(nèi)存管理模塊進行事后查詢的,當然你可以自己編寫代碼來維護。 問題:棧內(nèi)存使用 下面程序運行有什么問題?char *GetString(void)char p = hello world;return p;/ 編譯器將提出警告void Test4(void)char *str = NULL;str = GetString();/ str 的內(nèi)容是
54、垃圾cout str = ulAllocedLines)/ * 當前豎向空間已經(jīng)不夠了,通過realloc對其進行擴展。ulAllocedLines += 50; / 每次擴展50行。 ppText = realloc (ppText, ulAllocedLines * (char *);if (NULL = ppText)return; / 內(nèi)存分配失敗,返回 ppTextulCurrLines+ = p; / 橫向“擴展”,指向不定長字符串 問題:指針數(shù)組與數(shù)組指針與指向指針的指針指針和數(shù)組分別有如下的特征:指針:動態(tài)分配,初始空間小數(shù)組:索引方便,初始空間大下面使用高維數(shù)組來說明指針數(shù)組
55、、數(shù)組指針、指向指針的指針各自的適合場合。 多維靜態(tài)數(shù)組:各維均確定,適用于整體空間需求不大的場合,此結(jié)構(gòu)可方便索引,例a1040。 數(shù)組指針:低維確定,高維需要動態(tài)生成的場合,例ax40。int (*pA)5. pA+. 指針數(shù)組:高維確定,低維需要動態(tài)生成的場合,例a10y。int *pAi. pA0 = &b0 指向指針的指針:高、低維均需要動態(tài)生成的場合,例axy。問題:數(shù)組名相關(guān)問題假設(shè)有一個整數(shù)數(shù)組a,a和&a的區(qū)別是什么?答案與分析:a = &a = &a0,數(shù)組名a不占用存儲空間。需要引用數(shù)組(非字符串)首地址的地方,我一般使用&a0,使用a容易和指針混淆,使用&a容易和非指針
56、變量混淆。區(qū)別在于二者的類型。對數(shù)組a的直接引用將產(chǎn)生一個指向數(shù)組第一個元素的指針,而&a的結(jié)果則產(chǎn)生一個指向全部數(shù)組的指針。例如:int a2 = 1, 2;int *p = 0;p = a; /* p指向a0所在的地方 */x = *p; /* x = a0 = 1*/p = &a; /* 編譯器會提示你錯誤,*/*顯示整數(shù)指針與整數(shù)數(shù)組指針不一樣 */問題:函數(shù)指針與指針函數(shù)請問:如下定義是什么意思:int *pF1();int (*pF2)();答案與分析:首先清楚它們的定義: 指針函數(shù),返回一個指針的函數(shù)。 函數(shù)指針,指向一個函數(shù)的指針。可知: pF1是一個指針函數(shù),它返回一個指向i
57、nt型數(shù)據(jù)的指針。 pF2是一個函數(shù)指針,它指向一個參數(shù)為空的函數(shù),這個函數(shù)返回一個整數(shù)。水滴石穿C語言之指針步進辨析基本解釋通過上一篇的分析,我們已經(jīng)很清楚地知道:指針不是一個簡單的類型,它是一個本身和所指向物相復(fù)合的類型。指針的算術(shù)運算(如步進)與指針所指向物的類型密切相關(guān)。 問題:指針步進 & HYPERLINK /key/3883/183883.html t _blank amp; 步進單位下面的代碼中打印出的結(jié)果是幾?int arContext5 =0,1,2,3,4, i, *pAr;pAr = arContext;printf (%dn, *(pAr + 3 * HYPERLIN
58、K /key/2851/182851.html t _blank sizeof (int);答案與分析:這段代碼沒有正確答案,因為這段代碼是錯的,printf將打出無法預(yù)測的內(nèi)存區(qū)的值,其中的原因如下:在 HYPERLINK /key/4077/169077.html t _blank C語言中,指針總是按照它所指向的對象的大小步進。在上面的例子中,pAr是指向整數(shù)類型變量的指針,一個整數(shù)是4個字節(jié)(默認CPU字長是32位),pAr + 1就指向下一個整數(shù),也就是指針后移4個字節(jié),而不是說將地址只移動一個字節(jié)。因為C語言編譯器知道每個指針的類型,因此對指針的運算是會自動把所指類型的Size考慮
59、進去的。pAr + 3 * sizeof (int) = pAr + 3 * 4 = pAr + 12 ,因此pAr指向了數(shù)組的第 HYPERLINK /key/738/215738.html t _blank 13個整數(shù)元素。而數(shù)組本身才5個元素,pAr早已經(jīng)超出了界限,所指向的地方當然就是無人可知道的東西了,具體指向什么東西,各種不同的編譯器互不相同??傊?,肯定不能打印出我們想要的值就是了。指針不是一個簡單的類型,它是一個和指針所指物的類型相復(fù)合的類型。因此,它的算術(shù)運算與指針所指物的類型密切相關(guān), HYPERLINK /key/193/200193.html t _blank 在C+語言
60、中也是同樣。再比如下面的例子:int a8;int* p = a;int* q = p + 3;p+;指針的加減并不是指針本身的二進制表示加減,要記住,指針是一個元素的地址,它每加一次,就指向下一個元素。所以:int* q = p + 3;q指向從p開始的第三個整數(shù)。p+; p指向下一個整數(shù)。問題:指針步進 & 步進單位轉(zhuǎn)換我有一個char *類型的指針,恰好指向了一個int類型的值,我想讓這個指針跳過int指向下一個char,下面的代碼可以達到這個目的嗎?(int *)p)+;答案與分析:可以。首先我們要清楚語言中左值和右值的概念,C語言中左值是指可以放在“=”左側(cè),即可以被賦值,右值是可以
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 東陽光筆試題目及答案
- 小學六年級語文調(diào)研題目及答案
- 統(tǒng)計學中的評分標準與評估試題及答案
- 汽車維修工考試知識點解析試題及答案
- 企業(yè)中高層管理培訓(xùn)
- 2024年食品質(zhì)檢員考試策略與準備
- 2024年美容體驗顧客分析試題及答案
- 統(tǒng)計學考試理解常用模型試題及答案
- 2024年二手車評估師考試學習資源與答案
- 找刺猬閱讀測試題及答案
- 快餐店創(chuàng)業(yè)計劃書
- 2025年輔警招聘考試試題庫及答案(全優(yōu))
- 【初中地理】西亞課件-2024-2025學年人教版(2024)七年級地理下冊
- 2024年4月27日福建省事業(yè)單位《綜合基礎(chǔ)知識》真題及答案
- (一模)2025年廣東省高三高考模擬測試 (一) 英語試卷(含官方答案及詳解)
- 退役軍人無人機培訓(xùn)宣傳
- 退役軍人保密教育
- 《園林微景觀設(shè)計與制作》課件-項目二 作品展示
- 本科畢業(yè)論文完整范文(滿足查重要求)城市社區(qū)網(wǎng)格化治理問題研究
- 2025年旅游專業(yè)面試試題及答案
- 液壓升降平臺施工方案
評論
0/150
提交評論