嵌入式軟件C語(yǔ)言可靠性設(shè)計(jì)問題匯總_第1頁(yè)
嵌入式軟件C語(yǔ)言可靠性設(shè)計(jì)問題匯總_第2頁(yè)
嵌入式軟件C語(yǔ)言可靠性設(shè)計(jì)問題匯總_第3頁(yè)
嵌入式軟件C語(yǔ)言可靠性設(shè)計(jì)問題匯總_第4頁(yè)
嵌入式軟件C語(yǔ)言可靠性設(shè)計(jì)問題匯總_第5頁(yè)
已閱讀5頁(yè),還剩16頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

./嵌入式軟件可靠性設(shè)計(jì)問題集錦目錄TOC\o"1-4"\h\z\u1、程序員理解錯(cuò)誤21.1、英文標(biāo)點(diǎn)被誤寫成中文標(biāo)點(diǎn);21.2、+=與=+、-=與=-容易混21.3、程序員輸入錯(cuò)誤21.4、數(shù)組問題21.5、switch…case語(yǔ)句中的

break關(guān)鍵字31.5、變量賦值31.6、指針的加減運(yùn)算41.7、增量運(yùn)算符++和減量運(yùn)算符--52、編譯器語(yǔ)義檢查62.1、數(shù)據(jù)類型問題62.2、誤加標(biāo)點(diǎn)符號(hào)62.3、編譯器忽略掉多余的空格符和換行符62.4、數(shù)組越界。72.5、數(shù)組聲明具有外部鏈接時(shí)大小應(yīng)顯式聲明72.6、編譯器檢查不出數(shù)組越界82.7、編譯器與volatile限定符92.8、?定義為volatile的變量的作用過程112.9、局部變量必須顯式初始化113、不合理的優(yōu)先級(jí)123.1、常規(guī)使用可能引起誤會(huì)的運(yùn)算符134、隱式轉(zhuǎn)換和強(qiáng)制轉(zhuǎn)換134.1、有符號(hào)和無(wú)符號(hào)char和short類型自動(dòng)轉(zhuǎn)換134.2、混合數(shù)據(jù)類型運(yùn)算中會(huì)轉(zhuǎn)換成較高級(jí)別數(shù)據(jù)類型144.3、賦值語(yǔ)句計(jì)算結(jié)果被轉(zhuǎn)換成被賦予值的變量類型154.4、作為函數(shù)參數(shù)被傳遞時(shí)的數(shù)據(jù)類型轉(zhuǎn)換154.5、C語(yǔ)言強(qiáng)制類型轉(zhuǎn)換規(guī)則154.6、通用編程建議155、判錯(cuò)155.1、具有形參的函數(shù),需判斷傳遞來的實(shí)參是否合法。165.2、仔細(xì)檢查函數(shù)的返回值175.3、防止指針越界175.4、防止數(shù)組越界175.5、數(shù)學(xué)運(yùn)算185.6、其它可能出現(xiàn)運(yùn)行時(shí)錯(cuò)誤的地方206、容錯(cuò)206.1、關(guān)鍵數(shù)據(jù)多區(qū)備份,取數(shù)據(jù)采用"表決法"206.2、非易失性存儲(chǔ)器的數(shù)據(jù)存儲(chǔ)216.3、軟件鎖216.4、通信數(shù)據(jù)的檢錯(cuò)216.5、開關(guān)量輸入的檢測(cè)、確認(rèn)226.6、開關(guān)量輸出226.7、初始化信息的保存與恢復(fù)226.8、陷阱226.9、while循環(huán)226.10、系統(tǒng)自檢221、程序員理解錯(cuò)誤1.1、英文標(biāo)點(diǎn)被誤寫成中文標(biāo)點(diǎn);比較運(yùn)算符==誤寫成賦值運(yùn)算符=,代碼if<x=5>{…}本意是比較變量x是否等于常量5,但是誤將’==’寫成了’=’,if語(yǔ)句恒為真。如果在邏輯判斷表達(dá)式中出現(xiàn)賦值運(yùn)算符,現(xiàn)在的大多數(shù)編譯器會(huì)給出警告信息。并非所有程序員都會(huì)注意到這類警告,因此有經(jīng)驗(yàn)的程序員使用下面的代碼來避免此類錯(cuò)誤:

if<5==x>{…}將常量放在變量x的左邊,即使程序員誤將’==’寫成了’=’,編譯器會(huì)產(chǎn)生一個(gè)任誰(shuí)也不能無(wú)視的語(yǔ)法錯(cuò)誤信息:不可給常量賦值!1.2、+=與=+、-=與=-容易混復(fù)合運(yùn)算符會(huì)給程序帶來隱含Bug,如下所示代碼:

