可重入函數(shù)與不可重入函數(shù)
主要用于多任務(wù)環(huán)境中,一個(gè)可重入的函數(shù)簡單來說就是可以被中斷的函數(shù),也就是說,可以在這個(gè)函數(shù)執(zhí)行的任何時(shí)刻中斷它,轉(zhuǎn)入OS調(diào)度下去執(zhí)行另外一段代碼,而返回控制時(shí)不會(huì)出現(xiàn)什么錯(cuò)誤;而不可重入的函數(shù)由于使用了一些系統(tǒng)資源,比如全局變量區(qū),中斷向量表等,所以它如果被中斷的話,可能會(huì)出現(xiàn)問題,這類函數(shù)是不能運(yùn)行在多任務(wù)環(huán)境下的。
也可以這樣理解,重入即表示重復(fù)進(jìn)入,首先它意味著這個(gè)函數(shù)可以被中斷,其次意味著它除了使用自己棧上的變量以外不依賴于任何環(huán)境(包括static),這樣的函數(shù)就是purecode(純代碼)可重入,可以允許有該函數(shù)的多個(gè)副本在運(yùn)行,由于它們使用的是分離的棧,所以不會(huì)互相干擾。如果確實(shí)需要訪問全局變量(包括static),一定要注意實(shí)施互斥手段??芍厝牒瘮?shù)在并行運(yùn)行環(huán)境中非常重要,但是一般要為訪問全局變量付出一些性能代價(jià)。
編寫可重入函數(shù)時(shí),若使用全局變量,則應(yīng)通過關(guān)中斷、信號(hào)量(即P、V操作)等手段對(duì)其加以保護(hù)。
說明:若對(duì)所使用的全局變量不加以保護(hù),則此函數(shù)就不具有可重入性,即當(dāng)多個(gè)進(jìn)程調(diào)用此函數(shù)時(shí),很有可能使有關(guān)全局變量變?yōu)椴豢芍獱顟B(tài)。
示例:假設(shè)Exam是int型全局變量,函數(shù)Squre_Exam返回Exam平方值。那么如下函數(shù)不具有可重入性。
unsigned int example( int para )
{
unsigned int temp;
Exam = para; // (**)
temp = Square_Exam( );
return temp;
}
此函數(shù)若被多個(gè)進(jìn)程調(diào)用的話,其結(jié)果可能是未知的,因?yàn)楫?dāng)(**)語句剛執(zhí)行完后,另外一個(gè)使用本函數(shù)的進(jìn)程可能正好被激活,那么當(dāng)新激活的進(jìn)程執(zhí)行到此函數(shù)時(shí),將使Exam賦與另一個(gè)不同的para值,所以當(dāng)控制重新回到“temp = Square_Exam( )”后,計(jì)算出的temp很可能不是預(yù)想中的結(jié)果。此函數(shù)應(yīng)如下改進(jìn)。
unsigned int example( int para ) {
unsigned int temp;
[申請(qǐng)信號(hào)量操作] //(1)
Exam = para;
temp = Square_Exam( );
[釋放信號(hào)量操作]
return temp;
}
(1)若申請(qǐng)不到“信號(hào)量”,說明另外的進(jìn)程正處于給Exam賦值并計(jì)算其平方過程中(即正在使用此信號(hào)),本進(jìn)程必須等待其釋放信號(hào)后,才可繼續(xù)執(zhí)行。若申請(qǐng)到信號(hào),則可繼續(xù)執(zhí)行,但其它進(jìn)程必須等待本進(jìn)程釋放信號(hào)量后,才能再使用本信號(hào)。
保證函數(shù)的可重入性的方法:
在寫函數(shù)時(shí)候盡量使用局部變量(例如寄存器、堆棧中的變量),對(duì)于要使用的全局變量要加以保護(hù)(如采取關(guān)中斷、信號(hào)量等方法),這樣構(gòu)成的函數(shù)就一定是一個(gè)可重入的函數(shù)。
VxWorks中采取的可重入的技術(shù)有:
* 動(dòng)態(tài)堆棧變量(各子函數(shù)有自己獨(dú)立的堆棧空間)
* 受保護(hù)的全局變量和靜態(tài)變量
* 任務(wù)變量
--------------------------------------------------
在實(shí)時(shí)系統(tǒng)的設(shè)計(jì)中,經(jīng)常會(huì)出現(xiàn)多個(gè)任務(wù)調(diào)用同一個(gè)函數(shù)的情況。如果這個(gè)函數(shù)不幸被設(shè)計(jì)成為不可重入的函數(shù)的話,那么不同任務(wù)調(diào)用這個(gè)函數(shù)時(shí)可能修改其他任務(wù)調(diào)用這個(gè)函數(shù)的數(shù)據(jù),從而導(dǎo)致不可預(yù)料的后果。那么什么是可重入函數(shù)呢?所謂可重入函數(shù)是指一個(gè)可以被多個(gè)任務(wù)調(diào)用的過程,任務(wù)在調(diào)用時(shí)不必?fù)?dān)心數(shù)據(jù)是否會(huì)出錯(cuò)。不可重入函數(shù)在實(shí)時(shí)系統(tǒng)設(shè)計(jì)中被視為不安全函數(shù)。滿足下列條件的函數(shù)多數(shù)是不可重入的:
1) 函數(shù)體內(nèi)使用了靜態(tài)的數(shù)據(jù)結(jié)構(gòu);
2) 函數(shù)體內(nèi)調(diào)用了malloc()或者free()函數(shù);
3) 函數(shù)體內(nèi)調(diào)用了標(biāo)準(zhǔn)I/O函數(shù)。
下面舉例加以說明。
A. 可重入函數(shù)
void strcpy(char *lpszDest, char *lpszSrc)
{
while(*lpszDest++=*lpszSrc++);
*dest=0;
}
B. 不可重入函數(shù)1
charcTemp;//全局變量
void SwapChar1(char *lpcX, char *lpcY)
{
cTemp=*lpcX;
*lpcX=*lpcY;
lpcY=cTemp;//訪問了全局變量
}
C. 不可重入函數(shù)2
void SwapChar2(char *lpcX,char *lpcY)
{
static char cTemp;//靜態(tài)局部變量
cTemp=*lpcX;
*lpcX=*lpcY;
lpcY=cTemp;//使用了靜態(tài)局部變量
}
問題1,如何編寫可重入的函數(shù)?
答:在函數(shù)體內(nèi)不訪問那些全局變量,不使用靜態(tài)局部變量,堅(jiān)持只使用局部變量,寫出的函數(shù)就將是可重入的。如果必須訪問全局變量,記住利用互斥信號(hào)量來保護(hù)全局變量。
問題2,如何將一個(gè)不可重入的函數(shù)改寫成可重入的函數(shù)?
答:把一個(gè)不可重入函數(shù)變成可重入的唯一方法是用可重入規(guī)則來重寫它。其實(shí)很簡單,只要遵守了幾條很容易理解的規(guī)則,那么寫出來的函數(shù)就是可重入的。
1) 不要使用全局變量。因?yàn)閯e的代碼很可能覆蓋這些變量值。
2) 在和硬件發(fā)生交互的時(shí)候,切記執(zhí)行類似disinterrupt()之類的操作,就是關(guān)閉硬件中斷。完成交互記得打開中斷,在有些系列上,這叫做“進(jìn)入/退出核心”。
3) 不能調(diào)用其它任何不可重入的函數(shù)。
4) 謹(jǐn)慎使用堆棧。最好先在使用前先OS_ENTER_KERNAL。
堆棧操作涉及內(nèi)存分配,稍不留神就會(huì)造成益出導(dǎo)致覆蓋其他任務(wù)的數(shù)據(jù),所以,請(qǐng)謹(jǐn)慎使用堆棧!最好別用!很多黑客程序就利用了這一點(diǎn)以便系統(tǒng)執(zhí)行非法代碼從而輕松獲得系統(tǒng)控制權(quán)。還有一些規(guī)則,總之,時(shí)刻記住一句話:保證中斷是安全的!
實(shí)例問題:曾經(jīng)設(shè)計(jì)過如下一個(gè)函數(shù),在代碼檢視的時(shí)候被提醒有bug,因?yàn)檫@個(gè)函數(shù)是不可重入的,為什么?
unsigned int sum_int( unsigned int base )
{
unsigned int index;
static unsigned int sum = 0; // 注意,是static類型
for (index = 1; index <= base; index++)
sum += index;
return sum;
}
分析:所謂的函數(shù)是可重入的(也可以說是可預(yù)測(cè)的),即只要輸入數(shù)據(jù)相同就應(yīng)產(chǎn)生相同的輸出。這個(gè)函數(shù)之所以是不可預(yù)測(cè)的,就是因?yàn)楹瘮?shù)中使用了static變量,因?yàn)?span lang=EN-US>static變量的特征,這樣的函數(shù)被稱為:帶“內(nèi)部存儲(chǔ)器”功能的的函數(shù)。因此如果需要一個(gè)可重入的函數(shù),一定要避免函數(shù)中使用static變量,這種函數(shù)中的static變量,使用原則是,能不用盡量不用。
將上面的函數(shù)修改為可重入的函數(shù),只要將聲明sum變量中的static關(guān)鍵字去掉,變量sum即變?yōu)橐粋€(gè)auto類型的變量,函數(shù)即變?yōu)橐粋€(gè)可重入的函數(shù)。
當(dāng)然,有些時(shí)候,在函數(shù)中是必須要使用static變量的,比如當(dāng)某函數(shù)的返回值為指針類型時(shí),則必須是static的局部變量的地址作為返回值,若為auto類型,則返回為錯(cuò)指針。
posted @
2007-08-03 12:56 frank.sunny 閱讀(5758) |
評(píng)論 (3) |
編輯 收藏
MSP430的時(shí)鐘問題
MSP430的時(shí)鐘周期(振蕩周期)、機(jī)器周期、指令周期之間的關(guān)系
通用知識(shí)
時(shí)鐘周期也稱為振蕩周期:定義為時(shí)鐘脈沖的倒數(shù)(時(shí)鐘周期就是直接供內(nèi)部CPU使用的晶振的倒數(shù),例如12M的晶振,它的時(shí)鐘周期就是1/12us),是計(jì)算機(jī)中的最基本的、最小的時(shí)間單位。在一個(gè)時(shí)鐘周期內(nèi),CPU僅完成一個(gè)最基本的動(dòng)作。時(shí)鐘脈沖是計(jì)算機(jī)的基本工作脈沖,控制著計(jì)算機(jī)的工作節(jié)奏。時(shí)鐘頻率越高,工作速度就越快。
機(jī)器周期:在計(jì)算機(jī)中,常把一條指令的執(zhí)行過程劃分為若干個(gè)階段,每一個(gè)階段完成一項(xiàng)工作。每一項(xiàng)工作稱為一個(gè)基本操作,完成一個(gè)基本操作所需要的時(shí)間稱為機(jī)器周期。8051系列單片機(jī)的一個(gè)機(jī)器周期由6個(gè)S周期(狀態(tài)周期)組成。一個(gè)S周期=2個(gè)時(shí)鐘周期,所以8051單片機(jī)的一個(gè)機(jī)器周期=6個(gè)狀態(tài)周期=12個(gè)時(shí)鐘周期。
指令周期:執(zhí)行一條指令所需要的時(shí)間,一般由若干個(gè)機(jī)器周期組成。指令不同,所需的機(jī)器周期也不同。
專用知識(shí):
在430中,一個(gè)時(shí)鐘周期 = MCLK晶振的倒數(shù)。如果MCLK是8M,則一個(gè)時(shí)鐘周期為1/8us;
一個(gè)機(jī)器周期 = 一個(gè)時(shí)鐘周期,即430每個(gè)動(dòng)作都能完成一個(gè)基本操作;
一個(gè)指令周期 = 1~6個(gè)機(jī)器周期,具體根據(jù)具體指令而定。
另:指令長度,只是一個(gè)存儲(chǔ)單位與時(shí)間沒有必然關(guān)系。
MSP430根據(jù)型號(hào)的不同最多可以選擇使用3個(gè)振蕩器。我們可以根據(jù)需要選擇合適的振蕩頻率,并可以在不需要時(shí)隨時(shí)關(guān)閉振蕩器,以節(jié)省功耗。這3個(gè)振蕩器分別為:
(1)DCO 數(shù)控RC振蕩器。它在芯片內(nèi)部,不用時(shí)可以關(guān)閉。DCO的振蕩頻率會(huì)受周圍環(huán)境溫度和MSP430工作電壓的影響,且同一型號(hào)的芯片所產(chǎn)生的頻率也不相同。但DCO的調(diào)節(jié)功能可以改善它的性能,他的調(diào)節(jié)分為以下3步:a:選擇BCSCTL1.RSELx確定時(shí)鐘的標(biāo)稱頻率;b:選擇DCOCTL.DCOx在標(biāo)稱頻率基礎(chǔ)上分段粗調(diào);c:選擇DCOCTL.MODx的值進(jìn)行細(xì)調(diào)。
(2)LFXT1 接低頻振蕩器。典型為接32768HZ的時(shí)鐘振蕩器,此時(shí)振蕩器不需要接負(fù)載電容。也可以接450KHZ~8MHZ的標(biāo)準(zhǔn)晶體振蕩器,此時(shí)需要接負(fù)載電容。
(3)XT2 接450KHZ~8MHZ的標(biāo)準(zhǔn)晶體振蕩器。此時(shí)需要接負(fù)載電容,不用時(shí)可以關(guān)閉。
低頻振蕩器主要用來降低能量消耗,如使用電池供電的系統(tǒng),高頻振蕩器用來對(duì)事件做出快速反應(yīng)或者供CPU進(jìn)行大量運(yùn)算。當(dāng)然高端430還有鎖頻環(huán)(FLL)及FLL+等模塊,但是初步不用考慮那么多。
MSP430的3種時(shí)鐘信號(hào):MCLK系統(tǒng)主時(shí)鐘;SMCLK系統(tǒng)子時(shí)鐘;ACLK輔助時(shí)鐘。
(1)MCLK系統(tǒng)主時(shí)鐘。除了CPU運(yùn)算使用此時(shí)鐘以外,外圍模塊也可以使用。MCLK可以選擇任何一個(gè)振蕩器所產(chǎn)生的時(shí)鐘信號(hào)并進(jìn)行1、2、4、8分頻作為其信號(hào)源。
(2)SMCLK系統(tǒng)子時(shí)鐘。供外圍模塊使用。并在使用前可以通過各模塊的寄存器實(shí)現(xiàn)分頻。SMCLK可以選擇任何一個(gè)振蕩器所產(chǎn)生的時(shí)鐘信號(hào)并進(jìn)行1、2、4、8分頻作為其信號(hào)源。
(3)ACLK輔助時(shí)鐘。供外圍模塊使用。并在使用前可以通過各模塊的寄存器實(shí)現(xiàn)分頻。但ACLK只能由LFXT1進(jìn)行1、2、4、8分頻作為信號(hào)源。
PUC復(fù)位后,MCLK和SMCLK的信號(hào)源為DCO,DCO的振蕩頻率默認(rèn)為800KHZ。ACLK的信號(hào)源為LFXT1。
MSP430內(nèi)部含有晶體振蕩器失效監(jiān)測(cè)電路,監(jiān)測(cè)LFXT1(工作在高頻模式)和XT2輸出的時(shí)鐘信號(hào)。當(dāng)時(shí)鐘信號(hào)丟失50us時(shí),監(jiān)測(cè)電路捕捉到振蕩器失效。如果MCLK信號(hào)來自LFXT1或者XT2,那么MSP430自動(dòng)把MCLK的信號(hào)切換為DCO,這樣可以保證程序繼續(xù)運(yùn)行。但MSP430不對(duì)工作在低頻模式的LFXT1進(jìn)行監(jiān)測(cè)。
為了實(shí)現(xiàn)具體的時(shí)鐘可以設(shè)置跟時(shí)鐘相關(guān)的寄存器,在低端430中是DCOCTL、BCSCTL1和BCSCTL2三個(gè)寄存器。而對(duì)于高端的430,則要考慮SCFI0、SCFQCTL、FLL_CTL0、FLL_CTL1和BTCTL等幾個(gè)寄存器。具體設(shè)置,參看DataSheet。
上傳上來的時(shí)候,發(fā)現(xiàn)圖片沒有顯示出來,不好意思
posted @
2007-06-07 22:14 frank.sunny 閱讀(4339) |
評(píng)論 (7) |
編輯 收藏
摘要:
關(guān)于變量的存儲(chǔ)問題
以前從事上位機(jī)程序代碼的編寫,壓根不用很具體的考慮變量的具體存放位置,只知道以下概念就行了:
1. 堆區(qū)( heap ):由程序員申請(qǐng)分配和釋放,屬動(dòng)態(tài)內(nèi)存分配方式,變量存放于動(dòng)態(tài)存儲(chǔ)區(qū),若程序員不釋放,程序結(jié)束時(shí)可能會(huì)由 OS 回收。不過這個(gè)內(nèi)存分配很容易引起問題,如果申請(qǐng)的內(nèi)存不釋放就會(huì)造成內(nèi)存泄漏;如果釋放的不是所要釋放的內(nèi)存,則輕者引...
閱讀全文
posted @
2007-06-01 15:00 frank.sunny 閱讀(3111) |
評(píng)論 (2) |
編輯 收藏
MSP430入門
本人學(xué)習(xí)和使用msp430一個(gè)月,偶在微控論壇看到一筆記,記載的蠻好的,現(xiàn)轉(zhuǎn)載到自己博客以做備忘。
硬件初步
這只是我在學(xué)習(xí)TI公司生產(chǎn)的16位超的功耗單片機(jī)MSP430的隨筆,希望能對(duì)其他朋友有所借鑒,不對(duì)之處還請(qǐng)多指教。
下面,開始430之旅。
講解430的書現(xiàn)在也有很多了,不過大多數(shù)都是詳細(xì)說明底層硬件結(jié)構(gòu)的,看了不免有些空洞和枯燥,我認(rèn)為了解一個(gè)MCU的操作首先要對(duì)其基礎(chǔ)特性有所了解,然后再仔細(xì)研究各模塊的功能。
1.首先你要知道msp430的存儲(chǔ)器結(jié)構(gòu)。典型微處理器的結(jié)構(gòu)有兩種:馮。諾依曼結(jié)構(gòu)——程序存儲(chǔ)器和數(shù)據(jù)存儲(chǔ)器統(tǒng)一編碼;哈佛結(jié)構(gòu)——程序存儲(chǔ)器和數(shù)據(jù)存儲(chǔ)器;msp430系列單片機(jī)屬于前者,而常用的mcs51系列屬于后者。
0-0xf特殊功能寄存器;0x10-0x1ff外圍模塊寄存器;0x200-?根據(jù)不同型號(hào)地址從低向高擴(kuò)展;0x1000-0x107f seg_b0x1080_0x10ff seg_a 供flash信息存儲(chǔ)剩下的從0xffff開始向下擴(kuò)展,根據(jù)不同容量,例如149為60KB,0xffff-0x1100
2.復(fù)位信號(hào)是MCU工作的起點(diǎn),430的復(fù)位信號(hào)有兩種:上電復(fù)位信號(hào)POR和上電清除信號(hào)PUC。POR信號(hào)只在上電和RST/NMI復(fù)位管腳被設(shè)置為復(fù)位功能,且低電平時(shí)系統(tǒng)復(fù)位。而PUC信號(hào)是POR信號(hào)產(chǎn)生,以及其他如看門狗定時(shí)溢出、安全鍵值出現(xiàn)錯(cuò)誤是產(chǎn)生。但是,無論那種信號(hào)觸發(fā)的復(fù)位,都會(huì)使msp430在地址0xffff處讀取復(fù)位中斷向量,然后程序從中斷向量所指的地址開始執(zhí)行。復(fù)位后的狀態(tài)不寫了,詳見參考書,嘿嘿。
3.系統(tǒng)時(shí)鐘是一個(gè)程序運(yùn)行的指揮官,時(shí)序和中斷也是整個(gè)程序的核心和中軸線。430最多有三個(gè)振蕩器,DCO內(nèi)部振蕩器;LFXT1外接低頻振蕩器,常見的32768HZ,不用外接負(fù)載電容;也可接高頻450KHZ-8M,需接負(fù)載電容;XT2接高頻450KHZ-8M,加外接電容。(經(jīng)驗(yàn)中發(fā)現(xiàn),接XT2時(shí),需要注意自己開啟XT2,并延時(shí)50us等待XT2起振,然后手工清除IFG1中的OFIFG位,其操作順序?yàn)椋捍蜷_XT2->等待XT2穩(wěn)定->切換系統(tǒng)時(shí)鐘為XT2)
430有三種時(shí)鐘信號(hào):MCLK系統(tǒng)主時(shí)鐘,可分頻1 2 4 8,供cpu使用,其他外圍模塊在有選擇情況下也可使用;SMCLK系統(tǒng)子時(shí)鐘,供外圍模塊使用,可選則不同振蕩器產(chǎn)生的時(shí)鐘信號(hào);ACLK輔助時(shí)鐘,只能由LFXT1產(chǎn)生,供外圍模塊。
4.中斷是430處理器的一大特色,因?yàn)閹缀趺總€(gè)外圍模塊都能產(chǎn)生,430可以在沒有任務(wù)時(shí)進(jìn)入低功耗狀態(tài),有事件時(shí)中斷喚醒cpu,處理完畢再次進(jìn)入低功耗狀態(tài)。
整個(gè)中斷的響應(yīng)過程是這樣的,當(dāng)有中斷請(qǐng)求時(shí),如果cpu處于活動(dòng)狀態(tài),先完成當(dāng)前命令;如果處于低功耗,先退出,將下一條指令的pc值壓入堆棧;如果有多個(gè)中斷請(qǐng)求,先響應(yīng)優(yōu)先級(jí)高的;執(zhí)行完后,等待中斷請(qǐng)求標(biāo)志位復(fù)位,要注意,單中斷源的中斷請(qǐng)求標(biāo)志位自動(dòng)復(fù)位,而多中斷的標(biāo)志位需要軟件復(fù)位;然后系統(tǒng)總中斷允許位SR.GIE復(fù)位,相應(yīng)的中斷向量值裝入pc,程序從這個(gè)地址繼續(xù)執(zhí)行。
這里要注意,中斷允許位SR.GIE和中斷嵌套問題。如果當(dāng)你執(zhí)行中斷程序過程中,希望可以響應(yīng)更高級(jí)別的中斷請(qǐng)求時(shí),必須在進(jìn)入第一個(gè)中斷時(shí)把SR.GIE置位。
其實(shí),其他的外圍模塊時(shí)鐘沿著時(shí)鐘和中斷這個(gè)核心來執(zhí)行的。具體的結(jié)構(gòu)我也不羅索了,可以參考430系列手冊(cè)。
C語言編程起步
因?yàn)槌S玫?span lang=EN-US>430編程開發(fā)是c語言,所以下面講解C語言對(duì)430編程的整體結(jié)構(gòu)?;旧蠈儆诳蚣芙Y(jié)構(gòu),即整體的模塊化編程,其實(shí)這也是硬件編程的基本法則拉(可不是我規(guī)定的法則哦)。
首先是程序的頭文件,包括#include <MSP430x14x.h>,這是14系列,因?yàn)槌S?span lang=EN-US>149;其他型號(hào)可自己修改。還可以包括#include "data.h" 等數(shù)據(jù)庫頭文件,或函數(shù)變量聲明頭文件,都是你自己定義的哦。
接著就是函數(shù)和變量的聲明 void Init_Sys(void),即系統(tǒng)初始化。系統(tǒng)初始化是個(gè)整體的概念,廣義上講包括所有外圍模塊的初始化,你可以把外圍模塊初始化的子函數(shù)寫到Init_Sys()中,也可以分別寫各個(gè)模塊的初始化。但結(jié)構(gòu)的簡潔,最好寫完系統(tǒng)的時(shí)鐘初始化后,其他所用到的模塊(包括一些中斷初始化)也在這里初始化。
void Init_Sys()
{
unsigned int i;
BCSCTL1&=~XT2OFF; //打開XT2振蕩器
do
{
IFG1 &= ~OFIFG; // 清除振蕩器失效標(biāo)志
for (i = 0xFF; i > 0; i--); // 延時(shí),等待XT2起振
}
while ((IFG1 & OFIFG) != 0); // 判斷XT2是否起振
BCSCTL2 =SELM_2+SELS; //選擇MCLK、SMCLK為XT2
//以下對(duì)各種模塊、中斷、外圍設(shè)備等進(jìn)行初始化
........................................
_EINT(); //打開全局中斷控制
}
這里涉及到時(shí)鐘問題,通常我們選擇XT2為8M晶振,也即系統(tǒng)主時(shí)鐘MCLK為8M,cpu執(zhí)行命令以此時(shí)鐘為準(zhǔn);但其他外圍模塊可以在相應(yīng)的控制寄存器中選擇其他的時(shí)鐘,ACLK;當(dāng)你對(duì)速度要求很低,定時(shí)時(shí)間間隔大時(shí),就可以選擇ACLK,例如在定時(shí)器Timea初始化中設(shè)置。
主程序:
void main( void )
{
WDTCTL = WDTPW + WDTHOLD;//關(guān)閉看門狗
InitSys(); //初始化
//自己任務(wù)中的其他功能函數(shù)
。。。。。。。。。。。。。。。。。。。。。
while(1);
}
主程序之后我要講講中斷函數(shù),中斷是你做單片機(jī)任務(wù)中不可缺少的部分,也可以說是靈魂了(夸張嗎)。
/***********************************************************************
各中斷函數(shù),可按優(yōu)先級(jí)依次書寫
***********************************************************************/
舉個(gè)定時(shí)中斷的例子:
//初始化
void Init_Timer_A(void)
{
TACTL = TASSEL0 + TACLR; // ACLK, clear TAR
CCTL0 = CCIE; // CCR0 中斷使能
CCR0=32768; //定時(shí)1s
TACTL|=MC0; //增計(jì)數(shù)模式
}
// 中斷服務(wù)
#pragma vector=TIMERA0_VECTOR
__interrupt void TimerA0()
{
// 你自己要求中斷執(zhí)行的任務(wù)
}
當(dāng)然,還有其他的定時(shí),和多種中斷,各系列芯片的中斷向量個(gè)數(shù)也不同。
學(xué)完并懂得以上知識(shí)后,接下去推薦看微控論壇上的msp430常用模塊應(yīng)用原理。
posted @
2007-04-18 16:04 frank.sunny 閱讀(3177) |
評(píng)論 (5) |
編輯 收藏
也談關(guān)于時(shí)間
最近轉(zhuǎn)去搞低層些的單片機(jī)程序編程,在一塊msp430上要增加一個(gè)國際標(biāo)準(zhǔn)時(shí)間,由于以前在VC中都是拿來用的,沒遇到問題,也就不會(huì)去深究。在單片機(jī)上想用標(biāo)準(zhǔn)C里面的time(time_t*)函數(shù)求得系統(tǒng)時(shí)間,最后結(jié)果出不來。后來才知道原來以前是取得的是操作系統(tǒng)的時(shí)間,汗死,單片機(jī)沒系統(tǒng)的啊,希望能夠盡早讓我搞嵌入式啊,呵呵。
后來自己弄明白了,設(shè)個(gè)時(shí)間值,然后用單片機(jī)晶振累加計(jì)數(shù),還是可以用time.h輕松實(shí)現(xiàn)標(biāo)準(zhǔn)時(shí)間計(jì)時(shí)的,而且方便不用考慮自己去寫時(shí)間轉(zhuǎn)換函數(shù),以下是具體的time,h的講解,我就不再展開了。
time.h從頭學(xué)
本文從介紹基礎(chǔ)概念入手,探討了在C/C++中對(duì)日期和時(shí)間操作所用到的數(shù)據(jù)結(jié)構(gòu)和函數(shù),并對(duì)計(jì)時(shí)、時(shí)間的獲取、時(shí)間的計(jì)算和顯示格式等方面進(jìn)行了闡述。本文還通過大量的實(shí)例向你展示了time.h頭文件中聲明的各種函數(shù)和數(shù)據(jù)結(jié)構(gòu)的詳細(xì)使用方法。
關(guān)鍵字:UTC(世界標(biāo)準(zhǔn)時(shí)間),Calendar Time(日歷時(shí)間),epoch(時(shí)間點(diǎn)),clock tick(時(shí)鐘計(jì)時(shí)單元)
1. 概念
在C/C++中,對(duì)字符串的操作有很多值得注意的問題,同樣,C/C++對(duì)時(shí)間的操作也有許多值得大家注意的地方。最近,在技術(shù)群中有很多網(wǎng)友也多次問到過C++語言中對(duì)時(shí)間的操作、獲取和顯示等等的問題。下面,在這篇文章中,筆者將主要介紹在C/C++中時(shí)間和日期的使用方法.
通過學(xué)習(xí)許多C/C++庫,你可以有很多操作、使用時(shí)間的方法。但在這之前你需要了解一些“時(shí)間”和“日期”的概念,主要有以下幾個(gè):
Coordinated Universal Time(UTC):協(xié)調(diào)世界時(shí),又稱為世界標(biāo)準(zhǔn)時(shí)間,也就是大家所熟知的格林威治標(biāo)準(zhǔn)時(shí)間(Greenwich Mean Time,GMT)。比如,中國內(nèi)地的時(shí)間與UTC的時(shí)差為+8,也就是UTC+8。美國是UTC-5。
Calendar Time:日歷時(shí)間,是用“從一個(gè)標(biāo)準(zhǔn)時(shí)間點(diǎn)到此時(shí)的時(shí)間經(jīng)過的秒數(shù)”來表示的時(shí)間。這個(gè)標(biāo)準(zhǔn)時(shí)間點(diǎn)對(duì)不同的編譯器來說會(huì)有所不同,但對(duì)一個(gè)編譯系統(tǒng)來說,這個(gè)標(biāo)準(zhǔn)時(shí)間點(diǎn)是不變的,該編譯系統(tǒng)中的時(shí)間對(duì)應(yīng)的日歷時(shí)間都通過該標(biāo)準(zhǔn)時(shí)間點(diǎn)來衡量,所以可以說日歷時(shí)間是“相對(duì)時(shí)間”,但是無論你在哪一個(gè)時(shí)區(qū),在同一時(shí)刻對(duì)同一個(gè)標(biāo)準(zhǔn)時(shí)間點(diǎn)來說,日歷時(shí)間都是一樣的。
epoch:時(shí)間點(diǎn)。時(shí)間點(diǎn)在標(biāo)準(zhǔn)C/C++中是一個(gè)整數(shù),它用此時(shí)的時(shí)間和標(biāo)準(zhǔn)時(shí)間點(diǎn)相差的秒數(shù)(即日歷時(shí)間)來表示。
clock tick:時(shí)鐘計(jì)時(shí)單元(而不把它叫做時(shí)鐘滴答次數(shù)),一個(gè)時(shí)鐘計(jì)時(shí)單元的時(shí)間長短是由CPU控制的。一個(gè)clock tick不是CPU的一個(gè)時(shí)鐘周期,而是C/C++的一個(gè)基本計(jì)時(shí)單位。
我們可以使用ANSI標(biāo)準(zhǔn)庫中的time.h頭文件。這個(gè)頭文件中定義的時(shí)間和日期所使用的方法,無論是在結(jié)構(gòu)定義,還是命名,都具有明顯的C語言風(fēng)格。下面,我將說明在C/C++中怎樣使用日期的時(shí)間功能。
2. 計(jì)時(shí)
C/C++中的計(jì)時(shí)函數(shù)是clock(),而與其相關(guān)的數(shù)據(jù)類型是clock_t。在MSDN中,查得對(duì)clock函數(shù)定義如下:
clock_t clock( void );
這個(gè)函數(shù)返回從“開啟這個(gè)程序進(jìn)程”到“程序中調(diào)用clock()函數(shù)”時(shí)之間的CPU時(shí)鐘計(jì)時(shí)單元(clock tick)數(shù),在MSDN中稱之為掛鐘時(shí)間(wal-clock)。其中clock_t是用來保存時(shí)間的數(shù)據(jù)類型,在time.h文件中,我們可以找到對(duì)它的定義:
#ifndef _CLOCK_T_DEFINED
typedef long clock_t;
#define _CLOCK_T_DEFINED
#endif
很明顯,clock_t是一個(gè)長整形數(shù)。在time.h文件中,還定義了一個(gè)常量CLOCKS_PER_SEC,它用來表示一秒鐘會(huì)有多少個(gè)時(shí)鐘計(jì)時(shí)單元,其定義如下:
#define CLOCKS_PER_SEC ((clock_t)1000)
可以看到每過千分之一秒(1毫秒),調(diào)用clock()函數(shù)返回的值就加1。下面舉個(gè)例子,你可以使用公式clock()/CLOCKS_PER_SEC來計(jì)算一個(gè)進(jìn)程自身的運(yùn)行時(shí)間:
void elapsed_time()
{
printf("Elapsed time:%u secs.\n",clock()/CLOCKS_PER_SEC);
}
當(dāng)然,你也可以用clock函數(shù)來計(jì)算你的機(jī)器運(yùn)行一個(gè)循環(huán)或者處理其它事件到底花了多少時(shí)間:
#include “stdio.h”
#include “stdlib.h”
#include “time.h”
int main( void )
{
long i = 10000000L;
clock_t start, finish;
double duration;
/* 測(cè)量一個(gè)事件持續(xù)的時(shí)間*/
printf( "Time to do %ld empty loops is ", i );
start = clock();
while( i-- ) ;
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf( "%f seconds\n", duration );
system("pause");
}
在筆者的機(jī)器上,運(yùn)行結(jié)果如下:
Time to do 10000000 empty loops is 0.03000 seconds
上面我們看到時(shí)鐘計(jì)時(shí)單元的長度為1毫秒,那么計(jì)時(shí)的精度也為1毫秒,那么我們可不可以通過改變CLOCKS_PER_SEC的定義,通過把它定義的大一些,從而使計(jì)時(shí)精度更高呢?通過嘗試,你會(huì)發(fā)現(xiàn)這樣是不行的。在標(biāo)準(zhǔn)C/C++中,最小的計(jì)時(shí)單位是一毫秒。
3.與日期和時(shí)間相關(guān)的數(shù)據(jù)結(jié)構(gòu)
在標(biāo)準(zhǔn)C/C++中,我們可通過tm結(jié)構(gòu)來獲得日期和時(shí)間,tm結(jié)構(gòu)在time.h中的定義如下:
#ifndef _TM_DEFINED
struct tm {
int tm_sec; /* 秒 – 取值區(qū)間為[0,59] */
int tm_min; /* 分 - 取值區(qū)間為[0,59] */
int tm_hour; /* 時(shí) - 取值區(qū)間為[0,23] */
int tm_mday; /* 一個(gè)月中的日期 - 取值區(qū)間為[1,31] */
int tm_mon; /* 月份(從一月開始,0代表一月) - 取值區(qū)間為[0,11] */
int tm_year; /* 年份,其值等于實(shí)際年份減去1900 */
int tm_wday; /* 星期 – 取值區(qū)間為[0,6],其中0代表星期天,1代表星期一,以此類推 */
int tm_yday; /* 從每年的1月1日開始的天數(shù) – 取值區(qū)間為[0,365],其中0代表1月1日,1代表1月2日,以此類推 */
int tm_isdst; /* 夏令時(shí)標(biāo)識(shí)符,實(shí)行夏令時(shí)的時(shí)候,tm_isdst為正。不實(shí)行夏令時(shí)的進(jìn)候,tm_isdst為0;不了解情況時(shí),tm_isdst()為負(fù)。*/
};
#define _TM_DEFINED
#endif
ANSI C標(biāo)準(zhǔn)稱使用tm結(jié)構(gòu)的這種時(shí)間表示為分解時(shí)間(broken-down time)。
而日歷時(shí)間(Calendar Time)是通過time_t數(shù)據(jù)類型來表示的,用time_t表示的時(shí)間(日歷時(shí)間)是從一個(gè)時(shí)間點(diǎn)(例如:1970年1月1日0時(shí)0分0秒)到此時(shí)的秒數(shù)。在time.h中,我們也可以看到time_t是一個(gè)長整型數(shù):
#ifndef _TIME_T_DEFINED
typedef long time_t; /* 時(shí)間值 */
#define _TIME_T_DEFINED /* 避免重復(fù)定義 time_t */
#endif
大家可能會(huì)產(chǎn)生疑問:既然time_t實(shí)際上是長整型,到未來的某一天,從一個(gè)時(shí)間點(diǎn)(一般是1970年1月1日0時(shí)0分0秒)到那時(shí)的秒數(shù)(即日歷時(shí)間)超出了長整形所能表示的數(shù)的范圍怎么辦?對(duì)time_t數(shù)據(jù)類型的值來說,它所表示的時(shí)間不能晚于2038年1月18日19時(shí)14分07秒。為了能夠表示更久遠(yuǎn)的時(shí)間,一些編譯器廠商引入了64位甚至更長的整形數(shù)來保存日歷時(shí)間。比如微軟在Visual C++中采用了__time64_t數(shù)據(jù)類型來保存日歷時(shí)間,并通過_time64()函數(shù)來獲得日歷時(shí)間(而不是通過使用32位字的time()函數(shù)),這樣就可以通過該數(shù)據(jù)類型保存3001年1月1日0時(shí)0分0秒(不包括該時(shí)間點(diǎn))之前的時(shí)間。
在time.h頭文件中,我們還可以看到一些函數(shù),它們都是以time_t為參數(shù)類型或返回值類型的函數(shù):
double difftime(time_t time1, time_t time0);
time_t mktime(struct tm * timeptr);
time_t time(time_t * timer);
char * asctime(const struct tm * timeptr);
char * ctime(const time_t *timer);
此外,time.h還提供了兩種不同的函數(shù)將日歷時(shí)間(一個(gè)用time_t表示的整數(shù))轉(zhuǎn)換為我們平時(shí)看到的把年月日時(shí)分秒分開顯示的時(shí)間格式tm:
struct tm * gmtime(const time_t *timer); struct tm * localtime(const time_t * timer);
通過查閱MSDN,我們可以知道Microsoft C/C++ 7.0中時(shí)間點(diǎn)的值(time_t對(duì)象的值)是從1899年12月31日0時(shí)0分0秒到該時(shí)間點(diǎn)所經(jīng)過的秒數(shù),而其它各種版本的Microsoft C/C++和所有不同版本的Visual C++都是計(jì)算的從1970年1月1日0時(shí)0分0秒到該時(shí)間點(diǎn)所經(jīng)過的秒數(shù)。
4.與日期和時(shí)間相關(guān)的函數(shù)及應(yīng)用
在本節(jié),我將向大家展示怎樣利用time.h中聲明的函數(shù)對(duì)時(shí)間進(jìn)行操作。這些操作包括取當(dāng)前時(shí)間、計(jì)算時(shí)間間隔、以不同的形式顯示時(shí)間等內(nèi)容。
4.1 獲得日歷時(shí)間
我們可以通過time()函數(shù)來獲得日歷時(shí)間(Calendar Time),其原型為:
time_t time(time_t * timer);
如果你已經(jīng)聲明了參數(shù)timer,你可以從參數(shù)timer返回現(xiàn)在的日歷時(shí)間,同時(shí)也可以通過返回值返回現(xiàn)在的日歷時(shí)間,即從一個(gè)時(shí)間點(diǎn)(例如:1970年1月1日0時(shí)0分0秒)到現(xiàn)在此時(shí)的秒數(shù)。如果參數(shù)為空(NUL),函數(shù)將只通過返回值返回現(xiàn)在的日歷時(shí)間,比如下面這個(gè)例子用來顯示當(dāng)前的日歷時(shí)間:
#include "time.h"
#include "stdio.h"
int main(void)
{
struct tm *ptr;
time_t lt;
lt =time(NUL);
printf("The Calendar Time now is %d\n",lt);
return 0;
}
運(yùn)行的結(jié)果與當(dāng)時(shí)的時(shí)間有關(guān),我當(dāng)時(shí)運(yùn)行的結(jié)果是:
The Calendar Time now is 1122707619
其中1122707619就是我運(yùn)行程序時(shí)的日歷時(shí)間。即從1970年1月1日0時(shí)0分0秒到此時(shí)的秒數(shù)。
4.2 獲得日期和時(shí)間
這里說的日期和時(shí)間就是我們平時(shí)所說的年、月、日、時(shí)、分、秒等信息。從第2節(jié)我們已經(jīng)知道這些信息都保存在一個(gè)名為tm的結(jié)構(gòu)體中,那么如何將一個(gè)日歷時(shí)間保存為一個(gè)tm結(jié)構(gòu)的對(duì)象呢?
其中可以使用的函數(shù)是gmtime()和localtime(),這兩個(gè)函數(shù)的原型為:
struct tm * gmtime(const time_t *timer);
struct tm * localtime(const time_t * timer);
其中gmtime()函數(shù)是將日歷時(shí)間轉(zhuǎn)化為世界標(biāo)準(zhǔn)時(shí)間(即格林尼治時(shí)間),并返回一個(gè)tm結(jié)構(gòu)體來保存這個(gè)時(shí)間,而localtime()函數(shù)是將日歷時(shí)間轉(zhuǎn)化為本地時(shí)間。比如現(xiàn)在用gmtime()函數(shù)獲得的世界標(biāo)準(zhǔn)時(shí)間是2005年7月30日7點(diǎn)18分20秒,那么我用localtime()函數(shù)在中國地區(qū)獲得的本地時(shí)間會(huì)比世界標(biāo)準(zhǔn)時(shí)間晚8個(gè)小時(shí),即2005年7月30日15點(diǎn)18分20秒。下面是個(gè)例子:
#include "time.h"
#include "stdio.h"
int main(void)
{
struct tm *local;
time_t t;
t=time(NUL);
local=localtime(&t);
printf("Local hour is: %d\n",local->tm_hour);
local=gmtime(&t);
printf("UTC hour is: %d\n",local->tm_hour);
return 0;
}
運(yùn)行結(jié)果是:
Local hour is: 15
UTC hour is: 7
4.3 固定的時(shí)間格式
我們可以通過asctime()函數(shù)和ctime()函數(shù)將時(shí)間以固定的格式顯示出來,兩者的返回值都是char*型的字符串。返回的時(shí)間格式為:
星期幾 月份日期 時(shí):分:秒 年\n\0
例如:Wed Jan 02 02:03:55 1980\n\0
其中\n是一個(gè)換行符,\0是一個(gè)空字符,表示字符串結(jié)束。下面是兩個(gè)函數(shù)的原型:
char * asctime(const struct tm * timeptr);
char * ctime(const time_t *timer);
其中asctime()函數(shù)是通過tm結(jié)構(gòu)來生成具有固定格式的保存時(shí)間信息的字符串,而ctime()是通過日歷時(shí)間來生成時(shí)間字符串。這樣的話,asctime()函數(shù)只是把tm結(jié)構(gòu)對(duì)象中的各個(gè)域填到時(shí)間字符串的相應(yīng)位置就行了,而ctime()函數(shù)需要先參照本地的時(shí)間設(shè)置,把日歷時(shí)間轉(zhuǎn)化為本地時(shí)間,然后再生成格式化后的字符串。在下面,如果t是一個(gè)非空的time_t變量的話,那么:
printf(ctime(&t));
等價(jià)于:
struct tm *ptr;
ptr=localtime(&t);
printf(asctime(ptr));
那么,下面這個(gè)程序的兩條printf語句輸出的結(jié)果就是不同的了(除非你將本地時(shí)區(qū)設(shè)為世界標(biāo)準(zhǔn)時(shí)間所在的時(shí)區(qū)):
#include "time.h"
#include "stdio.h"
int main(void)
{
struct tm *ptr;
time_t lt;
lt =time(NUL);
ptr=gmtime(<);
printf(asctime(ptr));
printf(ctime(<));
return 0;
}
運(yùn)行結(jié)果:
Sat Jul 30 08:43:03 2005
Sat Jul 30 16:43:03 2005
4.4 自定義時(shí)間格式
我們可以使用strftime()函數(shù)將時(shí)間格式化為我們想要的格式。它的原型如下:
size_t strftime(
char *strDest,
size_t maxsize,
const char *format,
const struct tm *timeptr
);
我們可以根據(jù)format指向字符串中格式命令把timeptr中保存的時(shí)間信息放在strDest指向的字符串中,最多向strDest中存放maxsize個(gè)字符。該函數(shù)返回向strDest指向的字符串中放置的字符數(shù)。
函數(shù)strftime()的操作有些類似于sprintf():識(shí)別以百分號(hào)(%)開始的格式命令集合,格式化輸出結(jié)果放在一個(gè)字符串中。格式化命令說明串strDest中各種日期和時(shí)間信息的確切表示方法。格式串中的其他字符原樣放進(jìn)串中。格式命令列在下面,它們是區(qū)分大小寫的。
%a 星期幾的簡寫
%A 星期幾的全稱
%b 月分的簡寫
%B 月份的全稱
%c 標(biāo)準(zhǔn)的日期的時(shí)間串
%C 年份的后兩位數(shù)字
%d 十進(jìn)制表示的每月的第幾天
%D 月/天/年
%e 在兩字符域中,十進(jìn)制表示的每月的第幾天
%F 年-月-日
%g 年份的后兩位數(shù)字,使用基于周的年
%G 年分,使用基于周的年
%h 簡寫的月份名
%H 24小時(shí)制的小時(shí)
%I 12小時(shí)制的小時(shí)
%j 十進(jìn)制表示的每年的第幾天
%m 十進(jìn)制表示的月份
%M 十時(shí)制表示的分鐘數(shù)
%n 新行符
%p 本地的AM或PM的等價(jià)顯示
%r 12小時(shí)的時(shí)間
%R 顯示小時(shí)和分鐘:hh:mm
%S 十進(jìn)制的秒數(shù)
%t 水平制表符
%T 顯示時(shí)分秒:hh:mm:ss
%u 每周的第幾天,星期一為第一天 (值從0到6,星期一為0)
%U 第年的第幾周,把星期日做為第一天(值從0到53)
%V 每年的第幾周,使用基于周的年
%w 十進(jìn)制表示的星期幾(值從0到6,星期天為0)
%W 每年的第幾周,把星期一做為第一天(值從0到53)
%x 標(biāo)準(zhǔn)的日期串
%X 標(biāo)準(zhǔn)的時(shí)間串
%y 不帶世紀(jì)的十進(jìn)制年份(值從0到99)
%Y 帶世紀(jì)部分的十進(jìn)制年份
%z,%Z 時(shí)區(qū)名稱,如果不能得到時(shí)區(qū)名稱則返回空字符。
%% 百分號(hào)
如果想顯示現(xiàn)在是幾點(diǎn)了,并以12小時(shí)制顯示,就象下面這段程序:
#include “time.h”
#include “stdio.h”
int main(void)
{
struct tm *ptr;
time_t lt;
char str[80];
lt=time(NUL);
ptr=localtime(<);
strftime(str,100,"It is now %I %p",ptr);
printf(str);
return 0;
}
其運(yùn)行結(jié)果為:
It is now 4PM
而下面的程序則顯示當(dāng)前的完整日期:
#include <stdio.h>
#include <time.h>
void main( void )
{
struct tm *newtime;
char tmpbuf[128];
time_t lt1;
time( <1 );
newtime=localtime(<1);
strftime( tmpbuf, 128, "Today is %A, day %d of %B in the year %Y.\n", newtime);
printf(tmpbuf);
}
運(yùn)行結(jié)果:
Today is Saturday, day 30 of July in the year 2005.
4.5 計(jì)算持續(xù)時(shí)間的長度
有時(shí)候在實(shí)際應(yīng)用中要計(jì)算一個(gè)事件持續(xù)的時(shí)間長度,比如計(jì)算打字速度。在第1節(jié)計(jì)時(shí)部分中,我已經(jīng)用clock函數(shù)舉了一個(gè)例子。Clock()函數(shù)可以精確到毫秒級(jí)。同時(shí),我們也可以使用difftime()函數(shù),但它只能精確到秒。該函數(shù)的定義如下:
double difftime(time_t time1, time_t time0);
雖然該函數(shù)返回的以秒計(jì)算的時(shí)間間隔是double類型的,但這并不說明該時(shí)間具有同double一樣的精確度,這是由它的參數(shù)覺得的(time_t是以秒為單位計(jì)算的)。比如下面一段程序:
#include "time.h"
#include "stdio.h"
#include "stdlib.h"
int main(void)
{
time_t start,end;
start = time(NUL);
system("pause");
end = time(NUL);
printf("The pause used %f seconds.\n",difftime(end,start));//<-
system("pause");
return 0;
}
運(yùn)行結(jié)果為:
請(qǐng)按任意鍵繼續(xù). . .
The pause used 2.000000 seconds.
請(qǐng)按任意鍵繼續(xù). . .
可以想像,暫停的時(shí)間并不那么巧是整整2秒鐘。其實(shí),你將上面程序的帶有“//<-”注釋的一行用下面的一行代碼替換:
printf("The pause used %f seconds.\n",end-start);
其運(yùn)行結(jié)果是一樣的。
4.6 分解時(shí)間轉(zhuǎn)化為日歷時(shí)間
這里說的分解時(shí)間就是以年、月、日、時(shí)、分、秒等分量保存的時(shí)間結(jié)構(gòu),在C/C++中是tm結(jié)構(gòu)。我們可以使用mktime()函數(shù)將用tm結(jié)構(gòu)表示的時(shí)間轉(zhuǎn)化為日歷時(shí)間。其函數(shù)原型如下:
time_t mktime(struct tm * timeptr);
其返回值就是轉(zhuǎn)化后的日歷時(shí)間。這樣我們就可以先制定一個(gè)分解時(shí)間,然后對(duì)這個(gè)時(shí)間進(jìn)行操作了,下面的例子可以計(jì)算出1997年7月1日是星期幾:
#include "time.h"
#include "stdio.h"
#include "stdlib.h"
int main(void)
{
struct tm t;
time_t t_of_day;
t.tm_year=1997-1900;
t.tm_mon=6;
t.tm_mday=1;
t.tm_hour=0;
t.tm_min=0;
t.tm_sec=1;
t.tm_isdst=0;
t_of_day=mktime(&t);
printf(ctime(&t_of_day));
return 0;
}
運(yùn)行結(jié)果:
Tue Jul 01 00:00:01 1997
現(xiàn)在注意了,有了mktime()函數(shù),是不是我們可以操作現(xiàn)在之前的任何時(shí)間呢?你可以通過這種辦法算出1945年8月15號(hào)是星期幾嗎?答案是否定的。因?yàn)檫@個(gè)時(shí)間在1970年1月1日之前,所以在大多數(shù)編譯器中,這樣的程序雖然可以編譯通過,但運(yùn)行時(shí)會(huì)異常終止。
posted @
2007-04-05 23:53 frank.sunny 閱讀(978) |
評(píng)論 (0) |
編輯 收藏
由switch選擇結(jié)構(gòu)理解局部變量
函數(shù)體內(nèi)部自定義變量,稱為局部變量,存儲(chǔ)于棧(stack)中,由編譯器自動(dòng)分配和釋放,局部變量的生存期(或者說作用域)是當(dāng)前函數(shù)內(nèi)部,使用時(shí)必須初始化,否則其值將不定。以前對(duì)局部變量的定義也就是這么多,而且也就那么在用。近期碰到如下一個(gè)問題:
void func( void )
{
int x = 2;
switch ( x )
{
int m =0; //initialization skipped by case0,case1,case2,default
case 0 :
int i = 0; //initialization skipped by case1,case2,default
{ int j = 1; } // OK, initialized in enclosing block
break;
case 1 :
break;
case 2:
break;
default:
int k = 1; // OK, initialization not skipped
}
}
遇到這個(gè)問題,網(wǎng)上的解答很多,很多人覺得switch內(nèi)不能定義局部變量,這個(gè)明顯是不對(duì)的。因?yàn)槲野汛a改成以下形式后就完全可以用了。
void func( void )
{
int x = 2;
switch ( x )
{
int m;
m = 0; //without execute;
case 0:
int i;
i = 0;
{ int j = 1; } // OK, initialized in enclosing block
printf("%d %d\n", m, i);
break;
case 1:
i = 1;
printf("%d %d\n", m, i);
break;
case 2:
i = 2;
printf("%d %d\n", m, i);
break;
default:
int k = 1; // OK, initialization not skipped
}
}
編譯時(shí)有一個(gè)warning,即“local variable 'm' used without having been initialized”,執(zhí)行結(jié)果為:-858993460 2
因此switch內(nèi)不但可以定義變量,而且也不用像很多人所說的在case內(nèi)遇到要用變量時(shí)一定要用{}括起來,不過嚴(yán)格的說不用{}擴(kuò)起來的變量是是屬于整個(gè)switch塊結(jié)構(gòu)的,為此編程一定要將新增變量作用域限定在case內(nèi)就必須要用{}。
通過switch···case結(jié)構(gòu),對(duì)局部變量的聲明、定義以及初始化等概念可以有一個(gè)比較清晰的認(rèn)識(shí)。我的理解就是:聲明語句不管是放在哪里,其編譯時(shí)都是將其置頂?shù)綁K的頭部,如int k雖然在default中,但是這個(gè)變量的聲明就在switch的{}內(nèi),其生存期與變量m等同,只是由于前面沒有聲明,所以default之前不能用。
posted @
2007-04-05 23:25 frank.sunny 閱讀(1463) |
評(píng)論 (0) |
編輯 收藏
?
關(guān)于內(nèi)存映射文件處理
今天看到一篇文章講內(nèi)存映射文件的處理,雖然自己沒有處理過如此大的文件系統(tǒng),但是好奇就也看了下,誰知道自己以后會(huì)不會(huì)用到或考到這方面的知識(shí)。所以就給自己
mark
一下,增加點(diǎn)自己的印象。
首先,通過
CreateFile()
函數(shù)來創(chuàng)建或打開一個(gè)文件內(nèi)核對(duì)象,這個(gè)對(duì)象標(biāo)識(shí)了磁盤上將要用作內(nèi)存映射文件的文件。(其實(shí)是獲取文件句柄)
其次,通過
CreateFileMapping()
函數(shù)來為剛才創(chuàng)建的文件內(nèi)核對(duì)象創(chuàng)建一個(gè)文件映射內(nèi)核對(duì)象并告訴系統(tǒng)文件的尺寸以及訪問文件的方式。(獲取文件映射內(nèi)核對(duì)象的句柄)
再次,通過
MapViewOfFile()
函數(shù)將文件內(nèi)核映射對(duì)象添加到進(jìn)程中。(獲取映射內(nèi)核對(duì)象的指針)
接著,程序就可以通過指針進(jìn)行常規(guī)的文件讀取了,這里的操作就和文件操作一樣,不做展開。
用完之后,還得回收,先用
UnmapViewOfFile()
將釋放映射內(nèi)核對(duì)象指針,然后通過
CloseHandle
關(guān)閉之前創(chuàng)建的文件映射內(nèi)核對(duì)象句柄和文件內(nèi)核對(duì)象句柄。
?
以下是我找到的文章的出處:
http://newcactus.bokee.com/viewdiary.15316244.html
下面純粹是粘貼別人的作品:
VC++
中使用內(nèi)存映射文件處理大文件
摘要:
本文給出了一種方便實(shí)用的解決大文件的讀取、存儲(chǔ)等處理的方法,并結(jié)合相關(guān)程序代碼對(duì)具體的實(shí)現(xiàn)過程進(jìn)行了介紹。
引言
文件操作是應(yīng)用程序最為基本的功能之一,Win32 API和MFC均提供有支持文件處理的函數(shù)和類,常用的有Win32 API的CreateFile()、WriteFile()、ReadFile()和MFC提供的CFile類等。一般來說,以上這些函數(shù)可以滿足大多數(shù)場合的要求,但是對(duì)于某些特殊應(yīng)用領(lǐng)域所需要的動(dòng)輒幾十GB、幾百GB、乃至幾TB的海量存儲(chǔ),再以通常的文件處理方法進(jìn)行處理顯然是行不通的。目前,對(duì)于上述這種大文件的操作一般是以內(nèi)存映射文件的方式來加以處理的,本文下面將針對(duì)這種Windows核心編程技術(shù)展開討論。
內(nèi)存映射文件
內(nèi)存映射文件與虛擬內(nèi)存有些類似,通過內(nèi)存映射文件可以保留一個(gè)地址空間的區(qū)域,同時(shí)將物理存儲(chǔ)器提交給此區(qū)域,只是內(nèi)存文件映射的物理存儲(chǔ)器來自一個(gè)已經(jīng)存在于磁盤上的文件,而非系統(tǒng)的頁文件,而且在對(duì)該文件進(jìn)行操作之前必須首先對(duì)文件進(jìn)行映射,就如同將整個(gè)文件從磁盤加載到內(nèi)存。由此可以看出,使用內(nèi)存映射文件處理存儲(chǔ)于磁盤上的文件時(shí),將不必再對(duì)文件執(zhí)行I/O操作,這意味著在對(duì)文件進(jìn)行處理時(shí)將不必再為文件申請(qǐng)并分配緩存,所有的文件緩存操作均由系統(tǒng)直接管理,由于取消了將文件數(shù)據(jù)加載到內(nèi)存、數(shù)據(jù)從內(nèi)存到文件的回寫以及釋放內(nèi)存塊等步驟,使得內(nèi)存映射文件在處理大數(shù)據(jù)量的文件時(shí)能起到相當(dāng)重要的作用。另外,實(shí)際工程中的系統(tǒng)往往需要在多個(gè)進(jìn)程之間共享數(shù)據(jù),如果數(shù)據(jù)量小,處理方法是靈活多變的,如果共享數(shù)據(jù)容量巨大,那么就需要借助于內(nèi)存映射文件來進(jìn)行。實(shí)際上,內(nèi)存映射文件正是解決本地多個(gè)進(jìn)程間數(shù)據(jù)共享的最有效方法。
內(nèi)存映射文件并不是簡單的文件I/O操作,實(shí)際用到了Windows的核心編程技術(shù)--內(nèi)存管理。所以,如果想對(duì)內(nèi)存映射文件有更深刻的認(rèn)識(shí),必須對(duì)Windows操作系統(tǒng)的內(nèi)存管理機(jī)制有清楚的認(rèn)識(shí),內(nèi)存管理的相關(guān)知識(shí)非常復(fù)雜,超出了本文的討論范疇,在此就不再贅述,感興趣的讀者可以參閱其他相關(guān)書籍。下面給出使用內(nèi)存映射文件的一般方法:
首先要通過CreateFile()函數(shù)來創(chuàng)建或打開一個(gè)文件內(nèi)核對(duì)象,這個(gè)對(duì)象標(biāo)識(shí)了磁盤上將要用作內(nèi)存映射文件的文件。在用CreateFile()將文件映像在物理存儲(chǔ)器的位置通告給操作系統(tǒng)后,只指定了映像文件的路徑,映像的長度還沒有指定。為了指定文件映射對(duì)象需要多大的物理存儲(chǔ)空間還需要通過CreateFileMapping()函數(shù)來創(chuàng)建一個(gè)文件映射內(nèi)核對(duì)象以告訴系統(tǒng)文件的尺寸以及訪問文件的方式。在創(chuàng)建了文件映射對(duì)象后,還必須為文件數(shù)據(jù)保留一個(gè)地址空間區(qū)域,并把文件數(shù)據(jù)作為映射到該區(qū)域的物理存儲(chǔ)器進(jìn)行提交。由MapViewOfFile()函數(shù)負(fù)責(zé)通過系統(tǒng)的管理而將文件映射對(duì)象的全部或部分映射到進(jìn)程地址空間。此時(shí),對(duì)內(nèi)存映射文件的使用和處理同通常加載到內(nèi)存中的文件數(shù)據(jù)的處理方式基本一樣,在完成了對(duì)內(nèi)存映射文件的使用時(shí),還要通過一系列的操作完成對(duì)其的清除和使用過資源的釋放。這部分相對(duì)比較簡單,可以通過UnmapViewOfFile()完成從進(jìn)程的地址空間撤消文件數(shù)據(jù)的映像、通過CloseHandle()關(guān)閉前面創(chuàng)建的文件映射對(duì)象和文件對(duì)象。
內(nèi)存映射文件相關(guān)函數(shù)
在使用內(nèi)存映射文件時(shí),所使用的API函數(shù)主要就是前面提到過的那幾個(gè)函數(shù),下面分別對(duì)其進(jìn)行介紹:
HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
|
函數(shù)CreateFile()即使是在普通的文件操作時(shí)也經(jīng)常用來創(chuàng)建、打開文件,在處理內(nèi)存映射文件時(shí),該函數(shù)來創(chuàng)建/打開一個(gè)文件內(nèi)核對(duì)象,并將其句柄返回,在調(diào)用該函數(shù)時(shí)需要根據(jù)是否需要數(shù)據(jù)讀寫和文件的共享方式來設(shè)置參數(shù)dwDesiredAccess和dwShareMode,錯(cuò)誤的參數(shù)設(shè)置將會(huì)導(dǎo)致相應(yīng)操作時(shí)的失敗。
HANDLE CreateFileMapping(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCTSTR lpName);
|
CreateFileMapping()函數(shù)創(chuàng)建一個(gè)文件映射內(nèi)核對(duì)象,通過參數(shù)hFile指定待映射到進(jìn)程地址空間的文件句柄(該句柄由CreateFile()函數(shù)的返回值獲取)。由于內(nèi)存映射文件的物理存儲(chǔ)器實(shí)際是存儲(chǔ)于磁盤上的一個(gè)文件,而不是從系統(tǒng)的頁文件中分配的內(nèi)存,所以系統(tǒng)不會(huì)主動(dòng)為其保留地址空間區(qū)域,也不會(huì)自動(dòng)將文件的存儲(chǔ)空間映射到該區(qū)域,為了讓系統(tǒng)能夠確定對(duì)頁面采取何種保護(hù)屬性,需要通過參數(shù)flProtect來設(shè)定,保護(hù)屬性PAGE_READONLY、PAGE_READWRITE和PAGE_WRITECOPY分別表示文件映射對(duì)象被映射后,可以讀取、讀寫文件數(shù)據(jù)。在使用PAGE_READONLY時(shí),必須確保CreateFile()采用的是GENERIC_READ參數(shù);PAGE_READWRITE則要求CreateFile()采用的是GENERIC_READ|GENERIC_WRITE參數(shù);至于屬性PAGE_WRITECOPY則只需要確保CreateFile()采用了GENERIC_READ和GENERIC_WRITE其中之一即可。DWORD型的參數(shù)dwMaximumSizeHigh和dwMaximumSizeLow也是相當(dāng)重要的,指定了文件的最大字節(jié)數(shù),由于這兩個(gè)參數(shù)共64位,因此所支持的最大文件長度為16EB,幾乎可以滿足任何大數(shù)據(jù)量文件處理場合的要求。
LPVOID MapViewOfFile(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, DWORD dwNumberOfBytesToMap);
|
MapViewOfFile()函數(shù)負(fù)責(zé)把文件數(shù)據(jù)映射到進(jìn)程的地址空間,參數(shù)hFileMappingObject為CreateFileMapping()返回的文件映像對(duì)象句柄。參數(shù)dwDesiredAccess則再次指定了對(duì)文件數(shù)據(jù)的訪問方式,而且同樣要與CreateFileMapping()函數(shù)所設(shè)置的保護(hù)屬性相匹配。雖然這里一再對(duì)保護(hù)屬性進(jìn)行重復(fù)設(shè)置看似多余,但卻可以使應(yīng)用程序能更多的對(duì)數(shù)據(jù)的保護(hù)屬性實(shí)行有效控制。MapViewOfFile()函數(shù)允許全部或部分映射文件,在映射時(shí),需要指定數(shù)據(jù)文件的偏移地址以及待映射的長度。其中,文件的偏移地址由DWORD型的參數(shù)dwFileOffsetHigh和dwFileOffsetLow組成的64位值來指定,而且必須是操作系統(tǒng)的分配粒度的整數(shù)倍,對(duì)于Windows操作系統(tǒng),分配粒度固定為64KB。當(dāng)然,也可以通過如下代碼來動(dòng)態(tài)獲取當(dāng)前操作系統(tǒng)的分配粒度:
SYSTEM_INFO sinf; GetSystemInfo(&sinf); DWORD dwAllocationGranularity = sinf.dwAllocationGranularity;
|
參數(shù)dwNumberOfBytesToMap指定了數(shù)據(jù)文件的映射長度,這里需要特別指出的是,對(duì)于Windows 9x操作系統(tǒng),如果MapViewOfFile()無法找到足夠大的區(qū)域來存放整個(gè)文件映射對(duì)象,將返回空值(NULL);但是在Windows 2000下,MapViewOfFile()只需要為必要的視圖找到足夠大的一個(gè)區(qū)域即可,而無須考慮整個(gè)文件映射對(duì)象的大小。
在完成對(duì)映射到進(jìn)程地址空間區(qū)域的文件處理后,需要通過函數(shù)UnmapViewOfFile()完成對(duì)文件數(shù)據(jù)映像的釋放,該函數(shù)原型聲明如下:
BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);
|
唯一的參數(shù)lpBaseAddress指定了返回區(qū)域的基地址,必須將其設(shè)定為MapViewOfFile()的返回值。在使用了函數(shù)MapViewOfFile()之后,必須要有對(duì)應(yīng)的UnmapViewOfFile()調(diào)用,否則在進(jìn)程終止之前,保留的區(qū)域?qū)o法釋放。除此之外,前面還曾由CreateFile()和CreateFileMapping()函數(shù)創(chuàng)建過文件內(nèi)核對(duì)象和文件映射內(nèi)核對(duì)象,在進(jìn)程終止之前有必要通過CloseHandle()將其釋放,否則將會(huì)出現(xiàn)資源泄漏的問題。
除了前面這些必須的API函數(shù)之外,在使用內(nèi)存映射文件時(shí)還要根據(jù)情況來選用其他一些輔助函數(shù)。例如,在使用內(nèi)存映射文件時(shí),為了提高速度,系統(tǒng)將文件的數(shù)據(jù)頁面進(jìn)行高速緩存,而且在處理文件映射視圖時(shí)不立即更新文件的磁盤映像。為解決這個(gè)問題可以考慮使用FlushViewOfFile()函數(shù),該函數(shù)強(qiáng)制系統(tǒng)將修改過的數(shù)據(jù)部分或全部重新寫入磁盤映像,從而可以確保所有的數(shù)據(jù)更新能及時(shí)保存到磁盤。
使用內(nèi)存映射文件處理大文件應(yīng)用示例
下面結(jié)合一個(gè)具體的實(shí)例來進(jìn)一步講述內(nèi)存映射文件的使用方法。該實(shí)例從端口接收數(shù)據(jù),并實(shí)時(shí)將其存放于磁盤,由于數(shù)據(jù)量大(幾十GB),在此選用內(nèi)存映射文件進(jìn)行處理。下面給出的是位于工作線程MainProc中的部分主要代碼,該線程自程序運(yùn)行時(shí)啟動(dòng),當(dāng)端口有數(shù)據(jù)到達(dá)時(shí)將會(huì)發(fā)出事件hEvent[0],WaitForMultipleObjects()函數(shù)等待到該事件發(fā)生后將接收到的數(shù)據(jù)保存到磁盤,如果終止接收將發(fā)出事件hEvent[1],事件處理過程將負(fù)責(zé)完成資源的釋放和文件的關(guān)閉等工作。下面給出此線程處理函數(shù)的具體實(shí)現(xiàn)過程:
…… //
創(chuàng)建文件內(nèi)核對(duì)象,其句柄保存于hFile HANDLE hFile = CreateFile("Recv1.zip", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
// 創(chuàng)建文件映射內(nèi)核對(duì)象,句柄保存于hFileMapping HANDLE hFileMapping = CreateFileMapping(hFile,NULL,PAGE_READWRITE, 0, 0x4000000, NULL); // 釋放文件內(nèi)核對(duì)象 CloseHandle(hFile);
// 設(shè)定大小、偏移量等參數(shù) __int64 qwFileSize = 0x4000000; __int64 qwFileOffset = 0; __int64 T = 600 * sinf.dwAllocationGranularity; DWORD dwBytesInBlock = 1000 * sinf.dwAllocationGranularity;
// 將文件數(shù)據(jù)映射到進(jìn)程的地址空間 PBYTE pbFile = (PBYTE)MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, (DWORD)(qwFileOffset>>32), (DWORD)(qwFileOffset&0xFFFFFFFF), dwBytesInBlock); while(bLoop) { // 捕獲事件hEvent[0]和事件hEvent[1] DWORD ret = WaitForMultipleObjects(2, hEvent, FALSE, INFINITE); ret -= WAIT_OBJECT_0; switch (ret) { // 接收數(shù)據(jù)事件觸發(fā) case 0: // 從端口接收數(shù)據(jù)并保存到內(nèi)存映射文件 nReadLen=syio_Read(port[1], pbFile + qwFileOffset, QueueLen); qwFileOffset += nReadLen;
// 當(dāng)數(shù)據(jù)寫滿60%時(shí),為防數(shù)據(jù)溢出,需要在其后開辟一新的映射視圖 if (qwFileOffset > T) { T = qwFileOffset + 600 * sinf.dwAllocationGranularity; UnmapViewOfFile(pbFile); pbFile = (PBYTE)MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, (DWORD)(qwFileOffset>>32), (DWORD)(qwFileOffset&0xFFFFFFFF), dwBytesInBlock); } break;
// 終止事件觸發(fā) case 1: bLoop = FALSE;
// 從進(jìn)程的地址空間撤消文件數(shù)據(jù)映像 UnmapViewOfFile(pbFile);
// 關(guān)閉文件映射對(duì)象 CloseHandle(hFileMapping); break; } } …
|
在終止事件觸發(fā)處理過程中如果只簡單的執(zhí)行UnmapViewOfFile()和CloseHandle()函數(shù)將無法正確標(biāo)識(shí)文件的實(shí)際大小,即如果開辟的內(nèi)存映射文件為30GB,而接收的數(shù)據(jù)只有14GB,那么上述程序執(zhí)行完后,保存的文件長度仍是30GB。也就是說,在處理完成后還要再次通過內(nèi)存映射文件的形式將文件恢復(fù)到實(shí)際大小,下面是實(shí)現(xiàn)此要求的主要代碼:
//
創(chuàng)建另外一個(gè)文件內(nèi)核對(duì)象 hFile2 = CreateFile("Recv.zip", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
// 以實(shí)際數(shù)據(jù)長度創(chuàng)建另外一個(gè)文件映射內(nèi)核對(duì)象 hFileMapping2 = CreateFileMapping(hFile2, NULL, PAGE_READWRITE, 0, (DWORD)(qwFileOffset&0xFFFFFFFF), NULL);
// 關(guān)閉文件內(nèi)核對(duì)象 CloseHandle(hFile2);
// 將文件數(shù)據(jù)映射到進(jìn)程的地址空間 pbFile2 = (PBYTE)MapViewOfFile(hFileMapping2, FILE_MAP_ALL_ACCESS, 0, 0, qwFileOffset);
// 將數(shù)據(jù)從原來的內(nèi)存映射文件復(fù)制到此內(nèi)存映射文件 memcpy(pbFile2, pbFile, qwFileOffset);
file://從進(jìn)程的地址空間撤消文件數(shù)據(jù)映像 UnmapViewOfFile(pbFile); UnmapViewOfFile(pbFile2);
// 關(guān)閉文件映射對(duì)象 CloseHandle(hFileMapping); CloseHandle(hFileMapping2);
// 刪除臨時(shí)文件 DeleteFile("Recv1.zip");
|
結(jié)論
經(jīng)實(shí)際測(cè)試,內(nèi)存映射文件在處理大數(shù)據(jù)量文件時(shí)表現(xiàn)出了良好的性能,比通常使用CFile類和ReadFile()和WriteFile()等函數(shù)的文件處理方式具有明顯的優(yōu)勢(shì)。本文所述代碼在Windows 98下由Microsoft Visual C++ 6.0編譯通過。
?
posted @
2007-03-30 21:16 frank.sunny 閱讀(4644) |
評(píng)論 (0) |
編輯 收藏
摘要: 聞道有先后,術(shù)業(yè)有專功
?
對(duì)于網(wǎng)絡(luò),我似乎是后知后覺的多些。特別是畢業(yè)以后的一年半多的工作生涯中,完全沒有好好的接觸過網(wǎng)絡(luò)這個(gè)資源。一開題就跑題了,言歸正傳,本科畢業(yè)后一段時(shí)間才知道有林銳這么一個(gè)人,還拜讀了他寫的《大學(xué)十年》,當(dāng)時(shí)感想頗多,但是沒有記下來,所以現(xiàn)在想來好像也沒有其它太大的感想,現(xiàn)在記得當(dāng)時(shí)可能想做的...
閱讀全文
posted @
2007-03-30 20:40 frank.sunny 閱讀(2284) |
評(píng)論 (4) |
編輯 收藏
網(wǎng)訊筆試歸來
昨天去網(wǎng)訊(杭州)筆試了,做了下筆試題,感覺題目都不難,但是自己做的的確不怎么樣,估計(jì)是沒機(jī)會(huì)去了,不過暫時(shí)還是先把幾道自己還記得的題目,寫出來,總結(jié)下,以做復(fù)習(xí)。
1、 要求自己實(shí)現(xiàn) String 類,給出了 String 類的以下頭文件類聲明
class String
{
public:
String(const char *m_char = NULL);
String(const String & Str);
String& operator = (const String &Str);
~String();
private:
char * m_Data;
};
關(guān)于 String 類的筆試題,以前看林銳的隨筆時(shí)聽說他在微軟面試時(shí)曾碰到那么一道題目,我自己也沒有真的下筆去做過,平常都是拿來就用的,這次自己碰到,才知道會(huì)死得那么慘,反正編得不堪入目(我就不拿出來獻(xiàn)丑了),下面是我回來后,自己重新寫的答案。
String::String(const char* m_char)
{
int m_nLength = strlen(m_char) + 1;
if (m_Data != NULL)
{
delete [] m_Data;
m_Data = NULL;
}// 以上判斷是否必要 ??
m_Data = new char[m_nLength];
memcpy(m_Data, m_char, m_nLength);
}
String::String(const String &Str)
{
int m_nLength = strlen(Str.m_Data) + 1;// 以前真的不知道,原來對(duì)象的私有變量
// 在類的實(shí)現(xiàn)代碼中也是可以訪問的
if (m_Data != NULL)
{
delete [] m_Data;
m_Data = NULL;
}// 以上判斷是否必要 ??
m_Data = new char[m_nLength];
memcpy(m_Data, Str.m_Data, m_nLength);
}
String& String::operator = (const String& Str)
{
if(this == &Str)
return *this;
int m_nLength = strlen(Str.m_Data) + 1;
if (m_Data != NULL)
{
delete [] m_Data;
m_Data = NULL;
}// 以上判斷是否必要 ??
m_Data = new char[m_nLength];
memcpy(m_Data, Str.m_Data, m_nLength);
return *this;
}
String::~String()
{
if (m_Data != NULL)
{
delete [] m_Data;
m_Data = NULL;
}
}
2、 關(guān)于內(nèi)存分配
這個(gè)題目很簡單,就給了一個(gè)函數(shù),然后問函數(shù)內(nèi)的局部變量存放在哪里,我也不知道為什么當(dāng)時(shí)會(huì)選擇 heap( 堆 ) ,下面再把幾個(gè)概念羅列出來:
1. 堆區(qū)( heap ):由程序員申請(qǐng)分配和釋放,屬動(dòng)態(tài)內(nèi)存分配方式,若程序員不釋放,程序結(jié)束時(shí)可能會(huì)由 OS 回收。不過這個(gè)內(nèi)存分配很容易引起問題,如果申請(qǐng)的內(nèi)存不釋放就會(huì)造成內(nèi)存泄漏;如果釋放的不是所要釋放的內(nèi)存,則輕者引起程序運(yùn)行結(jié)果出錯(cuò),重者系統(tǒng)崩潰。
2. 棧區(qū)( stack ):編譯器自動(dòng)分配釋放,存放函數(shù)的形參值、局部變量的值,也是屬于動(dòng)態(tài)內(nèi)存分配方式,它由系統(tǒng)分配,所以執(zhí)行效率也高,不過自由度小,聲明時(shí)就得決定其具體大小。
3. 全局區(qū)(靜態(tài)區(qū))( static ):全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的,而且初始化的全局變量和靜態(tài)變量在一塊區(qū)域,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。程序結(jié)束后由系統(tǒng)釋放,所以也不會(huì)造成內(nèi)存問題。
除了以上的變量外,還有兩類存放位置,文字常量區(qū)和程序代碼區(qū),兩者都是由系統(tǒng)分配和釋放,且文字常量區(qū)和前面三區(qū)合成為程序數(shù)據(jù)區(qū),與程序代碼區(qū)相對(duì)應(yīng)。
3、 關(guān)于類繼承的構(gòu)造和析構(gòu)函數(shù)
class Base
{
public:
Base(){cout<< "Base" <<endl;};
~Base(){cout<<"~Base"<<endl;};
protected:
private:
};
class First:public Base
{
public:
First(){cout << "First" << endl;};
~First(){cout << "~First" <<endl;};
};
int main()
{
Base *a = new First;
delete a;
}
問程序的輸出會(huì)是什么?
結(jié)果很簡單,也就是 Base
First
~Base
其它還有一個(gè)關(guān)于 & 的題目,把我搞的云里霧里的,還要再看些東西才知道怎么來解釋。
posted @
2007-03-04 21:53 frank.sunny 閱讀(3379) |
評(píng)論 (10) |
編輯 收藏
摘要: 雖然自己用多線程編程用過一陣子,但是未曾仔細(xì)了解過概念,用的也是亂亂的,今天看到一篇線程總結(jié)的文章,感覺講的很好,Windows下的多線程也就是了解了線程的概念然后加一同步代碼就行了。
Windows平臺(tái)下的多線程編程
?
...
閱讀全文
posted @
2007-01-26 22:10 frank.sunny 閱讀(2536) |
評(píng)論 (0) |
編輯 收藏