tmp=+1;該代碼本意是想表達(dá)tmp=tmp+1,但是將復(fù)合賦值運(yùn)算符+=誤寫成=+:將正整數(shù)常量1賦值給變量tmp。編譯器會(huì)欣然接受這類代碼,連警告都不會(huì)產(chǎn)生。-=與=-同理。類似的邏輯與&&、位與&、邏輯或||、位或|、邏輯非!、位取反~等。字母l和數(shù)字1、字母o和數(shù)字0也易混淆,這種情況可借助編譯器來糾正。1.3、程序員輸入錯(cuò)誤阿拉伯?dāng)?shù)值輸入,宏變量宏定義方式實(shí)現(xiàn)。防止多次錄入數(shù)字出現(xiàn)手誤。1.4、數(shù)組問題C語(yǔ)言數(shù)組下標(biāo)從0開始。定義inta[30],是從a[0]~a[29]。1.5、switch…case語(yǔ)句中的break關(guān)鍵字

switch…case語(yǔ)句可以很方便的實(shí)現(xiàn)多分支結(jié)構(gòu)。漏加break,引起順序執(zhí)行多個(gè)case語(yǔ)句;break關(guān)鍵字用于跳出最近的那層循環(huán)語(yǔ)句或者switch語(yǔ)句。network

code<>

{

switch<line>

{

case

THING1:

doit1<>;

break;

case

THING2:

if<x==STUFF>

{

do_first_stuff<>;

if<y==OTHER_STUFF>

break;

do_later_stuff<>;

}

initialize_modes_pointer<>;

break;

default:

processing<>;

}

use_modes_pointer<>;

}

1.5、變量賦值int

a=34intb=034

變量a和b相等嗎?No。以’0x’為前綴的16進(jìn)制常量,10進(jìn)制常量不需要前綴,數(shù)字’0’為前綴的8進(jìn)制。誤用8進(jìn)制的例子,最后一個(gè)數(shù)組元素賦值錯(cuò)誤:a[0]=106;

/*十進(jìn)制數(shù)106*/a[1]=112;

/*十進(jìn)制數(shù)112*/a[2]=052;

1.6、指針的加減運(yùn)算下面的代碼運(yùn)行在32位ARM架構(gòu)上,執(zhí)行后,a和p的值?int

a=1;

int

*p=<int*>0x00001000;

a=a+1;

p=p+1;

a=2,但是p的結(jié)果是0x00001004。指針p加1后,p的值增加了4。原因是指針做加減運(yùn)算時(shí)是以指針的數(shù)據(jù)類型為單位。p+1實(shí)際上是p+1*sizeof<int>。不理解這一點(diǎn),在使用指針直接操作數(shù)據(jù)時(shí)極易犯錯(cuò)。比如下面對(duì)連續(xù)RAM初始化零操作代碼:unsigned

int

*pRAMaddr;

//定義地址指針變量for<pRAMaddr=StartAddr;pRAMaddr<EndAddr;pRAMaddr+=4>

{

*pRAMaddr=0x00000000;

//指定RAM地址清零}

由于pRAMaddr是一個(gè)指針變量,所以pRAMaddr+=4代碼其實(shí)使pRAMaddr偏移了4*sizeof<int>=16個(gè)字節(jié),所以每執(zhí)行一次for循環(huán),會(huì)使變量pRAMaddr偏移16個(gè)字節(jié)空間,但只有4字節(jié)空間被初始化為零。其它的12字節(jié)數(shù)據(jù)的內(nèi)容,在大多數(shù)架構(gòu)處理器中都會(huì)是隨機(jī)數(shù)。對(duì)于sizeof<>,這里強(qiáng)調(diào)兩點(diǎn),第一它是一個(gè)關(guān)鍵字,而不是函數(shù),并且它默認(rèn)返回?zé)o符號(hào)整型數(shù)據(jù)〔無(wú)符號(hào)?。?;第二,使用sizeof獲取數(shù)組長(zhǎng)度時(shí),不要對(duì)指針應(yīng)用sizeof操作符,比如下面的例子:void

ClearRAM<char

array[]>

{

int

i

;

for<i=0;i<sizeof<array>/sizeof<array[0]>;i++>//array實(shí)際上是指針/*for<i=0;i<sizeof<array[20]>/sizeof<array[0]>;i++>*/

{

array[i]=0x00;

}

}

int

main<void>

{

char

Fle[20];

ClearRAM<Fle>;

//只能清除數(shù)組Fle中的前四個(gè)元素}

對(duì)于數(shù)組array[20],使用代碼sizeof<array[20]>/sizeof<array[0]>可以獲得數(shù)組的元素〔這里為20,但數(shù)組名和指針容易混淆,有且只有一種情況下可以當(dāng)做指針,就是數(shù)組名作為函數(shù)形參時(shí),數(shù)組名被認(rèn)為是指針。同時(shí)不能再兼任數(shù)組名。只有這種情況,數(shù)組名才可當(dāng)做指針,但容易引發(fā)風(fēng)險(xiǎn)。在ClearRAM函數(shù)內(nèi),作為形參的array[]不再是數(shù)組名了,而成了指針。sizeof<array>相當(dāng)于求指針變量占用的字節(jié)數(shù),在32位系統(tǒng)下,該值為4,sizeof<array>/sizeof<array[0]>的運(yùn)算結(jié)果也為4。所以在main函數(shù)中調(diào)用ClearRAM<Fle>,也只能清除數(shù)組Fle中的前四個(gè)元素了。1.7、增量運(yùn)算符++和減量運(yùn)算符--既可以做前綴也可以做后綴。前綴和后綴的區(qū)別在于值的增加或減少這一動(dòng)作發(fā)生的時(shí)間是不同的。作為前綴是先自加或自減然后做別的運(yùn)算,作為后綴時(shí),是先做運(yùn)算,之后再自加或自減。下面的例子可以很好的解釋前綴和后綴的區(qū)別。int

a=8,b=2,y;

y=a+++--b;

代碼執(zhí)行后,y的值是多少?y=<a++>+<--b>;

當(dāng)賦值給變量y時(shí),a的值為8,b的值為1,所以變量y的值為9;賦值完成后,變量a自加,a的值變?yōu)?,千萬(wàn)不要以為y的值為10。分解成兩條語(yǔ)句:y=a+<--b>;

a=a+1;

2、編譯器語(yǔ)義檢查蘿卜快了不洗泥。C語(yǔ)言足夠靈活且?guī)缀醪贿M(jìn)行任何運(yùn)行時(shí)檢查,比如數(shù)組越界、指針是否合法、運(yùn)算結(jié)果是否溢出等。C語(yǔ)言足夠靈活,對(duì)于一個(gè)數(shù)組a[30],它允許使用像a[-1]這樣的形式來快速獲取數(shù)組首元素所在地址前面的數(shù)據(jù);2.1、數(shù)據(jù)類型問題下面的兩個(gè)例子的問題是什么?unsignedchar

i;

for<i=0;i<256;i++>

{…}

unsignedchari;for<i=10;i>=0;i-->{…}無(wú)符號(hào)char類型,范圍為0~255,所以無(wú)符號(hào)char類型變量i永遠(yuǎn)小于256〔第一個(gè)for循環(huán)無(wú)限執(zhí)行,永遠(yuǎn)大于等于0〔第二個(gè)for循環(huán)無(wú)限執(zhí)行。2.2、誤加標(biāo)點(diǎn)符號(hào)if<a>b>;

//這里誤加了一個(gè)分號(hào)

a=b;

2.3、編譯器忽略掉多余的空格符和換行符if<n<3>

return//這里少加了一個(gè)分號(hào)logrec.data=x[0];

logrec.time=x[1];

logrec.code=x[2];

這段代碼的本意是n<3時(shí)程序直接返回,由于程序員的失誤,return少了一個(gè)結(jié)束分號(hào)。編譯器將它翻譯成返回表達(dá)式logrec.data=x[0]的結(jié)果,return后面即使是一個(gè)表達(dá)式也是C語(yǔ)言允許的。這樣當(dāng)n>=3時(shí),表達(dá)式logrec.data=x[0];就不會(huì)被執(zhí)行,給程序埋下了隱患。2.4、數(shù)組越界。代碼在硬件上運(yùn)行,一段時(shí)間后LCD顯示屏上的一個(gè)數(shù)字不正常的被改變。經(jīng)過一段時(shí)間的調(diào)試,問題被定位到下面的一段代碼中:int

SensorData[30];

for<i=30;i>0;i-->

{

SensorData[i]=...;

}

很多編譯器會(huì)對(duì)上述代碼產(chǎn)生警告:賦值超出數(shù)組界限。但并非所有程序員都對(duì)編譯器警告保持足夠敏感,而且編譯器也并不能檢查出數(shù)組越界的所有情況。2.5、數(shù)組聲明具有外部鏈接時(shí)大小應(yīng)顯式聲明模塊A中定義數(shù)組:int

SensorData[30];在模塊B中引用該數(shù)組,但由于你引用代碼并不規(guī)范,這里沒有顯式聲明數(shù)組大小,但編譯器也允許這么做:externint

SensorData[];

如果在模塊B中存在和上面一樣的代碼:for<i=30;i>0;i-->

{

SensorData[i]=…;

}

這次,編譯器不會(huì)給出警告信息,因?yàn)榫幾g器壓根就不知道數(shù)組的元素個(gè)數(shù)。所以,當(dāng)一個(gè)數(shù)組聲明為具有外部鏈接,它的大小應(yīng)該顯式聲明。2.6、編譯器檢查不出數(shù)組越界函數(shù)func<>的形參是一個(gè)數(shù)組形式,函數(shù)代碼簡(jiǎn)化如下所示:char

*

func<charSensorData[30]>

{

unsignedint

i;

for<i=30;i>0;i-->

{

SensorData[i]=…;

}

}

這個(gè)給SensorData[30]賦初值的語(yǔ)句,編譯器也是不給任何警告的。實(shí)際上,編譯器是將數(shù)組名SensorData隱含的轉(zhuǎn)化為指向數(shù)組第一個(gè)元素的指針,函數(shù)體是使用指針的形式來訪問數(shù)組的,它當(dāng)然也不會(huì)知道數(shù)組元素的個(gè)數(shù)了。造成這種局面的原因之一是C編譯器的作者們認(rèn)為指針代替數(shù)組可以提高程序效率,而且,還可以簡(jiǎn)化編譯器的復(fù)雜度。指針和數(shù)組容易給程序造成混亂,如何區(qū)分?可以將數(shù)組名等同于指針的情況有且只有一處,就是數(shù)組作為函數(shù)形參時(shí)。其它時(shí)候,數(shù)組名是數(shù)組名,指針是指針。另一個(gè)例子:編譯器同樣檢查不出數(shù)組越界。用數(shù)組來緩存通訊中的一幀數(shù)據(jù)。在通訊中斷中將接收的數(shù)據(jù)保存到數(shù)組中,直到一幀數(shù)據(jù)完全接收后再進(jìn)行處理。即使定義的數(shù)組長(zhǎng)度足夠長(zhǎng),接收數(shù)據(jù)的過程中也可能發(fā)生數(shù)組越界,特別是干擾嚴(yán)重時(shí)。這是由于外界的干擾破壞了數(shù)據(jù)幀的某些位,對(duì)一幀的數(shù)據(jù)長(zhǎng)度判斷錯(cuò)誤,接收的數(shù)據(jù)超出數(shù)組范圍,多余的數(shù)據(jù)會(huì)改寫數(shù)組相鄰的變量,造成系統(tǒng)崩潰。由于中斷事件的異步性,這類數(shù)組越界編譯器無(wú)法檢查到。如果局部數(shù)組越界,可能引發(fā)ARM架構(gòu)硬件異常。一設(shè)備用于接收無(wú)線傳感器的數(shù)據(jù),一次升級(jí)后,發(fā)現(xiàn)接收設(shè)備工作一段時(shí)間后會(huì)死機(jī)。調(diào)試表明ARM7處理器發(fā)生了硬件異常,異常處理代碼是一段死循環(huán)〔死機(jī)的直接原因。接收設(shè)備有一個(gè)硬件模塊用于接收無(wú)線傳感器的整包數(shù)據(jù)并存在自己的硬件緩沖區(qū)中,當(dāng)一幀數(shù)據(jù)接收完成后,使用外部中斷通知設(shè)備取數(shù)據(jù),外部中斷服務(wù)程序精簡(jiǎn)后如下所示:__irq

ExintHandler<void>

{

Unsignedchar

DataBuf[50];

GetData<DataBug>;

//從硬件緩沖區(qū)取一幀數(shù)據(jù)

}

由于存在多個(gè)無(wú)線傳感器近乎同時(shí)發(fā)送數(shù)據(jù)的可能,加之GetData<>函數(shù)保護(hù)力度不夠,數(shù)組DataBuf在取數(shù)據(jù)過程中發(fā)生越界。由于數(shù)組DataBuf為局部變量,被分配在堆棧中,同在此堆棧中的還有中斷發(fā)生時(shí)的運(yùn)行環(huán)境以及中斷返回地址。溢出的數(shù)據(jù)將這些數(shù)據(jù)破壞掉,中斷返回時(shí)PC指針可能變成一個(gè)不合法值,硬件異常由此產(chǎn)生。但如果被利用,則可作病毒。1988年,第一個(gè)網(wǎng)絡(luò)蠕蟲在一天之內(nèi)感染了2000到6000臺(tái)計(jì)算機(jī),利用的正是一個(gè)標(biāo)準(zhǔn)輸入庫(kù)函數(shù)的數(shù)組越界Bug。起因是一個(gè)標(biāo)準(zhǔn)輸入輸出庫(kù)函數(shù)gets<>,原來設(shè)計(jì)為從數(shù)據(jù)流中獲取一段文本,遺憾的是,gets<>函數(shù)沒有規(guī)定輸入文本的長(zhǎng)度。gets<>函數(shù)內(nèi)部定義了一個(gè)500字節(jié)的數(shù)組,攻擊者發(fā)送了大于500字節(jié)的數(shù)據(jù),利用溢出的數(shù)據(jù)修改了堆棧中的PC指針,從而獲取了系統(tǒng)權(quán)限。2.7、編譯器與volatile限定符源文件定義變量unsignedint

a;volatileunsignedint

a;頭文件聲明變量externunsignedlong

a;externunsignedint

a;編譯器提示語(yǔ)法錯(cuò)誤:變量‘a(chǎn)’聲明類型不一致編譯器:不給錯(cuò)誤信息〔或僅一條警告volatile屬于類型限定符,另一個(gè)常見的類型限定符是const關(guān)鍵字。限定符volatile在嵌入式軟件中至關(guān)重要,用來告訴編譯器不要優(yōu)化它修飾的變量。模塊A源文件中定義變量:volatileunsignedint

TimerCount=0;該變量用來在一個(gè)定時(shí)器服務(wù)程序中進(jìn)行軟件計(jì)時(shí):

TimerCount++;

//讀取IO端口1的值在模塊A的頭文件中,聲明變量:externunsignedint

TimerCount;

//這里漏掉了類型限定符volatile模塊B中,要使用TimerCount變量進(jìn)行精確的軟件延時(shí):#include

"...A.h"

//首先包含模塊A的頭文件

TimerCount=0;

while<TimerCount>=TIMER_VALUE>;

//延時(shí)一段時(shí)間

這是一個(gè)死循環(huán)。在模塊B中,變量TimerCount是被當(dāng)作unsignedint類型變量。由于寄存器速度遠(yuǎn)快于RAM,編譯器在使用非volatile限定變量時(shí),先將變量從RAM中拷貝到寄存器中,如果同一個(gè)代碼塊再次用到該變量,就不再?gòu)腞AM中拷貝數(shù)據(jù)而是直接使用之前寄存器備份值。代碼while<TimerCount>=TIMER_VALUE>中,變量TimerCount僅第一次執(zhí)行時(shí)被使用,之后都是使用的寄存器備份值,而這個(gè)寄存器值一直為0,所以程序無(wú)限循環(huán)。下面的流程圖說明了程序使用限定符volatile和不使用volatile的執(zhí)行過程使用了volatile會(huì)怎樣?2.8、?定義為volatile的變量的作用過程優(yōu)化器在用到這個(gè)變量時(shí)必須每次都小心地重新讀取這個(gè)變量的值,而不是使用保存在寄存器里的備份。下面左側(cè)函數(shù)有什么問題:代碼目的是用來返指針*ptr指向值的平方intsquare<volatileint*ptr>{return*ptr**ptr;}編譯器將產(chǎn)生類似下面的代碼intsquare<volatileint*ptr>{inta,b;a=*ptr;b=*ptr;returna*b;}正確代碼:longsquare<volatileint*ptr>{inta;a=*ptr;returna*a;}2.9、局部變量必須顯式初始化例1:unsigned

intGetTempValue<void>

{

unsigned

intsum;

//定義局部變量,保存總值。但其初值并不一定為0for<i=0;i<10;i++>

{

sum+=CollectTemp<>;

//函數(shù)CollectTemp可以得到當(dāng)前的溫度值

}

return

<sum/10>;

}

例2:char

*

GetData<void>

{

charbuffer[100];//局部數(shù)組

returnbuffer;

}

3、不合理的優(yōu)先級(jí)C語(yǔ)言有32個(gè)關(guān)鍵字,卻有34個(gè)運(yùn)算符。要記住所有運(yùn)算符的優(yōu)先級(jí)是困難的。不合理的#define會(huì)加重優(yōu)先級(jí)問題,讓問題變得更加隱蔽。#define

READSDA

IO0PIN&<1<<11>

//定義宏,讀IO口p0.11的端口狀態(tài)

//目的是判斷端口p0.11是否為高電平

if<READSDA==<1<<11>>

{

}

編譯器在編譯后將宏帶入,原if語(yǔ)句變?yōu)?if<IO0PIN&<1<<11>

==<1<<11>>

{

}

3.1、常規(guī)使用可能引起誤會(huì)的運(yùn)算符常被誤會(huì)的優(yōu)先級(jí)表達(dá)式誤認(rèn)為真實(shí)結(jié)果取值運(yùn)算符*、自增運(yùn)算符++,優(yōu)先級(jí)相同,但自右向左結(jié)合*p++<*p>++*<p++>成員選擇運(yùn)算符.高于取值運(yùn)算符**p.f<*p>.f*<p.f>數(shù)組下標(biāo)運(yùn)算符[]優(yōu)先級(jí),高于取值運(yùn)算符*int*ap[]int<*ap>[]ap為數(shù)組指針int*<ap[]>ap為指針數(shù)組函數(shù)<>優(yōu)先級(jí)高于取值運(yùn)算符*int*fp<>int<*fp><>fp為函數(shù)指針int*<fp<>>fp為函數(shù),返回指針等于==和不等于!=運(yùn)算符優(yōu)先級(jí)高于位操作運(yùn)算符&、^和|val&mask!=0<val&mask>!=0val&<mask!=0>等于==和不等于!=運(yùn)算符高于賦值運(yùn)算符=c=getchar<>!=EOF<c=getchar<>>!=EOFc=<getchar<>!=EOF>算數(shù)運(yùn)算符+和-優(yōu)先級(jí)高于移位運(yùn)算符<<和>>msb<<4+lsb<msb<<4>+lsbmsb<<<4+lsb>4、隱式轉(zhuǎn)換和強(qiáng)制轉(zhuǎn)換4.1、有符號(hào)和無(wú)符號(hào)char和short類型自動(dòng)轉(zhuǎn)換表達(dá)式中的有符號(hào)和無(wú)符號(hào)char和short類型都自動(dòng)被轉(zhuǎn)換為int類型.在需要的情況下,將自動(dòng)被轉(zhuǎn)換為unsignedint〔在short和int具有相同大小時(shí)。這稱為類型提升。提升在算數(shù)運(yùn)算中通常不會(huì)有什么大的壞處,但如果位運(yùn)算符~和<<,應(yīng)用在基本類型為unsignedchar或unsignedshort的操作數(shù),結(jié)果應(yīng)該立即強(qiáng)制轉(zhuǎn)換為unsignedchar或者unsignedshort類型〔取決于操作時(shí)使用的類型。uint8_t

port

=0xa5U;

/*typedefunsignedcharuint8_t*/uint8_t

result_8;

result_8=

<~port>

>>

4;

如果不了解表達(dá)式里的類型提升,認(rèn)為在運(yùn)算過程中變量port一直是unsignedchar類型的。期望的運(yùn)算過程:~port結(jié)果為0xa5,0xa5>>4結(jié)果為0x0a。實(shí)際上,result_8的結(jié)果卻是0xfa。在ARM結(jié)構(gòu)下,int類型為32位。變量port在運(yùn)算前被提升為int類型:~port結(jié)果為0xffffffa5,0xa5>>4結(jié)果為0x0ffffffa,賦值給變量result_8,發(fā)生類型截?cái)唷搽[性轉(zhuǎn)換,result_8=0xfa。正確表達(dá)式語(yǔ)句應(yīng)該為:

result_8=<unsignedchar><~port>>>4;

/*強(qiáng)制轉(zhuǎn)換*/4.2、混合數(shù)據(jù)類型運(yùn)算中會(huì)轉(zhuǎn)換成較高級(jí)別數(shù)據(jù)類型數(shù)據(jù)類型的級(jí)別從高到低的順序:longdouble、double、float、unsignedlonglong、longlong、unsignedlong、long、unsignedint、int。這種類型提升通常都是件好事,但往往有很多程序員不能真正理解這句話,從而做一些想當(dāng)然的事情,比如下面的例子,int類型表示16位。uint16_t

u16a

=

40000;

/*

16位無(wú)符號(hào)變量*/

uint16_t

u16b=

30000;

/*16位無(wú)符號(hào)變量*/

uint32_t

u32x;

/*32位無(wú)符號(hào)變量

*/

uint32_t

u32y;

u32x

=

u16a

+u16b;

/*

u32x

=

70000還是4464

?

*/

u32y

=<uint32_t><u16a

+

u16b>;

/*

u32y=70000還是4464?*/

u32x和u32y的結(jié)果都是4464〔16位值范圍0-65536,超出值域范圍部分4464。不要認(rèn)為表達(dá)式中有一個(gè)高類別uint32_t類型變量,編譯器都會(huì)幫你把所有其他低類別都提升到uint32_t類型。正確的書寫方式:u32x

=

<uint32_t>u16a

+<uint32_t>u16b;或者:

u32x

=

<uint32_t>u16a

+

u16b;

后一種寫法在本表達(dá)式中是正確的,但是在其它表達(dá)式中不一定正確,比如:uint16_t

u16a,u16b,u16c;

uint32_t

u32x;

u32x=u16a+u16b

+

<uint32_t>u16c;/*錯(cuò)誤寫法,u16a+u16b仍可能溢出*/4.3、賦值語(yǔ)句計(jì)算結(jié)果被轉(zhuǎn)換成被賦予值的變量類型這一過程可能導(dǎo)致類型提升、也可能導(dǎo)致類型降級(jí)。降級(jí)可能會(huì)導(dǎo)致問題。比如將運(yùn)算結(jié)果為321的值賦值給8位char類型變量。程序必須對(duì)運(yùn)算時(shí)的數(shù)據(jù)溢出做合理的處理。4.4、作為函數(shù)參數(shù)被傳遞時(shí)的數(shù)據(jù)類型轉(zhuǎn)換char和short會(huì)被轉(zhuǎn)換為int,float會(huì)被轉(zhuǎn)換為double。4.5、C語(yǔ)言強(qiáng)制類型轉(zhuǎn)換規(guī)則并非所有強(qiáng)制類型轉(zhuǎn)換都是有風(fēng)險(xiǎn)的,把一個(gè)整數(shù)值轉(zhuǎn)換為一種具有相同符號(hào)的更寬類型時(shí),是絕對(duì)安全的。精度高的類型強(qiáng)制轉(zhuǎn)換為精度低的類型時(shí),通過丟棄適當(dāng)數(shù)量的最高有效位來獲取結(jié)果,也就是說會(huì)發(fā)生數(shù)據(jù)截?cái)?并且可能改變數(shù)據(jù)的符號(hào)位。精度低的類型強(qiáng)制轉(zhuǎn)換為精度高的類型時(shí),如果兩種類型具有相同的符號(hào),那么沒什么問題;需要注意的是負(fù)的有符號(hào)精度低類型強(qiáng)制轉(zhuǎn)換為無(wú)符號(hào)精度高類型時(shí),會(huì)不直觀的執(zhí)行符號(hào)擴(kuò)展,例如:unsigned

int

bob;

signedchar

fred

=

-1;

bob=<unsigned

int

>fred;

/*發(fā)生符號(hào)擴(kuò)展,此時(shí)bob為0xFFFFFFFF*/

4.6、通用編程建議打開編譯器所有警告開關(guān)/重視所有警告使用靜態(tài)分析工具分析代碼安全的讀寫數(shù)據(jù)〔檢查所有數(shù)組邊界…檢查指針的合法性檢查函數(shù)入口參數(shù)合法性檢查所有返回值在聲明變量位置初始化所有變量合理的使用括號(hào)謹(jǐn)慎的進(jìn)行強(qiáng)制轉(zhuǎn)換使用好的診斷信息日志和工具5、判錯(cuò)編寫或移植一個(gè)類似C標(biāo)準(zhǔn)庫(kù)中的printf函數(shù),可以格式化打印字符、字符串、十進(jìn)制整數(shù)、十六進(jìn)制整數(shù)。這里稱為UARTprintf〔。unsigned

int

WriteData<unsigned

int

addr>

{

if<<addr>=

BASE_ADDR>&&<addr<=END_ADDR>>

{

…/*地址合法,進(jìn)行處理*/

}

else

{

/*地址錯(cuò)誤,打印錯(cuò)誤信息*/

UARTprintf

<"文件%s的第%d行寫數(shù)據(jù)時(shí)發(fā)生地址錯(cuò)誤,錯(cuò)誤地址為:0x%x\n",__FILE__,__LINE__,addr>;

…/*錯(cuò)誤處理代碼*/

}}

假設(shè)UARTprintf<>函數(shù)位于main.c模塊的第256行,并且WriteData<>函數(shù)在讀數(shù)據(jù)時(shí)傳遞了錯(cuò)誤地址0x00000011,則會(huì)執(zhí)行UARTprintf<>函數(shù),打印如下所示的信息:文件main.c的第256行寫數(shù)據(jù)時(shí)發(fā)生地址錯(cuò)誤,錯(cuò)誤地址為:0x00000011。類似這樣的信息會(huì)有助于程序員定位分析錯(cuò)誤產(chǎn)生的根源,更快的消除Bug。5.1、具有形參的函數(shù),需判斷傳遞來的實(shí)參是否合法。程序員可能無(wú)意識(shí)的傳遞了錯(cuò)誤參數(shù)、外界的強(qiáng)干擾可能將傳遞的參數(shù)修改掉、或者使用隨機(jī)參數(shù)意外的調(diào)用函數(shù),因此在執(zhí)行函數(shù)主體前,需要先確定實(shí)參是否合法。int

exam_fun<

unsigned

char

*str

>

{

if<

str

!=

NULL

>{

//

檢查"假設(shè)指針不為空"這個(gè)條件

...

//正常處理代碼

}

else

{

UARTprintf<…>;

//

打印錯(cuò)誤信息

…//處理錯(cuò)誤代碼

}

}

5.2、仔細(xì)檢查函數(shù)的返回值char

*DoSomething<…>

{

char

*

p;

p=malloc<1024>;

if<p==NULL>

{

/*對(duì)函數(shù)返回值作出判斷*/

UARTprintf<…>;

/*打印錯(cuò)誤信息*/

return

NULL;

}

retuen

p;

}

5.3、防止指針越界如果動(dòng)態(tài)計(jì)算一個(gè)地址時(shí),要保證被計(jì)算的地址是合理的并指向某個(gè)有意義的地方。特別對(duì)于指向一個(gè)結(jié)構(gòu)或數(shù)組的內(nèi)部的指針,當(dāng)指針增加或者改變后仍然指向同一個(gè)結(jié)構(gòu)或數(shù)組。5.4、防止數(shù)組越界C不會(huì)對(duì)數(shù)組進(jìn)行有效的檢測(cè),因此必須在應(yīng)用中顯式的檢測(cè)數(shù)組越界問題。下面的例子可用于中斷接收通訊數(shù)據(jù)。#define

REC_BUF_LEN

100

unsigned

char

RecBuf[REC_BUF_LEN];

//其它代碼

void

Uart_IRQHandler<void>

{

static

RecCount=0;

//接收數(shù)據(jù)長(zhǎng)度計(jì)數(shù)器

//其它代碼

if<RecCount<

REC_BUF_LEN>{

RecBuf[RecCount]=…;

//從硬件取數(shù)據(jù)

RecCount++;

//其它代碼

}

else

{

UARTprintf<…>;

//打印錯(cuò)誤信息

//其它錯(cuò)誤處理代碼

}

}

在使用一些庫(kù)函數(shù)時(shí),同樣需要對(duì)邊界進(jìn)行檢查:#define

REC_BUF_LEN

100

unsigned

char

RecBuf[REC_BUF_LEN];

if<len<

REC_BUF_LEN>{

memset<RecBuf,0,len>;

//將數(shù)組RecBuf清零

}

else

{

//處理錯(cuò)誤

}

5.5、數(shù)學(xué)運(yùn)算檢測(cè)除數(shù)是否為零、檢測(cè)運(yùn)算溢出情況有符號(hào)整數(shù)除法的運(yùn)算檢查兩個(gè)整數(shù)相除,除了檢測(cè)除數(shù)是否為零外,還要檢測(cè)除法是否溢出。對(duì)于一個(gè)signedlong類型變量,它能表示的數(shù)值范圍為:-2147483648~+2147483647,如果讓-2147483648/-1,那么結(jié)果應(yīng)該是+2147483648,但是這個(gè)結(jié)果已經(jīng)超出了signedlong所能表示的范圍了。#include

<limits.h>

signedlong

sl1,sl2,result;

/*初始化sl1和sl2*/

if<<sl2==0>||<<sl1==LONG_MIN>

&&

<sl2==-1>>>{

//處理錯(cuò)誤

}

else

{

result

=

sl1

/

sl2;

}加法溢出檢測(cè)a無(wú)符號(hào)加法#include

<limits.h>

unsigned

int

a,b,result;

/*初始化a,b*/

if<UINT_MAX-a<b>{

//處理溢出

}

else

{

result=a+b;

}

b有符號(hào)加法#include

<limits.h>

signedint

a,b,result;

/*初始化a,b

*/

if<<a>0

&&

INT_MAX-a<b>||<a<0>

&&

<INT_MIN-a>b>>{

//處理溢出

}

else

{

result=a+b;

}

乘法溢出檢測(cè)a無(wú)符號(hào)乘法#include

<limits.h>

unsigned

int

a,b,result;

/*初始化a,b*/

if<<a!=0>

&&

<UINT_MAX/a<b>>

{

//

}

else

{

result=a*b;

}

b有符號(hào)乘法#include

<limits.h>

signedint

a,b,tmp,result;

/*初始化a,b*/

tmp=a

*

b;

if<a!=0

&&

tmp/a!=b>{

//

}

else

{

result=tmp;

}

檢測(cè)移位時(shí)丟失有效位5.6、其它可能出現(xiàn)運(yùn)行時(shí)錯(cuò)誤的地方C語(yǔ)言在提供任何運(yùn)行時(shí)檢測(cè)方面能力較弱。要求可靠性較高的軟件來說,動(dòng)態(tài)檢測(cè)是必需的。因此C程序員需要謹(jǐn)慎考慮的問題是,在任何可能出現(xiàn)運(yùn)行時(shí)錯(cuò)誤的地方增加代碼的動(dòng)態(tài)檢測(cè)。大多數(shù)的動(dòng)態(tài)檢測(cè)與應(yīng)用緊密相關(guān),

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論