国产无遮挡裸体免费直播视频,久久精品国产蜜臀av,动漫在线视频一区二区,欧亚日韩一区二区三区,久艹在线 免费视频,国产精品美女网站免费,正在播放 97超级视频在线观看,斗破苍穹年番在线观看免费,51最新乱码中文字幕

嵌入式C程序優(yōu)質(zhì)編寫全面教程規(guī)范

 更新時(shí)間:2022年04月08日 20:21:11   作者:zhzht19861011  
這是一年前我為公司內(nèi)部寫的一個(gè)文檔,旨在向年輕的嵌入式軟件工程師們介紹如何在裸機(jī)環(huán)境下編寫優(yōu)質(zhì)嵌入式C程序。感覺是有一定的參考價(jià)值,所以拿出來分享,拋磚引玉

摘要:

本文首先分析了C語言的陷阱和缺陷,對容易犯錯(cuò)的地方進(jìn)行歸納整理;分析了編譯器語義檢查的不足之處并給出防范措施,以Keil MDK編譯器為例,介紹了該編譯器的特性、對未定義行為的處理以及一些高級應(yīng)用;

在此基礎(chǔ)上,介紹了防御性編程的概念,提出了編程過程中就應(yīng)該防范于未然的多種措施;提出了測試對編寫優(yōu)質(zhì)嵌入式程序的重要作用以及常用測試方法;最后,本文試圖以更高的層次看待編程,討論一些通用的編程思想。

1. 簡介

市面上介紹C語言以及編程方法的書數(shù)目繁多,但對如何編寫優(yōu)質(zhì)嵌入式C程序卻鮮有介紹,特別是對應(yīng)用于單片機(jī)、ARM7、Cortex-M3這類微控制器上的優(yōu)質(zhì)C程序編寫方法幾乎是個(gè)空白。本文面向的,正是使用單片機(jī)、ARM7、Cortex-M3這類微控制器的底層編程人員。

編寫優(yōu)質(zhì)嵌入式C程序絕非易事,它跟設(shè)計(jì)者的思維和經(jīng)驗(yàn)積累關(guān)系密切。嵌入式C程序員不僅需要熟知硬件的特性、硬件的缺陷等,更要深入一門語言編程,不浮于表面。為了更方便的操作硬件,還需要對編譯器進(jìn)行深入的了解。

本文將從語言特性、編譯器、防御性編程、測試和編程思想這幾個(gè)方面來討論如何編寫優(yōu)質(zhì)嵌入式C程序。與很多雜志、書籍不同,本文提供大量真實(shí)實(shí)例、代碼段和參考書目,不僅介紹應(yīng)該做什么,還重點(diǎn)介紹如何做、以及為什么這樣做。編寫優(yōu)質(zhì)嵌入式C程序涉及面十分廣,需要程序員長時(shí)間的經(jīng)驗(yàn)積累,本文希望能縮短這一過程。

2. C語言特性

語言是編程的基石,C語言詭異且有種種陷阱和缺陷,需要程序員多年歷練才能達(dá)到較為完善的地步。雖然有眾多書籍、雜志、專題討論過C語言的陷阱和缺陷,但這并不影響本節(jié)再次討論它??偸怯写笈某鯇W(xué)者,前仆后繼的倒在這些陷阱和缺陷上,民用設(shè)備、工業(yè)設(shè)備甚至是航天設(shè)備都不例外。本節(jié)將結(jié)合具體例子再次審視它們,希望引起足夠重視。深入理解C語言特性,是編寫優(yōu)質(zhì)嵌入式C程序的基礎(chǔ)。

2.1處處都是陷阱

2.1.1 無心之過

1)“=”和”==”

將比較運(yùn)算符”==”誤寫成賦值運(yùn)算符”=”,可能是絕大多數(shù)人都遇到過的,比如下面代碼:

	if(x=5)   
	{   
	    //其它代碼   
	}

代碼的本意是比較變量x是否等于常量5,但是誤將”==”寫成了”=”,if語句恒為真。如果在邏輯判斷表達(dá)式中出現(xiàn)賦值運(yùn)算符,現(xiàn)在的大多數(shù)編譯器會(huì)給出警告信息。比如keil MDK會(huì)給出警告提示:“warning: #187-D: use of "=" where"==" may have been intended”,但并非所有程序員都會(huì)注意到這類警告,因此有經(jīng)驗(yàn)的程序員使用下面的代碼來避免此類錯(cuò)誤:

	if(5==x)   
	{   
	    //其它代碼   
	}

將常量放在變量x的左邊,即使程序員誤將’==’寫成了’=’,編譯器會(huì)產(chǎn)生一個(gè)任誰也不能無視的語法錯(cuò)誤信息:不可給常量賦值!

2)復(fù)合賦值運(yùn)算符

復(fù)合賦值運(yùn)算符(+=、*=等等)雖然可以使表達(dá)式更加簡潔并有可能產(chǎn)生更高效的機(jī)器代碼,但某些復(fù)合賦值運(yùn)算符也會(huì)給程序帶來隱含Bug,比如”+=”容易誤寫成”=+”,代碼如下:

tmp=+1;

代碼本意是想表達(dá)tmp=tmp+1,但是將復(fù)合賦值運(yùn)算符”+=”誤寫成”=+”:將正整數(shù)常量1賦值給變量tmp。編譯器會(huì)欣然接受這類代碼,連警告都不會(huì)產(chǎn)生。

如果你能在調(diào)試階段就發(fā)現(xiàn)這個(gè)Bug,真應(yīng)該慶祝一下,否則這很可能會(huì)成為一個(gè)重大隱含Bug,且不易被察覺。

復(fù)合賦值運(yùn)算符”-=”也有類似問題存在。

3)其它容易誤寫
  • 使用了中文標(biāo)點(diǎn)
  • 頭文件聲明語句最后忘記結(jié)束分號
  • 邏輯與&&和位與&、邏輯或||和位或|、邏輯非!和位取反~
  • 字母l和數(shù)字1、字母O和數(shù)字0

這些誤寫其實(shí)容易被編譯器檢測出,只需要關(guān)注編譯器對此的提示信息,就能很快解決。

很多的軟件Bug源自于輸入錯(cuò)誤。在Google上搜索的時(shí)候,有些結(jié)果列表項(xiàng)中帶有一條警告,表明Google認(rèn)為它帶有惡意代碼。如果你在2009年1月31日一大早使用Google搜索的話,你就會(huì)看到,在那天早晨55分鐘的時(shí)間內(nèi),

Google的搜索結(jié)果標(biāo)明每個(gè)站點(diǎn)對你的PC都是有害的。這涉及到整個(gè)Internet上的所有站點(diǎn),包括Google自己的所有站點(diǎn)和服務(wù)。Google的惡意軟件檢測功能通過在一個(gè)已知攻擊者的列表上查找站點(diǎn),從而識別出危險(xiǎn)站點(diǎn)。在1月31日早晨,對這個(gè)列表的更新意外地包含了一條斜杠(“/”)。

所有的URL都包含一條斜杠,并且,反惡意軟件功能把這條斜杠理解為所有的URL都是可疑的,因此,它愉快地對搜索結(jié)果中的每個(gè)站點(diǎn)都添加一條警告。很少見到如此簡單的一個(gè)輸入錯(cuò)誤帶來的結(jié)果如此奇怪且影響如此廣泛,但程序就是這樣,容不得一絲疏忽。

2.1.2 數(shù)組下標(biāo)

數(shù)組常常也是引起程序不穩(wěn)定的重要因素,C語言數(shù)組的迷惑性與數(shù)組下標(biāo)從0開始密不可分,你可以定義int test[30],但是你絕不可以使用數(shù)組元素test [30],除非你自己明確知道在做什么。

2.1.3 容易被忽略的break關(guān)鍵字

1)不能漏加的break

switch…case語句可以很方便的實(shí)現(xiàn)多分支結(jié)構(gòu),但要注意在合適的位置添加break關(guān)鍵字。程序員往往容易漏加break從而引起順序執(zhí)行多個(gè)case語句,這也許是C的一個(gè)缺陷之處。

對于switch…case語句,從概率論上說,絕大多數(shù)程序一次只需執(zhí)行一個(gè)匹配的case語句,而每一個(gè)這樣的case語句后都必須跟一個(gè)break。去復(fù)雜化大概率事件,這多少有些不合常情。

2)不能亂加的break

break關(guān)鍵字用于跳出最近的那層循環(huán)語句或者switch語句,但程序員往往不夠重視這一點(diǎn)。

1990年1月15日,AT&T電話網(wǎng)絡(luò)位于紐約的一臺交換機(jī)宕機(jī)并且重啟,引起它鄰近交換機(jī)癱瘓,由此及彼,一個(gè)連著一個(gè),很快,114型交換機(jī)每六秒宕機(jī)重啟一次,六萬人九小時(shí)內(nèi)不能打長途電話。當(dāng)時(shí)的解決方式:工程師重裝了以前的軟件版本。。。事后的事故調(diào)查發(fā)現(xiàn),這是break關(guān)鍵字誤用造成的?!禖專家編程》提供了一個(gè)簡化版的問題源碼:

	network code()  
	{  
	    switch(line) 
	     {  
	        case  THING1:
			   {  
	            doit1(); 
	         } break;  
	        case  THING2:
			   {  
	            if(x==STUFF) 
	             {  
	                do_first_stuff();  
	                if(y==OTHER_STUFF)  
	                    break;  
	                do_later_stuff();  
	            }  /*代碼的意圖是跳轉(zhuǎn)到這里… …*/  
	            initialize_modes_pointer(); 
			   } break;  
	        default :  
	            processing();  
	    } /*… …但事實(shí)上跳到了這里。*/  
	    use_modes_pointer(); /*致使modes_pointer未初始化*/  
	}  

那個(gè)程序員希望從if語句跳出,但他卻忘記了break關(guān)鍵字實(shí)際上跳出最近的那層循環(huán)語句或者switch語句?,F(xiàn)在它跳出了switch語句,執(zhí)行了use_modes_pointer()函數(shù)。但必要的初始化工作并未完成,為將來程序的失敗埋下了伏筆。

2.1.4 意想不到的八進(jìn)制

將一個(gè)整形常量賦值給變量,代碼如下所示:

	int a=34, b=034; 

變量a和b相等嗎?

答案是不相等的。我們知道,16進(jìn)制常量以’0x’為前綴,10進(jìn)制常量不需要前綴,那么8進(jìn)制呢?它與10進(jìn)制和16進(jìn)制表示方法都不相通,它以數(shù)字’0’為前綴,這多少有點(diǎn)奇葩:三種進(jìn)制的表示方法完全不相通。如果8進(jìn)制也像16進(jìn)制那樣以數(shù)字和字母表示前綴的話,或許更有利于減少軟件Bug,畢竟你使用8進(jìn)制的次數(shù)可能都不會(huì)有誤使用的次數(shù)多!下面展示一個(gè)誤用8進(jìn)制的例子,最后一個(gè)數(shù)組元素賦值錯(cuò)誤:

    a[0]=106;       /*十進(jìn)制數(shù)106*/  
    a[1]=112;         /*十進(jìn)制數(shù)112*/   
    a[2]=052;       /*實(shí)際為十進(jìn)制數(shù)42,本意為十進(jìn)制52*/ 

2.1.5 指針加減運(yùn)算

指針的加減運(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的值很容判斷出結(jié)果為2,但是p的結(jié)果卻是0x00001004。指針p加1后,p的值增加了4,這是為什么呢?原因是指針做加減運(yùn)算時(shí)是以指針的數(shù)據(jù)類型為單位。p+1實(shí)際上是按照公式p+1*sizeof(int)來計(jì)算的。不理解這一點(diǎn),在使用指針直接操作數(shù)據(jù)時(shí)極易犯錯(cuò)。

某項(xiàng)目使用下面代碼對連續(xù)RAM初始化零操作,但運(yùn)行發(fā)現(xiàn)有些RAM并沒有被真正清零。

	unsigned int *pRAMaddr;         //定義地址指針變量  
	for(pRAMaddr=StartAddr;pRAMaddr<EndAddr;pRAMaddr+=4)  
	{  
	     *pRAMaddr=0x00000000;   //指定RAM地址清零  
	} 

通過分析我們發(fā)現(xiàn),由于pRAMaddr是一個(gè)無符號int型指針變量,所以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ù)。

2.1.6 關(guān)鍵字sizeof

不知道有多少人最初認(rèn)為sizeof是一個(gè)函數(shù)。其實(shí)它是一個(gè)關(guān)鍵字,其作用是返回一個(gè)對象或者類型所占的內(nèi)存字節(jié)數(shù),對絕大多數(shù)編譯器而言,返回值為無符號整形數(shù)據(jù)。需要注意的是,使用sizeof獲取數(shù)組長度時(shí),不要對指針應(yīng)用sizeof操作符,比如下面的例子:

	void ClearRAM(char array[])  
	    int i ;  
	    for(i=0;i<sizeof(array)/sizeof(array[0]);i++)     //這里用法錯(cuò)誤,array實(shí)際上是指針  
	    {  
	        array[i]=0x00;  
	    }  
	}  
	int main(void)  
	{  
	    char Fle[20];  
	    ClearRAM(Fle);          //只能清除數(shù)組Fle中的前四個(gè)元素  
	}  

我們知道,對于一個(gè)數(shù)組array[20],我們使用代碼sizeof(array)/sizeof(array[0])可以獲得數(shù)組的元素(這里為20),但數(shù)組名和指針往往是容易混淆的,有且只有一種情況下數(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è)元素了。

2.1.7 增量運(yùn)算符’++’和減量運(yùn)算符’—‘

增量運(yùn)算符”++”和減量運(yùn)算符”--“既可以做前綴也可以做后綴。前綴和后綴的區(qū)別在于值的增加或減少這一動(dòng)作發(fā)生的時(shí)間是不同的。作為前綴是先自加或自減然后做別的運(yùn)算,作為后綴時(shí),是先做運(yùn)算,之后再自加或自減。許多程序員對此認(rèn)識不夠,就容易埋下隱患。下面的例子可以很好的解釋前綴和后綴的區(qū)別。

	int a=8,b=2,y;  
	y=a+++--b;  

代碼執(zhí)行后,y的值是多少?

這個(gè)例子并非是挖空心思設(shè)計(jì)出來專門讓你絞盡腦汁的C難題(如果你覺得自己對C細(xì)節(jié)掌握很有信心,做一些C難題檢驗(yàn)一下是個(gè)不錯(cuò)的選擇。那么,《The C Puzzle Book》這本書一定不要錯(cuò)過),你甚至可以將這個(gè)難懂的語句作為不友好代碼的例子。但是它也可以讓你更好的理解C語言。根據(jù)運(yùn)算符優(yōu)先級以及編譯器識別字符的貪心法原則,第二句代碼可以寫成更明確的形式:

 y=(a++)+(--b); 

當(dāng)賦值給變量y時(shí),a的值為8,b的值為1,所以變量y的值為9;賦值完成后,變量a自加,a的值變?yōu)?,千萬不要以為y的值為10。這條賦值語句相當(dāng)于下面的兩條語句:

	y=a+(--b);  
	a=a+1; 

2.1.8 邏輯與’&&’和邏輯或’||’的陷阱

為了提高系統(tǒng)效率,邏輯與和邏輯或操作的規(guī)定如下:如果對第一個(gè)操作數(shù)求值后就可以推斷出最終結(jié)果,第二個(gè)操作數(shù)就不會(huì)進(jìn)行求值!比如下面代碼:

	if((i>=0)&&(i++ <=max))  
	{  
	       //其它代碼  
	}  

在這個(gè)代碼中,只有當(dāng)i>=0時(shí),i++才會(huì)被執(zhí)行。這樣,i是否自增是不夠明確的,這可能會(huì)埋下隱患。邏輯或與之類似。

2.1.9 結(jié)構(gòu)體的填充

結(jié)構(gòu)體可能產(chǎn)生填充,因?yàn)閷Υ蠖鄶?shù)處理器而言,訪問按字或者半字對齊的數(shù)據(jù)速度更快,當(dāng)定義結(jié)構(gòu)體時(shí),編譯器為了性能優(yōu)化,可能會(huì)將它們按照半字或字對齊,這樣會(huì)帶來填充問題。比如以下兩個(gè)個(gè)結(jié)構(gòu)體:

第一個(gè)結(jié)構(gòu)體:

	struct {  
	    char  c;  
	    short s;  
	    int   x;  
	}str_test1; 

第二個(gè)結(jié)構(gòu)體:

	struct {  
	    char  c;  
	    int   x;  
	    short s;      
	}str_test2;

這兩個(gè)結(jié)構(gòu)體元素都是相同的變量,只是元素?fù)Q了下位置,那么這兩個(gè)結(jié)構(gòu)體變量占用的內(nèi)存大小相同嗎?

其實(shí)這兩個(gè)結(jié)構(gòu)體變量占用的內(nèi)存是不同的,對于Keil MDK編譯器,默認(rèn)情況下第一個(gè)結(jié)構(gòu)體變量占用8個(gè)字節(jié),第二個(gè)結(jié)構(gòu)體占用12個(gè)字節(jié),差別很大。第一個(gè)結(jié)構(gòu)體變量在內(nèi)存中的存儲(chǔ)格式如圖2-1所示:

圖2-1:結(jié)構(gòu)體變量1內(nèi)存分布

第二個(gè)結(jié)構(gòu)體變量在內(nèi)存中的存儲(chǔ)格式如圖2-2所示。對比兩個(gè)圖可以看出MDK編譯器是是怎么將數(shù)據(jù)對齊的,這其中的填充內(nèi)容是之前內(nèi)存中的數(shù)據(jù),是隨機(jī)的,所以不能再結(jié)構(gòu)之間逐字節(jié)比較;另外,合理的排布結(jié)構(gòu)體內(nèi)的元素位置,可以最大限度減少填充,節(jié)省RAM。

圖2-2 :結(jié)構(gòu)體變量2內(nèi)存分布

2.2 不可輕視的優(yōu)先級

C語言有32個(gè)關(guān)鍵字,卻有34個(gè)運(yùn)算符。要記住所有運(yùn)算符的優(yōu)先級是困難的。稍不注意,你的代碼邏輯和實(shí)際執(zhí)行就會(huì)有很大出入。

比如下面將BCD碼轉(zhuǎn)換為十六進(jìn)制數(shù)的代碼:

 result=(uTimeValue>>4)*10+uTimeValue&0x0F; 

這里uTimeValue存放的BCD碼,想要轉(zhuǎn)換成16進(jìn)制數(shù)據(jù),實(shí)際運(yùn)行發(fā)現(xiàn),如果uTimeValue的值為0x23,按照我設(shè)定的邏輯,result的值應(yīng)該是0x17,但運(yùn)算結(jié)果卻是0x07。經(jīng)過種種排查后,才發(fā)現(xiàn)’+’的優(yōu)先級是大于’&’的,相當(dāng)于(uTimeValue>>4)*10+uTimeValue與0x0F位與,結(jié)果自然與邏輯不符。符合邏輯的代碼應(yīng)該是:

result=(uTimeValue>>4)*10+(uTimeValue&0x0F); 

不合理的#define會(huì)加重優(yōu)先級問題,讓問題變得更加隱蔽。

	#define READSDA IO0PIN&(1<<11)  //讀IO口p0.11的端口狀態(tài)  
	          
	if(READSDA==(1<<11))          //判斷端口p0.11是否為高電平   
	{    
	    //其它代碼  
	}  

編譯器在編譯后將宏帶入,原代碼語句變?yōu)?

	if(IO0PIN&(1<<11) ==(1<<11))  
	{  
	    //其它代碼   
	} 

運(yùn)算符'=='的優(yōu)先級是大于'&'的,代碼IO0PIN&(1<<11)==(1<<11))等效為IO0PIN&0x00000001:判斷端口P0.0是否為高電平,這與原意相差甚遠(yuǎn)。因此,使用宏定義的時(shí)候,最好將被定義的內(nèi)容用括號括起來。

按照常規(guī)方式使用時(shí),可能引起誤會(huì)的運(yùn)算符還有很多,如表2-1所示。C語言的運(yùn)算符當(dāng)然不會(huì)只止步于數(shù)目繁多!

有一個(gè)簡便方法可以避免優(yōu)先級問題:不清楚的優(yōu)先級就加上”()”,但這樣至少有會(huì)帶來兩個(gè)問題:

  • 過多的括號影響代碼的可讀性,包括自己和以后的維護(hù)人員
  • 別人的代碼不一定用括號來解決優(yōu)先級問題,但你總要讀別人的代碼

無論如何,在嵌入式編程方面,該掌握的基礎(chǔ)知識,偷巧不得。建議花一些時(shí)間,將優(yōu)先級順序以及容易出錯(cuò)的優(yōu)先級運(yùn)算符理清幾遍。

2.3 隱式轉(zhuǎn)換

C語言的設(shè)計(jì)理念一直被人吐槽,因?yàn)樗J(rèn)為C程序員完全清楚自己在做什么,其中一個(gè)證據(jù)就是隱式轉(zhuǎn)換。C語言規(guī)定,不同類型的數(shù)據(jù)(比如char和int型數(shù)據(jù))需要轉(zhuǎn)換成同一類型后,才可進(jìn)行計(jì)算。如果你混合使用類型,比如用char類型數(shù)據(jù)和int類型數(shù)據(jù)做減法,C使用一個(gè)規(guī)則集合來自動(dòng)(隱式的)完成類型轉(zhuǎn)換。這可能很方便,但也很危險(xiǎn)。

這就要求我們理解這個(gè)轉(zhuǎn)換規(guī)則并且能應(yīng)用到程序中去!

1)當(dāng)出現(xiàn)在表達(dá)式里時(shí),有符號和無符號的char和short類型都將自動(dòng)被轉(zhuǎn)換為int類型,在需要的情況下,將自動(dòng)被轉(zhuǎn)換為unsigned int(在short和int具有相同大小時(shí))。這稱為類型提升。

提升在算數(shù)運(yùn)算中通常不會(huì)有什么大的壞處,但如果位運(yùn)算符 ~ 和 << 應(yīng)用在基本類型為unsigned char或unsigned short 的操作數(shù),結(jié)果應(yīng)該立即強(qiáng)制轉(zhuǎn)換為unsigned char或者unsigned short類型(取決于操作時(shí)使用的類型)。

	uint8_t  port =0x5aU;  
	uint8_t  result_8;  
	result_8= (~port) >> 4; 

假如我們不了解表達(dá)式里的類型提升,認(rèn)為在運(yùn)算過程中變量port一直是unsigned char類型的。我們來看一下運(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)啵ㄟ@也是隱式的!),result_8=0xfa。經(jīng)過這么詭異的隱式轉(zhuǎn)換,結(jié)果跟我們期望的值,已經(jīng)大相徑庭!正確的表達(dá)式語句應(yīng)該為:

result_8=(unsigned char) (~port) >> 4;             /*強(qiáng)制轉(zhuǎn)換*/

2)在包含兩種數(shù)據(jù)類型的任何運(yùn)算里,兩個(gè)值都會(huì)被轉(zhuǎn)換成兩種類型里較高的級別。類型級別從高到低的順序是long double、double、float、unsigned long long、long long、unsigned long、long、unsigned int、int。

這種類型提升通常都是件好事,但往往有很多程序員不能真正理解這句話,比如下面的例子(int類型表示16位)。

	uint16_t  u16a = 40000;            	/* 16位無符號變量*/  
	uint16_t  u16b= 30000;          	/*16位無符號變量*/  
	uint32_t  u32x;                  	/*32位無符號變量 */  
	uint32_t  u32y;  
	u32x = u16a +u16b;                	/* u32x = 70000還是4464 ? */  
	u32y =(uint32_t)(u16a + u16b);   	/* u32y = 70000 還是4464 ? */

u32x和u32y的結(jié)果都是4464(70000%65536)!不要認(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仍可能溢出*/ 

3)在賦值語句里,計(jì)算的最后結(jié)果被轉(zhuǎn)換成將要被賦予值的那個(gè)變量的類型。這一過程可能導(dǎo)致類型提升也可能導(dǎo)致類型降級。降級可能會(huì)導(dǎo)致問題。比如將運(yùn)算結(jié)果為321的值賦值給8位char類型變量。程序必須對運(yùn)算時(shí)的數(shù)據(jù)溢出做合理的處理。

很多其他語言,像Pascal(C語言設(shè)計(jì)者之一曾撰文狠狠批評過Pascal語言),都不允許混合使用類型,但C語言不會(huì)限制你的自由,即便這經(jīng)常引起B(yǎng)ug。

4)當(dāng)作為函數(shù)的參數(shù)被傳遞時(shí),char和short會(huì)被轉(zhuǎn)換為int,float會(huì)被轉(zhuǎn)換為double。

當(dāng)不得已混合使用類型時(shí),一個(gè)比較好的習(xí)慣是使用類型強(qiáng)制轉(zhuǎn)換。強(qiáng)制類型轉(zhuǎn)換可以避免編譯器隱式轉(zhuǎn)換帶來的錯(cuò)誤,同時(shí)也向以后的維護(hù)人員傳遞一些有用信息。這有個(gè)前提:你要對強(qiáng)制類型轉(zhuǎn)換有足夠的了解!下面總結(jié)一些規(guī)則:

  • 并非所有強(qiáng)制類型轉(zhuǎn)換都是由風(fēng)險(xiǎn)的,把一個(gè)整數(shù)值轉(zhuǎn)換為一種具有相同符號的更寬類型時(shí),是絕對安全的。
  • 精度高的類型強(qiáng)制轉(zhuǎn)換為精度低的類型時(shí),通過丟棄適當(dāng)數(shù)量的最高有效位來獲取結(jié)果,也就是說會(huì)發(fā)生數(shù)據(jù)截?cái)啵⑶铱赡芨淖償?shù)據(jù)的符號位。
  • 精度低的類型強(qiáng)制轉(zhuǎn)換為精度高的類型時(shí),如果兩種類型具有相同的符號,那么沒什么問題;需要注意的是負(fù)的有符號精度低類型強(qiáng)制轉(zhuǎn)換為無符號精度高類型時(shí),會(huì)不直觀的執(zhí)行符號擴(kuò)展,例如:
	unsigned int bob;  
	signed char fred = -1;  
	   
	bob=(unsigned int )fred;              /*發(fā)生符號擴(kuò)展,此時(shí)bob為0xFFFFFFFF*/ 

3. 編譯器

如果你和一個(gè)優(yōu)秀的程序員共事,你會(huì)發(fā)現(xiàn)他對他使用的工具非常熟悉,就像一個(gè)畫家了解他的畫具一樣。----比爾.蓋茨

3.1 不能簡單的認(rèn)為是個(gè)工具

嵌入式程序開發(fā)跟硬件密切相關(guān),需要使用C語言來讀寫底層寄存器、存取數(shù)據(jù)、控制硬件等,C語言和硬件之間由編譯器來聯(lián)系,一些C標(biāo)準(zhǔn)不支持的硬件特性操作,由編譯器提供。

匯編可以很輕易的讀寫指定RAM地址、可以將代碼段放入指定的Flash地址、可以精確的設(shè)置變量在RAM中分布等等,所有這些操作,在深入了解編譯器后,也可以使用C語言實(shí)現(xiàn)。

C語言標(biāo)準(zhǔn)并非完美,有著數(shù)目繁多的未定義行為,這些未定義行為完全由編譯器自主決定,了解你所用的編譯器對這些未定義行為的處理,是必要的。

嵌入式編譯器對調(diào)試做了優(yōu)化,會(huì)提供一些工具,可以分析代碼性能,查看外設(shè)組件等,了解編譯器的這些特性有助于提高在線調(diào)試的效率。

此外,堆棧操作、代碼優(yōu)化、數(shù)據(jù)類型的范圍等等,都是要深入了解編譯器的理由。

如果之前你認(rèn)為編譯器只是個(gè)工具,能夠編譯就好。那么,是時(shí)候改變這種思想了。

3.2 不能依賴編譯器的語義檢查

編譯器的語義檢查很弱小,甚至還會(huì)“掩蓋”錯(cuò)誤?,F(xiàn)代的編譯器設(shè)計(jì)是件浩瀚的工程,為了讓編譯器設(shè)計(jì)簡單一些,目前幾乎所有編譯器的語義檢查都比較弱小。為了獲得更快的執(zhí)行效率,C語言被設(shè)計(jì)的足夠靈活且?guī)缀醪贿M(jìn)行任何運(yùn)行時(shí)檢查,比如數(shù)組越界、指針是否合法、運(yùn)算結(jié)果是否溢出等等。這就造成了很多編譯正確但執(zhí)行奇怪的程序。

C語言足夠靈活,對于一個(gè)數(shù)組test[30],它允許使用像test[-1]這樣的形式來快速獲取數(shù)組首元素所在地址前面的數(shù)據(jù);允許將一個(gè)常數(shù)強(qiáng)制轉(zhuǎn)換為函數(shù)指針,使用代碼(*((void(*)())0))()來調(diào)用位于0地址的函數(shù)。C語言給了程序員足夠的自由,但也由程序員承擔(dān)濫用自由帶來的責(zé)任。

3.2.1莫名的死機(jī)

下面的兩個(gè)例子都是死循環(huán),如果在不常用分支中出現(xiàn)類似代碼,將會(huì)造成看似莫名其妙的死機(jī)或者重啟。

	unsigned char i;    //例程1 
	for(i=0;i<256;i++)    
	{   
	    //其它代碼  
	} 
	unsigned char i;     //例程2 
	for(i=10;i>=0;i--)   
	{   
	    //其它代碼  
	}

對于無符號char類型,表示的范圍為0~255,所以無符號char類型變量i永遠(yuǎn)小于256(第一個(gè)for循環(huán)無限執(zhí)行),永遠(yuǎn)大于等于0(第二個(gè)for循環(huán)無線執(zhí)行)。需要說明的是,賦值代碼i=256是被C語言允許的,即使這個(gè)初值已經(jīng)超出了變量i可以表示的范圍。C語言會(huì)千方百計(jì)的為程序員創(chuàng)造出錯(cuò)的機(jī)會(huì),可見一斑。

3.2.2不起眼的改變

假如你在if語句后誤加了一個(gè)分號,可能會(huì)完全改變了程序邏輯。編譯器也會(huì)很配合的幫忙掩蓋,甚至連警告都不提示。代碼如下:

	if(a>b);          	//這里誤加了一個(gè)分號  
	a=b;             	//這句代碼一直被執(zhí)行 

不但如此,編譯器還會(huì)忽略掉多余的空格符和換行符,就像下面的代碼也不會(huì)給出足夠提示:

	if(n<3)  
	return    		//這里少加了一個(gè)分號  
	logrec.data=x[0];  
	logrec.time=x[1];  
	logrec.code=x[2];  

這段代碼的本意是n<3時(shí)程序直接返回,由于程序員的失誤,return少了一個(gè)結(jié)束分號。編譯器將它翻譯成返回表達(dá)式logrec.data=x[0]的結(jié)果,return后面即使是一個(gè)表達(dá)式也是C語言允許的。這樣當(dāng)n>=3時(shí),表達(dá)式logrec.data=x[0];就不會(huì)被執(zhí)行,給程序埋下了隱患。

3.2.3 難查的數(shù)組越界

上文曾提到數(shù)組常常是引起程序不穩(wěn)定的重要因素,程序員往往不經(jīng)意間就會(huì)寫數(shù)組越界。

一位同事的代碼在硬件上運(yùn)行,一段時(shí)間后就會(huì)發(fā)現(xiàn)LCD顯示屏上的一個(gè)數(shù)字不正常的被改變。經(jīng)過一段時(shí)間的調(diào)試,問題被定位到下面的一段代碼中:

	int SensorData[30];  
	//其他代碼 
	for(i=30;i>0;i--)  
	{  
	     SensorData[i]=…;  
	     //其他代碼   
	}

這里聲明了擁有30個(gè)元素的數(shù)組,不幸的是for循環(huán)代碼中誤用了本不存在的數(shù)組元素SensorData[30],但C語言卻默許這么使用,并欣然的按照代碼改變了數(shù)組元素SensorData[30]所在位置的值, SensorData[30]所在的位置原本是一個(gè)LCD顯示變量,這正是顯示屏上的那個(gè)值不正常被改變的原因。真慶幸這么輕而易舉的發(fā)現(xiàn)了這個(gè)Bug。

其實(shí)很多編譯器會(huì)對上述代碼產(chǎn)生一個(gè)警告:賦值超出數(shù)組界限。但并非所有程序員都對編譯器警告保持足夠敏感,況且,編譯器也并不能檢查出數(shù)組越界的所有情況。比如下面的例子:

你在模塊A中定義數(shù)組:

int SensorData[30];

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

extern int SensorData[]; 

這次,編譯器不會(huì)給出警告信息,因?yàn)榫幾g器壓根就不知道數(shù)組的元素個(gè)數(shù)。所以,當(dāng)一個(gè)數(shù)組聲明為具有外部鏈接,它的大小應(yīng)該顯式聲明。

再舉一個(gè)編譯器檢查不出數(shù)組越界的例子。函數(shù)func()的形參是一個(gè)數(shù)組形式,函數(shù)代碼簡化如下所示:

	char * func(char SensorData[30])  
	{  
	     unsignedint i;  
	     for(i=30;i>0;i--)  
	     {  
	          SensorData[i]=…;  
	          //其他代碼
	     }  
	}  

這個(gè)給SensorData[30]賦初值的語句,編譯器也是不給任何警告的。實(shí)際上,編譯器是將數(shù)組名Sensor隱含的轉(zhuǎn)化為指向數(shù)組第一個(gè)元素的指針,函數(shù)體是使用指針的形式來訪問數(shù)組的,它當(dāng)然也不會(huì)知道數(shù)組元素的個(gè)數(shù)了。造成這種局面的原因之一是C編譯器的作者們認(rèn)為指針代替數(shù)組可以提高程序效率,而且,可以簡化編譯器的復(fù)雜度。

指針和數(shù)組是容易給程序造成混亂的,我們有必要仔細(xì)的區(qū)分它們的不同。其實(shí)換一個(gè)角度想想,它們也是容易區(qū)分的:可以將數(shù)組名等同于指針的情況有且只有一處,就是上面例子提到的數(shù)組作為函數(shù)形參時(shí)。其它時(shí)候,數(shù)組名是數(shù)組名,指針是指針。

下面的例子編譯器同樣檢查不出數(shù)組越界。

我們常常用數(shù)組來緩存通訊中的一幀數(shù)據(jù)。在通訊中斷中將接收的數(shù)據(jù)保存到數(shù)組中,直到一幀數(shù)據(jù)完全接收后再進(jìn)行處理。即使定義的數(shù)組長度足夠長,接收數(shù)據(jù)的過程中也可能發(fā)生數(shù)組越界,特別是干擾嚴(yán)重時(shí)。這是由于外界的干擾破壞了數(shù)據(jù)幀的某些位,對一幀的數(shù)據(jù)長度判斷錯(cuò)誤,接收的數(shù)據(jù)超出數(shù)組范圍,多余的數(shù)據(jù)改寫與數(shù)組相鄰的變量,造成系統(tǒng)崩潰。由于中斷事件的異步性,這類數(shù)組越界編譯器無法檢查到。

如果局部數(shù)組越界,可能引發(fā)ARM架構(gòu)硬件異常。

同事的一個(gè)設(shè)備用于接收無線傳感器的數(shù)據(jù),一次軟件升級后,發(fā)現(xiàn)接收設(shè)備工作一段時(shí)間后會(huì)死機(jī)。調(diào)試表明ARM7處理器發(fā)生了硬件異常,異常處理代碼是一段死循環(huán)(死機(jī)的直接原因)。接收設(shè)備有一個(gè)硬件模塊用于接收無線傳感器的整包數(shù)據(jù)并存在自己的緩沖區(qū)中,當(dāng)硬件模塊接收數(shù)據(jù)完成后,使用外部中斷通知設(shè)備取數(shù)據(jù),外部中斷服務(wù)程序精簡后如下所示:

	__irq ExintHandler(void)  
	{  
	     unsignedchar DataBuf[50];  
	     GetData(DataBug);        //從硬件緩沖區(qū)取一幀數(shù)據(jù)  
	     //其他代碼 
	}

由于存在多個(gè)無線傳感器近乎同時(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)生。

如果我們精心設(shè)計(jì)溢出部分的數(shù)據(jù),化數(shù)據(jù)為指令,就可以利用數(shù)組越界來修改PC指針的值,使之指向我們希望執(zhí)行的代碼。

1988年,第一個(gè)網(wǎng)絡(luò)蠕蟲在一天之內(nèi)感染了2000到6000臺計(jì)算機(jī),這個(gè)蠕蟲程序利用的正是一個(gè)標(biāo)準(zhǔn)輸入庫函數(shù)的數(shù)組越界Bug。起因是一個(gè)標(biāo)準(zhǔn)輸入輸出庫函數(shù)gets(),原來設(shè)計(jì)為從數(shù)據(jù)流中獲取一段文本,遺憾的是,gets()函數(shù)沒有規(guī)定輸入文本的長度。gets()函數(shù)內(nèi)部定義了一個(gè)500字節(jié)的數(shù)組,攻擊者發(fā)送了大于500字節(jié)的數(shù)據(jù),利用溢出的數(shù)據(jù)修改了堆棧中的PC指針,從而獲取了系統(tǒng)權(quán)限。目前,雖然有更好的庫函數(shù)來代替gets函數(shù),但gets函數(shù)仍然存在著。

3.2.4神奇的volatile

做嵌入式設(shè)備開發(fā),如果不對volatile修飾符具有足夠了解,實(shí)在是說不過去。volatile是C語言32個(gè)關(guān)鍵字中的一個(gè),屬于類型限定符,常用的const關(guān)鍵字也屬于類型限定符。

volatile限定符用來告訴編譯器,該對象的值無任何持久性,不要對它進(jìn)行任何優(yōu)化;它迫使編譯器每次需要該對象數(shù)據(jù)內(nèi)容時(shí)都必須讀該對象,而不是只讀一次數(shù)據(jù)并將它放在寄存器中以便后續(xù)訪問之用(這樣的優(yōu)化可以提高系統(tǒng)速度)。

這個(gè)特性在嵌入式應(yīng)用中很有用,比如你的IO口的數(shù)據(jù)不知道什么時(shí)候就會(huì)改變,這就要求編譯器每次都必須真正的讀取該IO端口。這里使用了詞語“真正的讀”,是因?yàn)橛捎诰幾g器的優(yōu)化,你的邏輯反應(yīng)到代碼上是對的,但是代碼經(jīng)過編譯器翻譯后,有可能與你的邏輯不符。你的代碼邏輯可能是每次都會(huì)讀取IO端口數(shù)據(jù),但實(shí)際上編譯器將代碼翻譯成匯編時(shí),可能只是讀一次IO端口數(shù)據(jù)并保存到寄存器中,接下來的多次讀IO口都是使用寄存器中的值來進(jìn)行處理。因?yàn)樽x寫寄存器是最快的,這樣可以優(yōu)化程序效率。與之類似的,中斷里的變量、多線程中的共享變量等都存在這樣的問題。

不使用volatile,可能造成運(yùn)行邏輯錯(cuò)誤,但是不必要的使用volatile會(huì)造成代碼效率低下(編譯器不優(yōu)化volatile限定的變量),因此清楚的知道何處該使用volatile限定符,是一個(gè)嵌入式程序員的必修內(nèi)容。

一個(gè)程序模塊通常由兩個(gè)文件組成,源文件和頭文件。如果你在源文件定義變量:

unsigned int test; 

并在頭文件中聲明該變量:

extern unsigned long test;

編譯器會(huì)提示一個(gè)語法錯(cuò)誤:變量’ test’聲明類型不一致。但如果你在源文件定義變量:

volatile unsigned int test;

在頭文件中這樣聲明變量:

extern unsigned int test; /*缺少volatile限定符*/

編譯器卻不會(huì)給出錯(cuò)誤信息(有些編譯器僅給出一條警告)。當(dāng)你在另外一個(gè)模塊(該模塊包含聲明變量test的頭文件)使用變量test時(shí),它已經(jīng)不再具有volatile限定,這樣很可能造成一些重大錯(cuò)誤。比如下面的例子,注意該例子是為了說明volatile限定符而專門構(gòu)造出的,因?yàn)楝F(xiàn)實(shí)中的volatile使用Bug大都隱含,并且難以理解。

在模塊A的源文件中,定義變量:

volatile unsigned int TimerCount=0;

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

TimerCount++; 

在模塊A的頭文件中,聲明變量:

extern unsigned int TimerCount; //這里漏掉了類型限定符volatile 

在模塊B中,要使用TimerCount變量進(jìn)行精確的軟件延時(shí):

	#include “…A.h”                     //首先包含模塊A的頭文件  
	//其他代碼  
	TimerCount=0;  
	while(TimerCount<=TIMER_VALUE);   //延時(shí)一段時(shí)間(感謝網(wǎng)友chhfish指出這里的邏輯錯(cuò)誤)  
	//其他代碼  

實(shí)際上,這是一個(gè)死循環(huán)。由于模塊A頭文件中聲明變量TimerCount時(shí)漏掉了volatile限定符,在模塊B中,變量TimerCount是被當(dāng)作unsigned int類型變量。由于寄存器速度遠(yuǎn)快于RAM,編譯器在使用非volatile限定變量時(shí)是先將變量從RAM中拷貝到寄存器中,如果同一個(gè)代碼塊再次用到該變量,就不再從RAM中拷貝數(shù)據(jù)而是直接使用之前寄存器備份值。代碼while(TimerCount<=TIMER_VALUE)中,變量TimerCount僅第一次執(zhí)行時(shí)被使用,之后都是使用的寄存器備份值,而這個(gè)寄存器值一直為0,所以程序無限循環(huán)。圖3-1的流程圖說明了程序使用限定符volatile和不使用volatile的執(zhí)行過程。

為了更容易的理解編譯器如何處理volatile限定符,這里給出未使用volatile限定符和使用volatile限定符程序的反匯編代碼:

沒有使用關(guān)鍵字volatile,在keil MDK V4.54下編譯,默認(rèn)優(yōu)化級別,如下所示(注意最后兩行):

    unIdleCount=0;   
	   123:        
	0x00002E10  E59F11D4  LDR       R1,[PC,#0x01D4]  
	0x00002E14  E3A05000  MOV       R5,#key1(0x00000000)  
	0x00002E18  E1A00005  MOV       R0,R5  
	0x00002E1C  E5815000  STR       R5,[R1]  
	   124:     while(unIdleCount!=200);   //延時(shí)2S鐘   
	   125:        
     0x00002E20  E35000C8  CMP       R0,#0x000000C8  
	0x00002E24  1AFFFFFD  BNE       0x00002E20</span>

使用關(guān)鍵字volatile,在keil MDK V4.54下編譯,默認(rèn)優(yōu)化級別,如下所示(注意最后三行):

     unIdleCount=0;   
	   123:        
	0x00002E10  E59F01D4  LDR       R0,[PC,#0x01D4]  
	0x00002E14  E3A05000  MOV       R5,#key1(0x00000000)  
	0x00002E18  E5805000  STR       R5,[R0]  
	   124:     while(unIdleCount!=200);   //延時(shí)2S鐘   
	   125:        
	0x00002E1C  E5901000  LDR       R1,[R0]  
	0x00002E20  E35100C8  CMP       R1,#0x000000C8  
	0x00002E24  1AFFFFFC  BNE       0x00002E1C 

可以看到,如果沒有使用volatile關(guān)鍵字,程序一直比較R0內(nèi)數(shù)據(jù)與0xC8是否相等,但R0中的數(shù)據(jù)是0,所以程序會(huì)一直在這里循環(huán)比較(死循環(huán));再看使用了volatile關(guān)鍵字的反匯編代碼,程序會(huì)先從變量中讀出數(shù)據(jù)放到R1寄存器中,然后再讓R1內(nèi)數(shù)據(jù)與0xC8相比較,這才是我們C代碼的正確邏輯!

3.2.5局部變量

ARM架構(gòu)下的編譯器會(huì)頻繁的使用堆棧,堆棧用于存儲(chǔ)函數(shù)的返回值、AAPCS規(guī)定的必須保護(hù)的寄存器以及局部變量,包括局部數(shù)組、結(jié)構(gòu)體、聯(lián)合體和C++的類。默認(rèn)情況下,堆棧的位置、初始值都是由編譯器設(shè)置,因此需要對編譯器的堆棧有一定了解。從堆棧中分配的局部變量的初值是不確定的,因此需要運(yùn)行時(shí)顯式初始化該變量。一旦離開局部變量的作用域,這個(gè)變量立即被釋放,其它代碼也就可以使用它,因此堆棧中的一個(gè)內(nèi)存位置可能對應(yīng)整個(gè)程序的多個(gè)變量。

局部變量必須顯式初始化,除非你確定知道你要做什么。下面的代碼得到的溫度值跟預(yù)期會(huì)有很大差別,因?yàn)樵谑褂镁植孔兞縮um時(shí),并不能保證它的初值為0。編譯器會(huì)在第一次運(yùn)行時(shí)清零堆棧區(qū)域,這加重了此類Bug的隱蔽性。

	unsigned intGetTempValue(void)  
	{  
	    unsigned int sum;                     //定義局部變量,保存總值  
	    for(i=0;i<10;i++)  
	    {  
	        sum+=CollectTemp();               //函數(shù)CollectTemp可以得到當(dāng)前的溫度值  
	    }  
	    return (sum/10);  
	} 

由于一旦程序離開局部變量的作用域即被釋放,所以下面代碼返回指向局部變量的指針是沒有實(shí)際意義的,該指針指向的區(qū)域可能會(huì)被其它程序使用,其值會(huì)被改變。

	char * GetData(void)  
	{  
	     char buffer[100];                 //局部數(shù)組  
	     …  
	     return buffer;  
	} 

3.2.6使用外部工具

由于編譯器的語義檢查比較弱,我們可以使用第三方代碼分析工具,使用這些工具來發(fā)現(xiàn)潛在的問題,這里介紹其中比較著名的是PC-Lint。

PC-Lint由Gimpel Software公司開發(fā),可以檢查C代碼的語法和語義并給出潛在的BUG報(bào)告。PC-Lint可以顯著降低調(diào)試時(shí)間。

目前公司ARM7和Cortex-M3內(nèi)核多是使用Keil MDK編譯器來開發(fā)程序,通過簡單配置,PC-Lint可以被集成到MDK上,以便更方便的檢查代碼。MDK已經(jīng)提供了PC-Lint的配置模板,所以整個(gè)配置過程十分簡單,Keil MDK開發(fā)套件并不包含PC-Lint程序,在此之前,需要預(yù)先安裝可用的PC-Lint程序,配置過程如下:

1)點(diǎn)擊菜單Tools---Set-up PC-Lint…

PC-Lint Include Folders:該列表路徑下的文件才會(huì)被PC-Lint檢查,此外,這些路徑下的文件內(nèi)使用#include包含的文件也會(huì)被檢查;

Lint Executable:指定PC-Lint程序的路徑

Configuration File:指定配置文件的路徑,該配置文件由MDK編譯器提供。

2)菜單Tools---Lint 文件路徑.c/.h

檢查當(dāng)前文件。

3)菜單Tools---Lint All C-Source Files

檢查所有C源文件。

PC-Lint的輸出信息顯示在MDK編譯器的Build Output窗口中,雙擊其中的一條信息可以跳轉(zhuǎn)到源文件所在位置。

編譯器語義檢查的弱小在很大程度上助長了不可靠代碼的廣泛存在。隨著時(shí)代的進(jìn)步,現(xiàn)在越來越多的編譯器開發(fā)商意識到了語義檢查的重要性,編譯器的語義檢查也越來越強(qiáng)大,比如公司使用的Keil MDK編譯器,雖然它的編輯器依然不盡人意,但在其 V4.47及以上版本中增加了動(dòng)態(tài)語法檢查并加強(qiáng)了語義檢查,可以友好的提示更多警告信息。建議經(jīng)常關(guān)注編譯器官方網(wǎng)站并將編譯器升級到V4.47或以上版本,升級的另一個(gè)好處是這些版本的編輯器增加了標(biāo)識符自動(dòng)補(bǔ)全功能,可以大大節(jié)省編碼的時(shí)間。

3.3 你覺得有意義的代碼未必正確

C語言標(biāo)準(zhǔn)特別的規(guī)定某些行為是未定義的,編寫未定義行為的代碼,其輸出結(jié)果由編譯器決定! C標(biāo)準(zhǔn)委員會(huì)定義未定義行為的原因如下:

  • 簡化標(biāo)準(zhǔn),并給予實(shí)現(xiàn)一定的靈活性,比如不捕捉那些難以診斷的程序錯(cuò)誤;
  • 編譯器開發(fā)商可以通過未定義行為對語言進(jìn)行擴(kuò)展

C語言的未定義行為,使得C極度高效靈活并且給編譯器實(shí)現(xiàn)帶來了方便,但這并不利于優(yōu)質(zhì)嵌入式C程序的編寫。因?yàn)樵S多 C 語言中看起來有意義的東西都是未定義的,并且這也容易使你的代碼埋下隱患,并且不利于跨編譯器移植。Java程序會(huì)極力避免未定義行為,并用一系列手段進(jìn)行運(yùn)行時(shí)檢查,使用Java可以相對容易的寫出安全代碼,但體積龐大效率低下。作為嵌入式程序員,我們需要了解這些未定義行為,利用C語言的靈活性,寫出比Java更安全、效率更高的代碼來。

3.3.1常見的未定義行為

1)自增自減在表達(dá)式中連續(xù)出現(xiàn)并作用于同一變量或者自增自減在表達(dá)式中出現(xiàn)一次,但作用的變量多次出現(xiàn)

自增(++)和自減(--)這一動(dòng)作發(fā)生在表達(dá)式的哪個(gè)時(shí)刻是由編譯器決定的,比如:

r = 1 * a[i++] + 2 * a[i++] + 3 * a[i++];

不同的編譯器可能有著不同的匯編代碼,可能是先執(zhí)行i++再進(jìn)行乘法和加法運(yùn)行,也可能是先進(jìn)行加法和乘法運(yùn)算,再執(zhí)行i++,因?yàn)檫@句代碼在一個(gè)表達(dá)式中出現(xiàn)了連續(xù)的自增并作用于同一變量。更加隱蔽的是自增自減在表達(dá)式中出現(xiàn)一次,但作用的變量多次出現(xiàn),比如:

1.a[i] = i++; /* 未定義行為 */

先執(zhí)行i++再賦值,還是先賦值再執(zhí)行i++是由編譯器決定的,而兩種不同的執(zhí)行順序的結(jié)果差別是巨大的。

2)函數(shù)實(shí)參被求值的順序

函數(shù)如果有多個(gè)實(shí)參,這些實(shí)參的求值順序是由編譯器決定的,比如:

printf("%d %d\n", ++n, power(2, n)); /* 未定義行為 */ 

是先執(zhí)行++n還是先執(zhí)行power(2,n)是由編譯器決定的。

3)有符號整數(shù)溢出

有符號整數(shù)溢出是未定義的行為,編譯器決定有符號整數(shù)溢出按照哪種方式取值。比如下面代碼:

	int value1,value2,sum  
	  
	//其它操作  
	sum=value1+value;    /*sum可能發(fā)生溢出*/

4)有符號數(shù)右移、移位的數(shù)量是負(fù)值或者大于操作數(shù)的位數(shù)

5)除數(shù)為零

6)malloc()、calloc()或realloc()分配零字節(jié)內(nèi)存

3.3.2如何避免C語言未定義行為

代碼中引入未定義行為會(huì)為代碼埋下隱患,防止代碼中出現(xiàn)未定義行為是困難的,我們總能不經(jīng)意間就會(huì)在代碼中引入未定義行為。但是還是有一些方法可以降低這種事件,總結(jié)如下:

了解C語言未定義行為

標(biāo)準(zhǔn)C99附錄J.2“未定義行為”列舉了C99中的顯式未定義行為,通過查看該文檔,了解那些行為是未定義的,并在編碼中時(shí)刻保持警惕;

尋求工具幫助

編譯器警告信息以及PC-Lint等靜態(tài)檢查工具能夠發(fā)現(xiàn)很多未定義行為并警告,要時(shí)刻關(guān)注這些工具反饋的信息;

總結(jié)并使用一些編碼標(biāo)準(zhǔn)

1)避免構(gòu)造復(fù)雜的自增或者自減表達(dá)式,實(shí)際上,應(yīng)該避免構(gòu)造所有復(fù)雜表達(dá)式;

比如a[i]=i++;語句可以改為a[i]=i; i++;這兩句代碼。

2)只對無符號操作數(shù)使用位操作;

必要的運(yùn)行時(shí)檢查

檢查是否溢出、除數(shù)是否為零,申請的內(nèi)存數(shù)量是否為零等等,比如上面的有符號整數(shù)溢出例子,可以按照如下方式編寫,以消除未定義特性:

int value1,value2,sum;  
//其它代碼  
if((value1>0 && value2>0 && value1>(INT_MAX-value2))||  
   (value1<0 && value2<0 && value1<(INT_MIN-value2)))  
{  
    //處理錯(cuò)誤  
}  
else  
{  
    sum=value1+value2;  
}

上面的代碼是通用的,不依賴于任何CPU架構(gòu),但是代碼效率很低。如果是有符號數(shù)使用補(bǔ)碼的CPU架構(gòu)(目前常見CPU絕大多數(shù)都是使用補(bǔ)碼),還可以用下面的代碼來做溢出檢查:

int value1, value2, sum;
unsigned int usum = (unsigned int)value1 + value2;
 
if((usum ^ value1) & (usum ^ value2) & INT_MIN)
{
	/*處理溢出情況*/
}
else
{
	sum = value1 + value2;
}

使用的原理解釋一下,因?yàn)樵诩臃ㄟ\(yùn)算中,操作數(shù)value1和value2只有符號相同時(shí),才可能發(fā)生溢出,所以我們先將這兩個(gè)數(shù)轉(zhuǎn)換為無符號類型,兩個(gè)數(shù)的和保存在變量usum中。如果發(fā)生溢出,則value1、value2和usum的最高位(符號位)一定不同,表達(dá)式(usum ^ value1) & (usum ^ value2) 的最高位一定為1,這個(gè)表達(dá)式位與(&)上INT_MIN是為了將最高位之外的其它位設(shè)置為0。

了解你所用的編譯器對未定義行為的處理策略

很多引入了未定義行為的程序也能運(yùn)行良好,這要?dú)w功于編譯器處理未定義行為的策略。不是你的代碼寫的正確,而是恰好編譯器處理策略跟你需要的邏輯相同。了解編譯器的未定義行為處理策略,可以讓你更清楚的認(rèn)識到那些引入了未定義行為程序能夠運(yùn)行良好是多么幸運(yùn)的事,不然多換幾個(gè)編譯器試試!

以Keil MDK為例,列舉常用的處理策略如下:

1)有符號量的右移是算術(shù)移位,即移位時(shí)要保證符號位不改變。

2)對于int類的值:超過31位的左移結(jié)果為零;無符號值或正的有符號值超過31位的右移結(jié)果為零。負(fù)的有符號值移位結(jié)果為-1。

3)整型數(shù)除以零返回零

3.4 了解你的編譯器

在嵌入式開發(fā)過程中,我們需要經(jīng)常和編譯器打交道,只有深入了解編譯器,才能用好它,編寫更高效代碼,更靈活的操作硬件,實(shí)現(xiàn)一些高級功能。下面以公司最常用的Keil MDK為例,來描述一下編譯器的細(xì)節(jié)。

3.4.1編譯器的一些小知識

1)默認(rèn)情況下,char類型的數(shù)據(jù)項(xiàng)是無符號的,所以它的取值范圍是0~255;

2)在所有的內(nèi)部和外部標(biāo)識符中,大寫和小寫字符不同;

3)通常局部變量保存在寄存器中,但當(dāng)局部變量太多放到棧里的時(shí)候,它們總是字對齊的。

4)壓縮類型的自然對齊方式為1。使用關(guān)鍵字__packed來壓縮特定結(jié)構(gòu),將所有有效類型的對齊邊界設(shè)置為1;

5)整數(shù)以二進(jìn)制補(bǔ)碼形式表示;浮點(diǎn)量按IEEE格式存儲(chǔ);

6)整數(shù)除法的余數(shù)的符號于被除數(shù)相同,由ISO C90標(biāo)準(zhǔn)得出;

7)如果整型值被截?cái)酁槎痰挠蟹栒?,則通過放棄適當(dāng)數(shù)目的最高有效位來得到結(jié)果。如果原始數(shù)是太大的正或負(fù)數(shù),對于新的類型,無法保證結(jié)果的符號將于原始數(shù)相同。

8)整型數(shù)超界不引發(fā)異常;像unsigned char test; test=1000;這類是不會(huì)報(bào)錯(cuò)的;

9)在嚴(yán)格C中,枚舉值必須被表示為整型。例如,必須在?2147483648 到+2147483647的范圍內(nèi)。但MDK自動(dòng)使用對象包含enum范圍的最小整型來實(shí)現(xiàn)(比如char類型),除非使用編譯器命令??enum_is_int 來強(qiáng)制將enum的基礎(chǔ)類型設(shè)為至少和整型一樣寬。超出范圍的枚舉值默認(rèn)僅產(chǎn)生警告:#66:enumeration value is out of "int" range;

10)對于結(jié)構(gòu)體填充,根據(jù)定義結(jié)構(gòu)的方式,keil MDK編譯器用以下方式的一種來填充結(jié)構(gòu):

I> 定義為static或者extern的結(jié)構(gòu)用零填充;

II> 棧或堆上的結(jié)構(gòu),例如,用malloc()或者auto定義的結(jié)構(gòu),使用先前存儲(chǔ)在那些存儲(chǔ)器位置的任何內(nèi)容進(jìn)行填充。不能使用memcmp()來比較以這種方式定義的填充結(jié)構(gòu)!

11)編譯器不對聲明為volatile類型的數(shù)據(jù)進(jìn)行優(yōu)化;

12)__nop():延時(shí)一個(gè)指令周期,編譯器絕不會(huì)優(yōu)化它。如果硬件支持NOP指令,則該句被替換為NOP指令,如果硬件不支持NOP指令,編譯器將它替換為一個(gè)等效于NOP的指令,具體指令由編譯器自己決定;

13)__align(n):指示編譯器在n 字節(jié)邊界上對齊變量。對于局部變量,n的值為1、2、4、8;

14)__attribute__((at(address))):可以使用此變量屬性指定變量的絕對地址;

15)__inline:提示編譯器在合理的情況下內(nèi)聯(lián)編譯C或C++ 函數(shù);

3.4.2初始化的全局變量和靜態(tài)變量的初始值被放到了哪里?

我們程序中的一些全局變量和靜態(tài)變量在定義時(shí)進(jìn)行了初始化,經(jīng)過編譯器編譯后,這些初始值被存放在了代碼的哪里?我們舉個(gè)例子說明:

	unsigned int g_unRunFlag=0xA5;  
	static unsigned int s_unCountFlag=0x5A; 

我曾做過一個(gè)項(xiàng)目,項(xiàng)目中的一個(gè)設(shè)備需要在線編程,也就是通過協(xié)議,將上位機(jī)發(fā)給設(shè)備的數(shù)據(jù)通過在應(yīng)用編程(IAP)技術(shù)寫入到設(shè)備的內(nèi)部Flash中。我將內(nèi)部Flash做了劃分,一小部分運(yùn)行程序,大部分用來存儲(chǔ)上位機(jī)發(fā)來的數(shù)據(jù)。隨著程序量的增加,在一次更新程序后發(fā)現(xiàn),在線編程之后,設(shè)備運(yùn)行正常,但是重啟設(shè)備后,運(yùn)行出現(xiàn)了故障!經(jīng)過一系列排查,發(fā)現(xiàn)故障的原因是一個(gè)全局變量的初值被改變了。這是件很不可思議的事情,你在定義這個(gè)變量的時(shí)候指定了初始值,當(dāng)你在第一次使用這個(gè)變量時(shí)卻發(fā)現(xiàn)這個(gè)初值已經(jīng)被改掉了!這中間沒有對這個(gè)變量做任何賦值操作,其它變量也沒有任何溢出,并且多次在線調(diào)試表明,進(jìn)入main函數(shù)的時(shí)候,該變量的初值已經(jīng)被改為一個(gè)恒定值。

要想知道為什么全局變量的初值被改變,就要了解這些初值編譯后被放到了二進(jìn)制文件的哪里。在此之前,需要先了解一點(diǎn)鏈接原理。

ARM映象文件各組成部分在存儲(chǔ)系統(tǒng)中的地址有兩種:一種是映象文件位于存儲(chǔ)器時(shí)(通俗的說就是存儲(chǔ)在Flash中的二進(jìn)制代碼)的地址,稱為加載地址;一種是映象文件運(yùn)行時(shí)(通俗的說就是給板子上電,開始運(yùn)行Flash中的程序了)的地址,稱為運(yùn)行時(shí)地址。賦初值的全局變量和靜態(tài)變量在程序還沒運(yùn)行的時(shí)候,初值是被放在Flash中的,這個(gè)時(shí)候他們的地址稱為加載地址,當(dāng)程序運(yùn)行后,這些初值會(huì)從Flash中拷貝到RAM中,這時(shí)候就是運(yùn)行時(shí)地址了。

原來,對于在程序中賦初值的全局變量和靜態(tài)變量,程序編譯后,MDK將這些初值放到Flash中,位于緊靠在可執(zhí)行代碼的后面。在程序進(jìn)入main函數(shù)前,會(huì)運(yùn)行一段庫代碼,將這部分?jǐn)?shù)據(jù)拷貝至相應(yīng)RAM位置。由于我的設(shè)備程序量不斷增加,超過了為設(shè)備程序預(yù)留的Flash空間,在線編程時(shí),將一部分存儲(chǔ)全局變量和靜態(tài)變量初值的Flash給重新編程了。在重啟設(shè)備前,初值已經(jīng)被拷貝到RAM中,所以這個(gè)時(shí)候程序運(yùn)行是正常的,但重新上電后,這部分初值實(shí)際上是在線編程的數(shù)據(jù),自然與初值不同了。

3.4.3在C代碼中使用的變量,編譯器將他們分配到RAM的哪里?

我們會(huì)在代碼中使用各種變量,比如全局變量、靜態(tài)變量、局部變量,并且這些變量時(shí)由編譯器統(tǒng)一管理的,有時(shí)候我們需要知道變量用掉了多少RAM,以及這些變量在RAM中的具體位置。這是一個(gè)經(jīng)常會(huì)遇到的事情,舉一個(gè)例子,程序中的一個(gè)變量在運(yùn)行時(shí)總是不正常的被改變,那么有理由懷疑它臨近的變量或數(shù)組溢出了,溢出的數(shù)據(jù)更改了這個(gè)變量值。要排查掉這個(gè)可能性,就必須知道該變量被分配到RAM的哪里、這個(gè)位置附近是什么變量,以便針對性的做跟蹤。

其實(shí)MDK編譯器的輸出文件中有一個(gè)“工程名.map”文件,里面記錄了代碼、變量、堆棧的存儲(chǔ)位置,通過這個(gè)文件,可以查看使用的變量被分配到RAM的哪個(gè)位置。要生成這個(gè)文件,需要在Options for Targer窗口,Listing標(biāo)簽欄下,勾選Linker Listing前的復(fù)選框,如圖3-1所示。

圖3-1 設(shè)置編譯器生產(chǎn)MAP文件

3.4.4默認(rèn)情況下,棧被分配到RAM的哪個(gè)地方?

MDK中,我們只需要在配置文件中定義堆棧大小,編譯器會(huì)自動(dòng)在RAM的空閑區(qū)域選擇一塊合適的地方來分配給我們定義的堆棧,這個(gè)地方位于RAM的那個(gè)地方呢?

通過查看MAP文件,原來MDK將堆棧放到程序使用到的RAM空間的后面,比如你的RAM空間從0x4000 0000開始,你的程序用掉了0x200字節(jié)RAM,那么堆??臻g就從0x4000 0200處開始。

使用了多少堆棧,是否溢出?

3.4.5有多少RAM會(huì)被初始化?

在進(jìn)入main()函數(shù)之前,MDK會(huì)把未初始化的RAM給清零的,我們的RAM可能很大,只使用了其中一小部分,MDK會(huì)不會(huì)把所有RAM都初始化呢?

答案是否定的,MDK只是把你的程序用到的RAM以及堆棧RAM給初始化,其它RAM的內(nèi)容是不管的。如果你要使用絕對地址訪問MDK未初始化的RAM,那就要小心翼翼的了,因?yàn)檫@些RAM上電時(shí)的內(nèi)容很可能是隨機(jī)的,每次上電都不同。

3.4.6MDK編譯器如何設(shè)置非零初始化變量?

對于控制類產(chǎn)品,當(dāng)系統(tǒng)復(fù)位后(非上電復(fù)位),可能要求保持住復(fù)位前RAM中的數(shù)據(jù),用來快速恢復(fù)現(xiàn)場,或者不至于因瞬間復(fù)位而重啟現(xiàn)場設(shè)備。而keil mdk在默認(rèn)情況下,任何形式的復(fù)位都會(huì)將RAM區(qū)的非初始化變量數(shù)據(jù)清零。

MDK編譯程序生成的可執(zhí)行文件中,每個(gè)輸出段都最多有三個(gè)屬性:RO屬性、RW屬性和ZI屬性。對于一個(gè)全局變量或靜態(tài)變量,用const修飾符修飾的變量最可能放在RO屬性區(qū),初始化的變量會(huì)放在RW屬性區(qū),那么剩下的變量就要放到ZI屬性區(qū)了。默認(rèn)情況下,ZI屬性區(qū)的數(shù)據(jù)在每次復(fù)位后,程序執(zhí)行main函數(shù)內(nèi)的代碼之前,由編譯器“自作主張”的初始化為零。所以我們要在C代碼中設(shè)置一些變量在復(fù)位后不被零初始化,那一定不能任由編譯器“胡作非為”,我們要用一些規(guī)則,約束一下編譯器。

分散加載文件對于連接器來說至關(guān)重要,在分散加載文件中,使用UNINIT來修飾一個(gè)執(zhí)行節(jié),可以避免編譯器對該區(qū)節(jié)的ZI數(shù)據(jù)進(jìn)行零初始化。這是要解決非零初始化變量的關(guān)鍵。因此我們可以定義一個(gè)UNINIT修飾的數(shù)據(jù)節(jié),然后將希望非零初始化的變量放入這個(gè)區(qū)域中。于是,就有了第一種方法:

1)修改分散加載文件,增加一個(gè)名為MYRAM的執(zhí)行節(jié),該執(zhí)行節(jié)起始地址為0x1000A000,長度為0x2000字節(jié)(8KB),由UNINIT修飾:

 LR_IROM1 0x00000000 0x00080000  {    ; load region size_region
 ER_IROM1 0x00000000 0x00080000  {  ; load address = execution address
  *.o (RESET, +First)
  *(InRoot$$Sections)
  .ANY (+RO)
 }
 RW_IRAM1 0x10000000 0x0000A000  {  ; RW data
  .ANY (+RW +ZI)
 }
  MYRAM 0x1000A000 UNINIT 0x00002000  {
   .ANY (NO_INIT)
  }
}

那么,如果在程序中有一個(gè)數(shù)組,你不想讓它復(fù)位后零初始化,就可以這樣來定義變量:

unsigned char plc_eu_backup[32] __attribute__((at(0x1000A000)));

變量屬性修飾符__attribute__((at(adde)))用來將變量強(qiáng)制定位到adde所在地址處。由于地址0x1000A000開始的8KB區(qū)域ZI變量不會(huì)被零初始化,所以位于這一區(qū)域的數(shù)組plc_eu_backup也就不會(huì)被零初始化了。

這種方法的缺點(diǎn)是顯而易見的:要程序員手動(dòng)分配變量的地址。如果非零初始化數(shù)據(jù)比較多,這將是件難以想象的大工程(以后的維護(hù)、增加、修改代碼等等)。所以要找到一種辦法,讓編譯器去自動(dòng)分配這一區(qū)域的變量。

2)分散加載文件同方法1,如果還是定義一個(gè)數(shù)組,可以用下面方法:

unsigned char plc_eu_backup[32] __attribute__((section("NO_INIT"),zero_init));

變量屬性修飾符__attribute__((section(“name”),zero_init))用于將變量強(qiáng)制定義到name屬性數(shù)據(jù)節(jié)中,zero_init表示將未初始化的變量放到ZI數(shù)據(jù)節(jié)中。因?yàn)?ldquo;NO_INIT”這顯性命名的自定義節(jié),具有UNINIT屬性。

3)將一個(gè)模塊內(nèi)的非初始化變量都非零初始化

假如該模塊名字為test.c,修改分散加載文件如下所示:

   LR_IROM1 0x00000000 0x00080000  {    ; load region size_region
   ER_IROM1 0x00000000 0x00080000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
   }
   RW_IRAM1 0x10000000 0x0000A000  {  ; RW data
    .ANY (+RW +ZI)
   }
    RW_IRAM2 0x1000A000 UNINIT 0x00002000  {
     test.o (+ZI)
    }
  }

在該模塊定義時(shí)變量時(shí)使用如下方法:

這里,變量屬性修飾符__attribute__((zero_init))用于將未初始化的變量放到ZI數(shù)據(jù)節(jié)中變量,其實(shí)MDK默認(rèn)情況下,未初始化的變量就是放在ZI數(shù)據(jù)區(qū)的。

4. 防御性編程

嵌入式產(chǎn)品的可靠性自然與硬件密不可分,但在硬件確定、并且沒有第三方測試的前提下,使用防御性編程思想寫出的代碼,往往具有更高的穩(wěn)定性。

防御性編程首先需要認(rèn)清C語言的種種缺陷和陷阱,C語言對于運(yùn)行時(shí)的檢查十分弱小,需要程序員謹(jǐn)慎的考慮代碼,在必要的時(shí)候增加判斷;防御性編程的另一個(gè)核心思想是假設(shè)代碼運(yùn)行在并不可靠的硬件上,外接干擾有可能會(huì)打亂程序執(zhí)行順序、更改RAM存儲(chǔ)數(shù)據(jù)等等。

4.1 具有形參的函數(shù),需判斷傳遞來的實(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 
          {  
              //處理錯(cuò)誤代碼  
         }  
    }  

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

對函數(shù)返回的錯(cuò)誤碼,要進(jìn)行全面仔細(xì)處理,必要時(shí)做錯(cuò)誤記錄。

 	char *DoSomething(…)  
 	{  
 	    char * p;  
 	     p=malloc(1024);  
 	     if(p==NULL)          /*對函數(shù)返回值作出判斷*/
 	     {         
 	        UARTprintf(…);   /*打印錯(cuò)誤信息*/  
 	         return NULL;  
 	     }  
 	     retuen p;  
 	}  

4.3 防止指針越界

如果動(dòng)態(tài)計(jì)算一個(gè)地址時(shí),要保證被計(jì)算的地址是合理的并指向某個(gè)有意義的地方。特別對于指向一個(gè)結(jié)構(gòu)或數(shù)組的內(nèi)部的指針,當(dāng)指針增加或者改變后仍然指向同一個(gè)結(jié)構(gòu)或數(shù)組。

4.4 防止數(shù)組越界

數(shù)組越界的問題前文已經(jīng)講述的很多了,由于C不會(huì)對數(shù)組進(jìn)行有效的檢測,因此必須在應(yīng)用中顯式的檢測數(shù)組越界問題。下面的例子可用于中斷接收通訊數(shù)據(jù)。

	#define REC_BUF_LEN 100  
	unsigned char RecBuf[REC_BUF_LEN];  
	//其它代碼  
	void Uart_IRQHandler(void)  
	{  
	    static RecCount=0;          //接收數(shù)據(jù)長度計(jì)數(shù)器  
	    //其它代碼  
	    if(RecCount< REC_BUF_LEN)   //判斷數(shù)組是否越界
	    {  
	         RecBuf[RecCount]=…;     //從硬件取數(shù)據(jù)  
	         RecCount++;  
	         //其它代碼  
	    } 
	    else 
	    {  
	        //錯(cuò)誤處理代碼   
	    }  
	     //其它代碼 
	}  

在使用一些庫函數(shù)時(shí),同樣需要對邊界進(jìn)行檢查,比如下面的memset(RecBuf,0,len)函數(shù)把RecBuf指指向的內(nèi)存區(qū)的前l(fā)en個(gè)字節(jié)用0填充,如果不注意len的長度,就會(huì)將數(shù)組RecBuf之外的內(nèi)存區(qū)清零:

	#define REC_BUF_LEN 100  
	unsigned char RecBuf[REC_BUF_LEN];  
	  
	if(len< REC_BUF_LEN)
	{  
	    memset(RecBuf,0,len);       //將數(shù)組RecBuf清零  
	} 
	else 
	{  
	    //處理錯(cuò)誤  
	}  

4.5 數(shù)學(xué)算數(shù)運(yùn)算

4.5.1除法運(yùn)算,只檢測除數(shù)為零就可靠嗎?

除法運(yùn)算前,檢查除數(shù)是否為零幾乎已經(jīng)成為共識,但是僅檢查除數(shù)是否為零就夠了嗎?

考慮兩個(gè)整數(shù)相除,對于一個(gè)signed long類型變量,它能表示的數(shù)值范圍為:-2147483648 ~+2147483647,如果讓-2147483648/ -1,那么結(jié)果應(yīng)該是+2147483648,但是這個(gè)結(jié)果已經(jīng)超出了signedlong所能表示的范圍了。所以,在這種情況下,除了要檢測除數(shù)是否為零外,還要檢測除法是否溢出。

	#include <limits.h>    
	signed long sl1,sl2,result;    
	/*初始化sl1和sl2*/    
	if((sl2==0)||(sl1==LONG_MIN && sl2==-1))  
	{    
	    //處理錯(cuò)誤    
	}   
	else   
	{    
	    result = sl1 / sl2;    
	}

4.5.2檢測運(yùn)算溢出

整數(shù)的加減乘運(yùn)算都有可能發(fā)生溢出,在討論未定義行為時(shí),給出過一個(gè)有符號整形加法溢出判斷代碼,這里再給出一個(gè)無符號整形加法溢出判斷代碼段:

	#include <limits.h>    
	unsigned int a,b,result;    
	/*初始化a,b*/    
	if(UINT_MAX-a<b)  
	{    
	    //處理溢出    
	}   
	else   
	{    
	    result=a+b;    
	}

嵌入式硬件一般沒有浮點(diǎn)處理器,浮點(diǎn)數(shù)運(yùn)算在嵌入式也比較少見并且溢出判斷嚴(yán)重依賴C庫支持,這里不討論。

4.5.3檢測移位

在討論未定義行為時(shí),提到有符號數(shù)右移、移位的數(shù)量是負(fù)值或者大于操作數(shù)的位數(shù)都是未定義行為,也提到不對有符號數(shù)進(jìn)行位操作,但要檢測移位的數(shù)量是否大于操作數(shù)的位數(shù)。下面給出一個(gè)無符號整數(shù)左移檢測代碼段:

	unsigned int ui1;  
	unsigned int ui2;  
	unsigned int uresult;  
	  
	/*初始化ui1,ui2*/  
	if(ui2>=sizeof(unsigned int)*CHAR_BIT)  
	{  
	    //處理錯(cuò)誤  
	}  
	else  
	{  
	    uresult=ui1<<ui2;  
	}  

4.6 如果有硬件看門狗,則使用它

在其它一切措施都失效的情況下,看門狗可能是最后的防線。它的原理特別簡單,但卻能大大提高設(shè)備的可靠性。如果設(shè)備有硬件看門狗,一定要為它編寫驅(qū)動(dòng)程序。

要盡可能早的開啟看門狗

這是因?yàn)閺纳想姀?fù)位結(jié)束到開啟看門狗的這段時(shí)間內(nèi),設(shè)備有可能被干擾而跳過看門狗初始化程序,導(dǎo)致看門狗失效。盡可能早的開啟看門狗,可以降低這種概率;

不要在中斷中喂狗,除非有其他聯(lián)動(dòng)措施

在中斷程序喂狗,由于干擾的存在,程序可能一直處于中斷之中,這樣會(huì)導(dǎo)致看門狗失效。如果在主程序中設(shè)置標(biāo)志位,中斷程序喂狗時(shí)與這個(gè)標(biāo)志位聯(lián)合判斷,也是允許的;

喂狗間隔跟產(chǎn)品需求有關(guān),并非特定的時(shí)間

產(chǎn)品的特性決定了喂狗間隔。對于不涉及安全性、實(shí)時(shí)性的設(shè)備,喂狗間隔比較寬松,但間隔時(shí)間不宜過長,否則被用戶感知到,是影響用戶體驗(yàn)的。對于設(shè)計(jì)安全性、有實(shí)時(shí)控制類的設(shè)備,原則是盡可能快的復(fù)位,否則會(huì)造成事故。

克萊門汀號在進(jìn)行第二階段的任務(wù)時(shí),原本預(yù)訂要從月球飛行到太空深處的Geographos小行星進(jìn)行探勘,然而這艘太空探測器在飛向小行星時(shí)卻由于一個(gè)軟件缺陷而使其中斷運(yùn)作20分鐘,不但未能到達(dá)小行星,也因?yàn)榭刂茋娮烊紵?1分鐘使電力供應(yīng)降低,無法再透過遠(yuǎn)端控制探測器,最終結(jié)束這項(xiàng)任務(wù),但也導(dǎo)致了資源與資金的浪費(fèi)。

“克萊門汀太空任務(wù)失敗這件事讓我感到十分震驚,它其實(shí)可以透過硬件中一款簡單的看門狗計(jì)時(shí)器避免掉這項(xiàng)意外,但由于當(dāng)時(shí)的開發(fā)時(shí)間相當(dāng)緊縮,程序設(shè)計(jì)人員沒時(shí)間編寫程序來啟動(dòng)它,”Ganssle說。

遺憾的是,1998年發(fā)射的近地號太空船(NEAR)也遇到了相同的問題。由于編程人員并未采納建議,因此,當(dāng)推進(jìn)器減速器系統(tǒng)故障時(shí),29公斤的儲(chǔ)備燃料也隨之報(bào)銷──這同樣是一個(gè)本來可經(jīng)由看門狗定時(shí)器編程而避免的問題,同時(shí)也證明要從其他程序設(shè)計(jì)人員的錯(cuò)誤中學(xué)習(xí)并不容易。

4.7關(guān)鍵數(shù)據(jù)儲(chǔ)存多個(gè)備份,取數(shù)據(jù)采用“表決法”

RAM中的數(shù)據(jù)在受到干擾情況下有可能被改變,對于系統(tǒng)關(guān)鍵數(shù)據(jù)應(yīng)該進(jìn)行保護(hù)。關(guān)鍵數(shù)據(jù)包括全局變量、靜態(tài)變量以及需要保護(hù)的數(shù)據(jù)區(qū)域。備份數(shù)據(jù)與原數(shù)據(jù)不應(yīng)該處于相鄰位置,因此不應(yīng)由編譯器默認(rèn)分配備份數(shù)據(jù)位置,而應(yīng)該由程序員指定區(qū)域存儲(chǔ)??梢詫AM分為3個(gè)區(qū)域,第一個(gè)區(qū)域保存原碼,第二個(gè)區(qū)域保存反碼,第三個(gè)區(qū)域保存異或碼,區(qū)域之間預(yù)留一定量的“空白”RAM作為隔離??梢允褂镁幾g器的“分散加載”機(jī)制將變量分別存儲(chǔ)在這些區(qū)域。需要進(jìn)行讀取時(shí),同時(shí)讀出3份數(shù)據(jù)并進(jìn)行表決,取至少有兩個(gè)相同的那個(gè)值。

假如設(shè)備的RAM從0x1000_0000開始,我需要在RAM的0x1000_0000~0x10007FFF內(nèi)存儲(chǔ)原碼,在0x1000_9000~0x10009FFF內(nèi)存儲(chǔ)反碼,在0x1000_B000~0x1000BFFF內(nèi)存儲(chǔ)0xAA的異或碼,編譯器的分散加載可以設(shè)置為:

	LR_IROM1 0x00000000 0x00080000  {    ; load region size_region  
	  ER_IROM1 0x00000000 0x00080000  {  ; load address = execution address  
	   *.o (RESET, +First)  
	   *(InRoot$$Sections)  
	   .ANY (+RO)  
	  }  
	  RW_IRAM1 0x10000000 0x00008000  {  ;保存原碼  
	   .ANY (+RW +ZI )  
	  }  
	  RW_IRAM3 0x10009000 0x00001000{    ;保存反碼  
	   .ANY (MY_BK1)  
	  }  
	  RW_IRAM2 0x1000B000 0x00001000  {  ;保存異或碼  
	   .ANY (MY_BK2)  
	  }  
	} 

如果一個(gè)關(guān)鍵變量需要多處備份,可以按照下面方式定義變量,將三個(gè)變量分別指定到三個(gè)不連續(xù)的RAM區(qū)中,并在定義時(shí)按照原碼、反碼、0xAA的異或碼進(jìn)行初始化。

	uint32  plc_pc=0;                                                       //原碼  
	__attribute__((section("MY_BK1"))) uint32 plc_pc_not=~0x0;              //反碼  
	__attribute__((section("MY_BK2"))) uint32 plc_pc_xor=0x0^0xAAAAAAAA;    //異或碼

當(dāng)需要寫這個(gè)變量時(shí),這三個(gè)位置都要更新;讀取變量時(shí),讀取三個(gè)值做判斷,取至少有兩個(gè)相同的那個(gè)值。

為什么選取異或碼而不是補(bǔ)碼?這是因?yàn)镸DK的整數(shù)是按照補(bǔ)碼存儲(chǔ)的,正數(shù)的補(bǔ)碼與原碼相同,在這種情況下,原碼和補(bǔ)碼是一致的,不但起不到冗余作用,反而對可靠性有害。比如存儲(chǔ)的一個(gè)非零整數(shù)區(qū)因?yàn)楦蓴_,RAM都被清零,由于原碼和補(bǔ)碼一致,按照3取2的“表決法”,會(huì)將干擾值0當(dāng)做正確的數(shù)據(jù)。

4.8對非易失性存儲(chǔ)器進(jìn)行備份存儲(chǔ)

非易失性存儲(chǔ)器包括但不限于Flash、EEPROM、鐵電。僅僅將寫入非易失性存儲(chǔ)器中的數(shù)據(jù)再讀出校驗(yàn)是不夠的。強(qiáng)干擾情況下可能導(dǎo)致非易失性存儲(chǔ)器內(nèi)的數(shù)據(jù)錯(cuò)誤,在寫非易失性存儲(chǔ)器的期間系統(tǒng)掉電將導(dǎo)致數(shù)據(jù)丟失,因干擾導(dǎo)致程序跑飛到寫非易失性存儲(chǔ)器函數(shù)中,將導(dǎo)致數(shù)據(jù)存儲(chǔ)紊亂。一種可靠的辦法是將非易失性存儲(chǔ)器分成多個(gè)區(qū),每個(gè)數(shù)據(jù)都將按照不同的形式寫入到這些分區(qū)中,需要進(jìn)行讀取時(shí),同時(shí)讀出多份數(shù)據(jù)并進(jìn)行表決,取相同數(shù)目較多的那個(gè)值。

4.9軟件鎖

對于初始化序列或者有一定先后順序的函數(shù)調(diào)用,為了保證調(diào)用順序或者確保每個(gè)函數(shù)都被調(diào)用,我們可以使用環(huán)環(huán)相扣,實(shí)質(zhì)上這也是一種軟件鎖。此外對于一些安全關(guān)鍵代碼語句(是語句,而不是函數(shù)),可以給它們設(shè)置軟件鎖,只有持有特定鑰匙的,才可以訪問這些關(guān)鍵代碼。也可以通俗的理解為,關(guān)鍵安全代碼不能按照單一條件執(zhí)行,要額外的多設(shè)置一個(gè)標(biāo)志。

比如,向Flash寫一個(gè)數(shù)據(jù),我們會(huì)判斷數(shù)據(jù)是否合法、寫入的地址是否合法,計(jì)算要寫入的扇區(qū)。之后調(diào)用寫Flash子程序,在這個(gè)子程序中,判斷扇區(qū)地址是否合法、數(shù)據(jù)長度是否合法,之后就要將數(shù)據(jù)寫入Flash。由于寫Flash語句是安全關(guān)鍵代碼,所以程序給這些語句上鎖:必須具有正確的鑰匙才可以寫Flash。這樣即使是程序跑飛到寫Flash子程序,也能大大降低誤寫的風(fēng)險(xiǎn)。

	/**************************************************************************** 
	* 名稱:RamToFlash() 
	* 功能:復(fù)制RAM的數(shù)據(jù)到FLASH,命令代碼51。 
	* 入口參數(shù): dst        目標(biāo)地址,即FLASH起始地址。以512字節(jié)為分界 
	*           src        源地址,即RAM地址。地址必須字對齊 
	*           no         復(fù)制字節(jié)個(gè)數(shù),為512/1024/4096/8192 
	*           ProgStart  軟件鎖標(biāo)志    
	* 出口參數(shù):IAP返回值(paramout緩沖區(qū)) CMD_SUCCESS,SRC_ADDR_ERROR,DST_ADDR_ERROR, 
	SRC_ADDR_NOT_MAPPED,DST_ADDR_NOT_MAPPED,COUNT_ERROR,BUSY,未選擇扇區(qū) 
	****************************************************************************/  
	void  RamToFlash(uint32 dst, uint32 src, uint32 no,uint8 ProgStart)  
	{     
	    PLC_ASSERT("Sector number",(dst>=0x00040000)&&(dst<=0x0007FFFF));  
	    PLC_ASSERT("Copy bytes number is 512",(no==512));  
	    PLC_ASSERT("ProgStart==0xA5",(ProgStart==0xA5));  
	    paramin[0] = IAP_RAMTOFLASH;             // 設(shè)置命令字  
	    paramin[1] = dst;                        // 設(shè)置參數(shù)  
	    paramin[2] = src;  
	    paramin[3] = no;  
	    paramin[4] = Fcclk/1000;                      
	    if(ProgStart==0xA5)                     //只有軟件鎖標(biāo)志正確時(shí),才執(zhí)行關(guān)鍵代碼  
	    {  
	        iap_entry(paramin, paramout);       // 調(diào)用IAP服務(wù)程序                 
	        ProgStart=0;   
	    }  
	    else  
	    {  
	        paramout[0]=PROG_UNSTART;     
	    }  
	}

該程序段是編程lpc1778內(nèi)部Flash,其中調(diào)用IAP程序的函數(shù)iap_entry(paramin,paramout)是關(guān)鍵安全代碼,所以在執(zhí)行該代碼前,先判斷一個(gè)特定設(shè)置的安全鎖標(biāo)志ProgStart,只有這個(gè)標(biāo)志符合設(shè)定值,才會(huì)執(zhí)行編程Flash操作。如果因?yàn)橐馔獬绦蚺茱w到該函數(shù),由于ProgStart標(biāo)志不正確,是不會(huì)對Flash進(jìn)行編程的。

4.10通信

通訊線上的數(shù)據(jù)誤碼相對嚴(yán)重,通訊線越長,所處的環(huán)境越惡劣,誤碼會(huì)越嚴(yán)重。拋開硬件和環(huán)境的作用,我們的軟件應(yīng)能識別錯(cuò)誤的通訊數(shù)據(jù)。對此有一些應(yīng)用措施:

制定協(xié)議時(shí),限制每幀的字節(jié)數(shù);

每幀字節(jié)數(shù)越多,發(fā)生誤碼的可能性就越大,無效的數(shù)據(jù)也會(huì)越多。對此以太網(wǎng)規(guī)定每幀數(shù)據(jù)不大于1500字節(jié),高可靠性的CAN收發(fā)器規(guī)定每幀數(shù)據(jù)不得多于8字節(jié),對于RS485,基于RS485鏈路應(yīng)用最廣泛的Modbus協(xié)議一幀數(shù)據(jù)規(guī)定不超過256字節(jié)。因此,建議制定內(nèi)部通訊協(xié)議時(shí),使用RS485時(shí)規(guī)定每幀數(shù)據(jù)不超過256字節(jié);

使用多種校驗(yàn)

編寫程序時(shí)應(yīng)使能奇偶校驗(yàn),每幀超過16字節(jié)的應(yīng)用,建議至少編寫CRC16校驗(yàn)程序;

增加額外判斷

1)增加緩沖區(qū)溢出判斷。這是因?yàn)閿?shù)據(jù)接收多是在中斷中完成,編譯器檢測不出緩沖區(qū)是否溢出,需要手動(dòng)檢查,在上文介紹數(shù)據(jù)溢出一節(jié)中已經(jīng)詳細(xì)說明。

2)增加超時(shí)判斷。當(dāng)一幀數(shù)據(jù)接收到一半,長時(shí)間接收不到剩余數(shù)據(jù),則認(rèn)為這幀數(shù)據(jù)無效,重新開始接收。可選,跟不同的協(xié)議有關(guān),但緩沖區(qū)溢出判斷必須實(shí)現(xiàn)。這是因?yàn)閷τ谛枰獛^判斷的協(xié)議,上位機(jī)可能發(fā)送完幀頭后突然斷電,重啟后上位機(jī)是從新的幀開始發(fā)送的,但是下位機(jī)已經(jīng)接收到了上次未發(fā)送完的幀頭,所以上位機(jī)的這次幀頭會(huì)被下位機(jī)當(dāng)成正常數(shù)據(jù)接收。這有可能造成數(shù)據(jù)長度字段為一個(gè)很大的值,填滿該長度的緩沖區(qū)需要相當(dāng)多的數(shù)據(jù)(比如一幀可能1000字節(jié)),影響響應(yīng)時(shí)間;另一方面,如果程序沒有緩沖區(qū)溢出判斷,那么緩沖區(qū)很可能溢出,后果是災(zāi)難性的。

重傳機(jī)制

如果檢測到通訊數(shù)據(jù)發(fā)生了錯(cuò)誤,則要有重傳機(jī)制重新發(fā)送出錯(cuò)的幀。

4.11開關(guān)量輸入的檢測、確認(rèn)

開關(guān)量容易受到尖脈沖干擾,如果不進(jìn)行濾除,可能會(huì)造成誤動(dòng)作。一般情況下,需要對開關(guān)量輸入信號進(jìn)行多次采樣,并進(jìn)行邏輯判斷直到確認(rèn)信號無誤為止。

4.12開關(guān)量輸出

開關(guān)信號簡單的一次輸出是不安全的,干擾信號可能會(huì)翻轉(zhuǎn)開關(guān)量輸出的狀態(tài)。采取重復(fù)刷新輸出可以有效防止電平的翻轉(zhuǎn)。

4.13初始化信息的保存和恢復(fù)

微處理器的寄存器值也可能會(huì)因外界干擾而改變,外設(shè)初始化值需要在寄存器中長期保存,最容易被破壞。由于Flash中的數(shù)據(jù)相對不易被破壞,可以將初始化信息預(yù)先寫入Flash,待程序空閑時(shí)比較與初始化相關(guān)的寄存器值是否被更改,如果發(fā)現(xiàn)非法更改則使用Flash中的值進(jìn)行恢復(fù)。

公司目前使用的4.3寸LCD顯示屏抗干擾能力一般。如果顯示屏與控制器之間的排線距離過長或者對使用該顯示屏的設(shè)備打靜電或者脈沖群,顯示屏有可能會(huì)花屏或者白屏。對此,我們可以將初始化顯示屏的數(shù)據(jù)保存在Flash中,程序運(yùn)行后,每隔一段時(shí)間從顯示屏的寄存器讀出當(dāng)前值和Flash存儲(chǔ)的值相比較,如果發(fā)現(xiàn)兩者不同,則重新初始化顯示屏。下面給出校驗(yàn)源碼,僅供參考。

定義數(shù)據(jù)結(jié)構(gòu):

    typedef struct {  
        uint8_t  lcd_command;           //LCD寄存器  
        uint8_t  lcd_get_value[8];      //初始化時(shí)寫入寄存器的值  
        uint8_t  lcd_value_num;         //初始化時(shí)寫入寄存器值的數(shù)目  
    }lcd_redu_list_struct; 

定義const修飾的結(jié)構(gòu)體變量,存儲(chǔ)LCD部分寄存器的初始值,這個(gè)初始值跟具體的應(yīng)用初始化有關(guān),不一定是表中的數(shù)據(jù),通常情況下,這個(gè)結(jié)構(gòu)體變量被存儲(chǔ)到Flash中。

	/*LCD部分寄存器設(shè)置值列表*/  
	lcd_redu_list_struct const lcd_redu_list_str[]=  
	{  
	  {SSD1963_Get_Address_Mode,{0x20}                                   ,1}, /*1*/ 
	  {SSD1963_Get_Pll_Mn      ,{0x3b,0x02,0x04}                         ,3}, /*2*/ 
	  {SSD1963_Get_Pll_Status  ,{0x04}                                   ,1}, /*3*/ 
	  {SSD1963_Get_Lcd_Mode    ,{0x24,0x20,0x01,0xdf,0x01,0x0f,0x00}     ,7}, /*4*/ 
	  {SSD1963_Get_Hori_Period ,{0x02,0x0c,0x00,0x2a,0x07,0x00,0x00,0x00},8}, /*5*/ 
	  {SSD1963_Get_Vert_Period ,{0x01,0x1d,0x00,0x0b,0x09,0x00,0x00}     ,7}, /*6*/ 
	  {SSD1963_Get_Power_Mode  ,{0x1c}                                   ,1}, /*7*/ 
	  {SSD1963_Get_Display_Mode,{0x03}                                   ,1}, /*8*/ 
	  {SSD1963_Get_Gpio_Conf   ,{0x0F,0x01}                              ,2}, /*9*/ 
	  {SSD1963_Get_Lshift_Freq ,{0x00,0xb8}                              ,2}, /*10*/
	}; 

實(shí)現(xiàn)函數(shù)如下所示,函數(shù)會(huì)遍歷結(jié)構(gòu)體變量中的每一個(gè)命令,以及每一個(gè)命令下的初始值,如果有一個(gè)不正確,則跳出循環(huán),執(zhí)行重新初始化和恢復(fù)措施。這個(gè)函數(shù)中的MY_DEBUGF宏是我自己的調(diào)試函數(shù),使用串口打印調(diào)試信息,在接下來的第五部分將詳細(xì)敘述。通過這個(gè)函數(shù),我可以長時(shí)間監(jiān)控顯示屏的哪些命令、哪些位容易被干擾。程序里使用了一個(gè)被妖魔化的關(guān)鍵字:goto。大多數(shù)C語言書籍對goto關(guān)鍵字談之色變,但你應(yīng)該有自己的判斷。在函數(shù)內(nèi)部跳出多重循環(huán),除了goto關(guān)鍵字,又有哪種方法能如此簡潔高效!

	/** 
	* lcd 顯示冗余 
	* 每隔一段時(shí)間調(diào)用該程序一次 
	*/  
	void lcd_redu(void)  
	{  
	    uint8_t  tmp[8];  
	    uint32_t i,j;  
	    uint32_t lcd_init_flag;  
	    lcd_init_flag =0;  
	    for(i=0;i<sizeof(lcd_redu_list_str)/sizeof(lcd_redu_list_str[0]);i++)  
	    {  
	        LCD_SendCommand(lcd_redu_list_str[i].lcd_command);  
	        uyDelay(10);  
	        for(j=0;j<lcd_redu_list_str[i].lcd_value_num;j++)  
	        {  
	            tmp[j]=LCD_ReadData();  
	            if(tmp[j]!=lcd_redu_list_str[i].lcd_get_value[j])  
	            {  
	                lcd_init_flag=0x55;  
	                MY_DEBUGF(MENU_DEBUG,("讀lcd寄存器值與預(yù)期不符,命令為:0x%x,第%d個(gè)參數(shù),
	            該參數(shù)正確值為:0x%x,實(shí)際讀出值為:0x%x\n",lcd_redu_list_str[i].lcd_command,j+1,
	            lcd_redu_list_str[i].lcd_get_value[j],tmp[j]));  
	                goto handle_lcd_init;  
	            }  
	        }  
	    }  
	    handle_lcd_init:  
	    if(lcd_init_flag==0x55)  
	    {  
	        //重新初始化LCD  
	        //一些必要的恢復(fù)措施  
	    }     
	}  

4.14陷阱

對于8051內(nèi)核單片機(jī),由于沒有相應(yīng)的硬件支持,可以用純軟件設(shè)置軟件陷阱,用來攔截一些程序跑飛。對于ARM7或者Cortex-M系列單片機(jī),硬件已經(jīng)內(nèi)建了多種異常,軟件需要根據(jù)硬件異常來編寫陷阱程序,用來快速定位甚至恢復(fù)錯(cuò)誤。

4.15阻塞處理

有時(shí)候程序員會(huì)使用while(!flag);語句阻塞在此等待標(biāo)志flag改變,比如串口發(fā)送時(shí)用來等待一字節(jié)數(shù)據(jù)發(fā)送完成。這樣的代碼時(shí)存在風(fēng)險(xiǎn)的,如果因?yàn)槟承┰驑?biāo)志位一直不改變則會(huì)造成系統(tǒng)死機(jī)。

一個(gè)良好冗余的程序是設(shè)置一個(gè)超時(shí)定時(shí)器,超過一定時(shí)間后,強(qiáng)制程序退出while循環(huán)。

2003年8月11日發(fā)生的W32.Blaster.Worm蠕蟲事件導(dǎo)致全球經(jīng)濟(jì)損失高達(dá)5億美元,這個(gè)漏洞是利用了Windows分布式組件對象模型的遠(yuǎn)程過程調(diào)用接口中的一個(gè)邏輯缺陷:在調(diào)用GetMachineName()函數(shù)時(shí),循環(huán)只設(shè)置了一個(gè)不充分的結(jié)束條件。

原代碼簡化如下所示:

	HRESULT GetMachineName ( WCHAR *pwszPath,  
	WCHARwszMachineName[MAX_COMPUTTERNAME_LENGTH_FQDN+1])  
	{  
	       WCHAR *pwszServerName = wszMachineName;  
	       WCHAR *pwszTemp = pwszPath + 2;  
	       while ( *pwszTemp != L'\\' )           /* 這句代碼循環(huán)結(jié)束條件不充分 */  
	             *pwszServerName++= *pwszTemp++;  
	       /*… */  
	}  

微軟發(fā)布的安全補(bǔ)丁MS03-026解決了這個(gè)問題,為GetMachineName()函數(shù)設(shè)置了充分終止條件。一個(gè)解決代碼簡化如下所示(并非微軟補(bǔ)丁代碼):

	HRESULT GetMachineName( WCHAR *pwszPath,  
	WCHARwszMachineName[MAX_COMPUTTERNAME_LENGTH_FQDN+1])  
	{  
	       WCHAR *pwszServerName = wszMachineName;  
	       WCHAR *pwszTemp = pwszPath + 2;  
	       WCHAR *end_addr = pwszServerName +MAX_COMPUTTERNAME_LENGTH_FQDN;  
	       while ((*pwszTemp != L'\\' ) && (*pwszTemp != L'\0')  
	&& (pwszServerName<end_addr))  /*充分終止條件*/  
	             *pwszServerName++= *pwszTemp++;  
	       /*… */  
	} 

5.測試,再測試

思維再縝密的程序員也不可能編寫完全無缺陷的程序,測試的目的正是盡可能多的發(fā)現(xiàn)這些缺陷并改正。這里說的測試,是指程序員的自測試。前期的自測試能夠更早的發(fā)現(xiàn)錯(cuò)誤,相應(yīng)的修復(fù)成本也會(huì)很低,如果你不徹底測試自己的代碼,恐怕你開發(fā)的就不只是代碼,可能還會(huì)聲名狼藉。

優(yōu)質(zhì)嵌入式C程序跟優(yōu)質(zhì)的基礎(chǔ)元素關(guān)系密切,可以將函數(shù)作為基礎(chǔ)元素,我們的測試正是從最基本的函數(shù)開始。判斷哪些函數(shù)需要測試需要一定的經(jīng)驗(yàn)積累,雖然代碼行數(shù)跟邏輯復(fù)雜度并不成正比,但如果你不能判斷某個(gè)函數(shù)是否要測試,一個(gè)簡單粗暴的方法是:當(dāng)函數(shù)有效代碼超過20行,就測試它。

程序員對自己的代碼以及邏輯關(guān)系十分清楚,測試時(shí),按照每一個(gè)邏輯分支全面測試。很多錯(cuò)誤發(fā)生在我們認(rèn)為不會(huì)出錯(cuò)的地方,所以即便某個(gè)邏輯分支很簡單,也建議測試一遍。第一個(gè)原因是我們自己看自己的代碼總是不容易發(fā)現(xiàn)錯(cuò)誤,而測試能暴露這些錯(cuò)誤;另一方面,語法正確、邏輯正確的代碼,經(jīng)過編譯器編譯后,生成的匯編代碼很可能與你的邏輯相差甚遠(yuǎn)。比如我們前文提及的使用volatile以及不使用volatile關(guān)鍵字編譯后生成的匯編代碼,再比如我們用低優(yōu)化級別編譯和使用高優(yōu)化級別編譯后生成的匯編代碼,都可能相差很大,實(shí)際運(yùn)行測試,可以暴漏這些隱含錯(cuò)誤。最后,雖然可能性極小,編譯器本身也可能有BUG,特別是構(gòu)造復(fù)雜表達(dá)式的情況下(應(yīng)極力避免復(fù)雜表達(dá)式)。

5.1使用硬件調(diào)試器測試

使用硬件調(diào)試器(比如J-link)測試是最通用的手段??梢詥尾竭\(yùn)行、設(shè)置斷點(diǎn),可以很方便的查看當(dāng)前寄存器、變量的值。在尋找缺陷方面,使用硬件調(diào)試器測試是最簡單卻又最有效的手段。

硬件調(diào)試器已經(jīng)在公司普遍使用,這方面的測試不做介紹,想必大家都已經(jīng)很熟悉了。

5.2有些缺陷很難纏

就像沒有一種方法能完美解決所有問題,在實(shí)際項(xiàng)目中,硬件調(diào)試器也有難以觸及的地方??梢耘e幾個(gè)例子說明:

使用了比較大的協(xié)議棧,需要跟進(jìn)到協(xié)議棧內(nèi)部調(diào)試的缺陷

比如公司使用lwIP協(xié)議棧,如果跟蹤數(shù)據(jù)的處理過程,需要從接收數(shù)據(jù)開始一直到應(yīng)用層處理數(shù)據(jù),之間會(huì)經(jīng)過驅(qū)動(dòng)層、IP層、TCP層和應(yīng)用層,會(huì)經(jīng)過十幾個(gè)文件幾十個(gè)函數(shù),使用硬件調(diào)試器跟蹤費(fèi)時(shí)費(fèi)力;

具有隨機(jī)性的缺陷

有一些缺陷,可能是不定時(shí)出現(xiàn)的,有可能是幾分鐘出現(xiàn),也有可能是幾個(gè)小時(shí)甚至幾天才出現(xiàn),像這樣的缺陷很難用硬件調(diào)試器捕捉到;

需要外界一系列有時(shí)間限制的輸入條件觸發(fā),但這一過程中有缺陷

比如我們用組合鍵來完成某個(gè)功能,規(guī)定按下按鍵1不小于3秒后松開,然后在6秒內(nèi)分別按下按鍵2、按鍵3、按鍵4這三個(gè)按鍵來執(zhí)行我們的特定程序,要測試類似這種過程,硬件調(diào)試器很難做到;

除了測試缺陷需要,有時(shí)候我們在做穩(wěn)定性測試時(shí),需要知道軟件每時(shí)每刻運(yùn)行到那些分支、執(zhí)行了哪些操作、我們關(guān)心的變量當(dāng)前值是什么等等,這些都表明,我們還需要一種和硬件調(diào)試器互補(bǔ)的測試手段。

這個(gè)測試手段就是在程序中增加額外調(diào)試語句,當(dāng)程序運(yùn)行時(shí),通過這些調(diào)試語句將運(yùn)行信息輸出到可以方便查看的設(shè)備上,可以是PC機(jī)、LCD顯示屏、存儲(chǔ)卡等等。

以串口輸出到PC機(jī)為例,下面提供完整的測試思路。在此之前,我們先對這種測試手段提一些要求:

必須簡單易用

我們在初學(xué)C語言的時(shí)候,都接觸過printf函數(shù),這個(gè)函數(shù)可以方便的輸出信息,并可以將各種變量格式化為指定格式的字符串,我們應(yīng)當(dāng)提供類似的函數(shù);

調(diào)試語句必須方便的從代碼中移除

在編碼階段,我們可能會(huì)往程序中加入大量的調(diào)試語句,但是程序發(fā)布時(shí),需要將這些調(diào)試語句從代碼中移除,這將是件恐怖的過程。我們必須提供一種策略,可以方便的移除這些調(diào)試語句。

5.2.1簡單易用的調(diào)試函數(shù)

1)使用庫函數(shù)printf。以MDK為例,方法如下:

I>初始化串口

II>重構(gòu)fputc函數(shù),printf函數(shù)會(huì)調(diào)用fputc函數(shù)執(zhí)行底層串口的數(shù)據(jù)發(fā)送。

	/** 
	  * @brief  將C庫中的printf函數(shù)重定向到指定的串口. 
	  * @param  ch:要發(fā)送的字符 
	  * @param  f :文件指針 
	  */  
	int fputc(int ch, FILE *f)  
	{  
	    /*這里是一個(gè)跟硬件相關(guān)函數(shù),將一個(gè)字符寫到UART */  
	    //舉例:USART_SendData(UART_COM1, (uint8_t) ch);  
	    return ch;  
	} 

III>在Options for Targer窗口,Targer標(biāo)簽欄下,勾選Use MicroLIB前的復(fù)選框以便避免使用半主機(jī)功能。(注:標(biāo)準(zhǔn)C庫printf函數(shù)默認(rèn)開啟半主機(jī)功能,如果非要使用標(biāo)準(zhǔn)C庫,請自行查閱資料)

2)構(gòu)建自己的調(diào)試函數(shù)

使用庫函數(shù)比較方便,但也少了一些靈活性,不利于隨心所欲的定制輸出格式。自己編寫類似printf函數(shù)則會(huì)更靈活一些,而且不依賴任何編譯器。下面給出一個(gè)完整的類printf函數(shù)實(shí)現(xiàn),該函數(shù)支持有限的格式參數(shù),使用方法與庫函數(shù)一致。同庫函數(shù)類似,該也需要提供一個(gè)底層串口發(fā)送函數(shù)(原型為:int32_t UARTwrite(const uint8_t *pcBuf, uint32_t ulLen)),用來發(fā)送指定數(shù)目的字符,并返回最終發(fā)送的字符個(gè)數(shù)。

  #include <stdarg.h>               /*支持函數(shù)接收不定量參數(shù)*/  
  const char * const g_pcHex = "0123456789abcdef";  
  /** 
  * 簡介:   一個(gè)簡單的printf函數(shù),支持\%c, \%d, \%p, \%s, \%u,\%x, and \%X. 
  */  
  void UARTprintf(const uint8_t *pcString, ...)  
  {  
      uint32_t ulIdx;  
      uint32_t ulValue;       //保存從不定量參數(shù)堆棧中取出的數(shù)值型變量  
      uint32_t ulPos, ulCount;  
      uint32_t ulBase;        //保存進(jìn)制基數(shù),如十進(jìn)制則為10,十六進(jìn)制數(shù)則為16  
      uint32_t ulNeg;         //為1表示從變量為負(fù)數(shù)  
      uint8_t *pcStr;         //保存從不定量參數(shù)堆棧中取出的字符型變量  
      uint8_t pcBuf[32];      //保存數(shù)值型變量字符化后的字符  
      uint8_t cFill;          //'%08x'->不足8個(gè)字符用'0'填充,cFill='0';    
                              //'%8x '->不足8個(gè)字符用空格填充,cFill=' '  
      va_list vaArgP;  
      va_start(vaArgP, pcString);  
      while(*pcString)  
      {  
          // 首先搜尋非%核字符串結(jié)束字符  
          for(ulIdx = 0; (pcString[ulIdx] != '%') && (pcString[ulIdx] != '\0'); ulIdx++)  
          { }  
          UARTwrite(pcString, ulIdx);  
    
          pcString += ulIdx;  
          if(*pcString == '%')  
          {  
              pcString++;  
    
              ulCount = 0;  
              cFill = ' ';  
  again:  
              switch(*pcString++)  
              {  
                  case '0': case '1': case '2': case '3': case '4':  
                  case '5': case '6': case '7': case '8': case '9':  
                  {  
                      // 如果第一個(gè)數(shù)字為0, 則使用0做填充,則用空格填充)  
                      if((pcString[-1] == '0') && (ulCount == 0))  
                      {  
                          cFill = '0';  
                      }  
                      ulCount *= 10;  
                      ulCount += pcString[-1] - '0';  
                      goto again;  
                  }  
                  case 'c':          
                  {  
                      ulValue = va_arg(vaArgP, unsigned long);  
                      UARTwrite((unsigned char *)&ulValue, 1);  
                      break;  
                  }  
                  case 'd':     
                  {  
                      ulValue = va_arg(vaArgP, unsigned long);  
                      ulPos = 0;  
                        
                      if((long)ulValue < 0)  
                      {  
                          ulValue = -(long)ulValue;  
                          ulNeg = 1;  
                      }  
                      else  
                      {  
                          ulNeg = 0;  
                      }  
                      ulBase = 10;          
                      goto convert;  
                  }  
                  case 's':  
                  {  
                      pcStr = va_arg(vaArgP, unsigned char *);  
    
                      for(ulIdx = 0; pcStr[ulIdx] != '\0'; ulIdx++)  
                      {  
                      }  
                      UARTwrite(pcStr, ulIdx);  
    
                      if(ulCount > ulIdx)  
                      {  
                          ulCount -= ulIdx;  
                          while(ulCount--)  
                          {  
                              UARTwrite(" ", 1);  
                          }  
                      }  
                      break;  
                  }  
                  case 'u':  
                  {  
                      ulValue = va_arg(vaArgP, unsigned long);  
                      ulPos = 0;  
                      ulBase = 10;  
                      ulNeg = 0;  
                      goto convert;  
  	                }  
  	                case 'x': case 'X': case 'p':  
  	                {  
  	                    ulValue = va_arg(vaArgP, unsigned long);  
  	                    ulPos = 0;  
  	                    ulBase = 16;  
  	                    ulNeg = 0;  
  	         convert:   //將數(shù)值轉(zhuǎn)換成字符  
  	                    for(ulIdx = 1; (((ulIdx * ulBase) <= ulValue) &&(((ulIdx * ulBase) / ulBase) == ulIdx)); ulIdx *= ulBase, ulCount--)       
  	                    { }  
  	                    if(ulNeg)  
  	                    {  
  	                        ulCount--;                        
  	                    }  
  	                    if(ulNeg && (cFill == '0'))  
  	                    {  
  	                        pcBuf[ulPos++] = '-';  
  	                        ulNeg = 0;  
  	                    }  
  	                    if((ulCount > 1) && (ulCount < 16))  
  	                    {  
  	                        for(ulCount--; ulCount; ulCount--)  
  	                        {  
  	                            pcBuf[ulPos++] = cFill;  
  	                        }  
  	                    }  
  	  
  	                    if(ulNeg)  
  	                    {  
  	                        pcBuf[ulPos++] = '-';  
  	                    }  
  	  
  	                    for(; ulIdx; ulIdx /= ulBase)  
  	                    {  
  	                        pcBuf[ulPos++] = g_pcHex[(ulValue / ulIdx) % ulBase]; 
  	                    }  
  	                    UARTwrite(pcBuf, ulPos);  
  	                    break;  
  	                }  
  	                case '%':  
  	                {  
  	                    UARTwrite(pcString - 1, 1);                    
  	                    break;  
  	                }  
  	                default:  
  	                {                      
  	                    UARTwrite("ERROR", 5);                    
  	                    break;  
  	                }  
  	            }  
  	        }  
  	    }  
  	    //可變參數(shù)處理結(jié)束  
  	    va_end(vaArgP);  
  	} 

5.2.2對調(diào)試函數(shù)進(jìn)一步封裝

上文說到,我們增加的調(diào)試語句應(yīng)能很方便的從最終發(fā)行版中去掉,因此我們不能直接調(diào)用printf或者自定義的UARTprintf函數(shù),需要將這些調(diào)試函數(shù)做一層封裝,以便隨時(shí)從代碼中去除這些調(diào)試語句。參考方法如下:

	#ifdef MY_DEBUG  
	#define MY_DEBUGF(message) do { \  
	                                  {UARTprintf message;} \  
	                               } while(0)  
	#else    
	#define MY_DEBUGF(message)    
	#endif /* PLC_DEBUG */

在我們編碼測試期間,定義宏MY_DEBUG,并使用宏MY_DEBUGF(注意比前面那個(gè)宏多了一個(gè)‘F’)輸出調(diào)試信息。經(jīng)過預(yù)處理后,宏MY_DEBUGF(message)會(huì)被UARTprintf message代替,從而實(shí)現(xiàn)了調(diào)試信息的輸出;當(dāng)正式發(fā)布時(shí),只需要將宏MY_DEBUG注釋掉,經(jīng)過預(yù)處理后,所有MY_DEBUGF(message)語句都會(huì)被空格代替,而從將調(diào)試信息從代碼中去除掉。

6.編程思想

6.1編程風(fēng)格

《計(jì)算機(jī)程序的構(gòu)造和解釋》一書在開篇寫到:程序?qū)懗鰜硎墙o人看的,附帶能在機(jī)器上運(yùn)行。

6.1.1整潔的樣式

使用什么樣的編碼樣式一直都頗具爭議性的,比如縮進(jìn)和大括號的位置。因?yàn)榫幋a的樣式也會(huì)影響程序的可讀性,面對一個(gè)亂放括號、對齊都不一致的源碼,我們很難提起閱讀它的興趣。我們總要看別人的程序,如果彼此編碼樣式相近,讀起源碼來會(huì)覺得比較舒適。但是編碼風(fēng)格的問題是主觀的,永遠(yuǎn)不可能在編碼風(fēng)格上達(dá)成統(tǒng)一意見。因此只要你的編碼樣式整潔、結(jié)構(gòu)清晰就足夠了。除此之外,對編碼樣式再?zèng)]有其它要求。

提出匈牙利命名法的程序員、前微軟首席架構(gòu)師Charles Simonyi說:我覺得代碼清單帶給人的愉快同整潔的家差不多。你一眼就能分辨出家里是雜亂無章還是整潔如新。這也許意義不大。因?yàn)楣馐欠孔诱麧嵳f明不了什么,它仍可能藏污納垢!但是第一印象很重要,它至少反映了程序的某些方面。我敢打賭,我在3米開外就能看出程序拙劣與否。我也許沒法保證它很不錯(cuò),但如果從3米外看起來就很糟,我敢保證這程序?qū)懙貌挥眯摹H绻麑懙貌挥眯?,那它在邏輯上也許就不會(huì)優(yōu)美。

6.1.2清晰的命名

變量、函數(shù)、宏等等都需要命名,清晰的命名是優(yōu)秀代碼的特點(diǎn)之一。命名的要點(diǎn)之一是名稱應(yīng)能清晰的描述這個(gè)對象,以至于一個(gè)初級程序員也能不費(fèi)力的讀懂你的代碼邏輯。我們寫的代碼主要給誰看是需要思考的:給自己、給編譯器還是給別人看?我覺得代碼最主要的是給別人看,其次是給自己看。如果沒有一個(gè)清晰的命名,別人在維護(hù)你的程序時(shí)很難在整個(gè)全貌上看清代碼,因?yàn)橐涀∈鄠€(gè)以上的糟糕命名的變量是件非常困難的事;而且一段時(shí)間之后你回過頭來看自己的代碼,很有可能不記得那些糟糕命名的變量是什么意思。

為對象起一個(gè)清晰的名字并不是簡單的事情。首先能認(rèn)識到名稱的重要性需要有一個(gè)過程,這也許跟譚式C程序教材被大學(xué)廣泛使用有關(guān):滿書的a、b、c、x、y、z變量名是很難在關(guān)鍵的初學(xué)階段給人傳達(dá)優(yōu)秀編程思想的;其次如何恰當(dāng)?shù)臑閷ο竺埠苡刑魬?zhàn)性,要準(zhǔn)確、無歧義、不羅嗦,要對英文有一定水平,所有這些都要滿足時(shí),就會(huì)變得很困難;此外,命名還需要考慮整體一致性,在同一個(gè)項(xiàng)目中要有統(tǒng)一的風(fēng)格,堅(jiān)持這種風(fēng)格也并不容易。

關(guān)于如何命名,Charles Simonyi說:面對一個(gè)具備某些屬性的結(jié)構(gòu),不要隨隨便便地取個(gè)名字,然后讓所有人去琢磨名字和屬性之間有什么關(guān)聯(lián),你應(yīng)該把屬性本身,用作結(jié)構(gòu)的名字。

6.1.3恰當(dāng)?shù)淖⑨?/h4>

注釋向來也是爭議之一,不加注釋和過多的注釋我都是反對的。不加注釋的代碼顯然是很糟糕的,但過多的注釋也會(huì)妨礙程序的可讀性,由于注釋可能存在的歧義,有可能會(huì)誤解程序真實(shí)意圖,此外,過多的注釋會(huì)增加程序員不必要的時(shí)間。如果你的編碼樣式整潔、命名又很清晰,那么,你的代碼可讀性不會(huì)差到哪去,而注釋的本意就是為了便于理解程序。

這里建議使用良好的編碼樣式和清晰的命名來減少注釋,對模塊、函數(shù)、變量、數(shù)據(jù)結(jié)構(gòu)、算法和關(guān)鍵代碼做注釋,應(yīng)重視注釋的質(zhì)量而不是數(shù)量。如果你需要一大段注釋才能說清楚程序做什么,那么你應(yīng)該注意了:是否是因?yàn)槌绦蜃兞棵粔蚯逦蛘叽a邏輯過于混亂,這個(gè)時(shí)候你應(yīng)該考慮的可能就不是注釋,而是如何精簡這個(gè)程序了。

6.2數(shù)據(jù)結(jié)構(gòu)

數(shù)據(jù)結(jié)構(gòu)是程序設(shè)計(jì)的基礎(chǔ)。在設(shè)計(jì)程序之前,應(yīng)該先考慮好所需要的數(shù)據(jù)結(jié)構(gòu)。

前微軟首席架構(gòu)師Charles Simonyi:編程的第一步是想象。就是要在腦海中對來龍去脈有極為清晰的把握。在這個(gè)初始階段,我會(huì)使用紙和鉛筆。我只是信手涂鴉,并不寫代碼。我也許會(huì)畫些方框或箭頭,但基本上只是涂鴉,因?yàn)檎嬲南敕ㄔ谖夷X海里。我喜歡想象那些有待維護(hù)的結(jié)構(gòu),那些結(jié)構(gòu)代表著我想編碼的真實(shí)世界。一旦這個(gè)結(jié)構(gòu)考慮得相當(dāng)嚴(yán)謹(jǐn)和明確,我便開始寫代碼。我會(huì)坐到終端前,或者換在以前的話,就會(huì)拿張白紙,開始寫代碼。這相當(dāng)容易。我只要把頭腦中的想法變換成代碼寫下來,我知道結(jié)果應(yīng)該是什么樣的。大部分代碼會(huì)水到渠成,不過我維護(hù)的那些數(shù)據(jù)結(jié)構(gòu)才是關(guān)鍵。我會(huì)先想好數(shù)據(jù)結(jié)構(gòu),并在整個(gè)編碼過程中將它們牢記于心。

開發(fā)過以太網(wǎng)和操作系統(tǒng)SDS 940的Butler Lampson:(程序員)最重要的素質(zhì)是能夠把問題的解決方案組織成容易操控的結(jié)構(gòu)。

開發(fā)CP/M操作系統(tǒng)的Gary.A:如果不能確認(rèn)數(shù)據(jù)結(jié)構(gòu)是正確的,我是決不會(huì)開始編碼的。我會(huì)先畫數(shù)據(jù)結(jié)構(gòu),然后花很長時(shí)間思考數(shù)據(jù)結(jié)構(gòu)。在確定數(shù)據(jù)結(jié)構(gòu)之后我就開始寫一些小段的代碼,并不斷地改善和監(jiān)測。在編碼過程中進(jìn)行測試可以確保所做的修改是局部的,并且如果有什么問題的話,能夠馬上發(fā)現(xiàn)。

微軟創(chuàng)始人比爾·蓋茨:編寫程序最重要的部分是設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)。接下來重要的部分是分解各種代碼塊。

編寫世界上第一個(gè)電子表格軟件的Dan Bricklin:在我看來,寫程序最重要的部分是設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu),此外,你還必須知道人機(jī)界面會(huì)是什么樣的。

我們舉個(gè)例子來說明。在介紹防御性編程的時(shí)候,提到公司使用的LCD顯示屏抗干擾能力一般,為了提高LCD的穩(wěn)定性,需要定期讀出LCD內(nèi)部的關(guān)鍵寄存器值,然后跟存在Flash中的初始值相比較。需要讀出的LCD寄存器有十多個(gè),從每個(gè)寄存器讀出的值也不盡相同,從1個(gè)到8個(gè)字節(jié)都有可能。如果不考慮數(shù)據(jù)結(jié)構(gòu),編寫出的程序?qū)?huì)很冗長。

	void lcd_redu(void)  
	{  
	    讀第一個(gè)寄存器值;  
	    if(第一個(gè)寄存器值==Flash存儲(chǔ)值)  
	    {  
	        讀第二個(gè)寄存器值;  
	        if(第二個(gè)寄存器值==Flash存儲(chǔ)值)  
	        {  
	            ...  
	            讀第十個(gè)寄存器值;  
	            if(第十個(gè)寄存器值==Flash存儲(chǔ)值)  
	            {  
	                返回;  
	            }  
	            else  
	            {  
	                重新初始化LCD;  
	            }  
	        }  
	        else  
	        {  
	            重新初始化LCD;  
	        }  
	    }  
	    else  
	    {  
	        重新初始化LCD;  
	    }  
	}  

我們分析這個(gè)過程,發(fā)現(xiàn)能提取出很多相同的元素,比如每次讀LCD寄存器都需要該寄存器的命令號,都會(huì)經(jīng)過讀寄存器、判斷值是否相同、處理異常情況這一過程。所以我們可以提取一些相同的元素,組織成數(shù)據(jù)結(jié)構(gòu),用統(tǒng)一的方法去處理這些數(shù)據(jù),將數(shù)據(jù)與處理過程分開來。

我們可以先提取相同的元素,將之組織成數(shù)據(jù)結(jié)構(gòu):

	typedef struct {  
	    uint8_t  lcd_command;           //LCD寄存器  
	    uint8_t  lcd_get_value[8];      //初始化時(shí)寫入寄存器的值  
	    uint8_t  lcd_value_num;         //初始化時(shí)寫入寄存器值的數(shù)目  
	}lcd_redu_list_struct;  

這里lcd_command表示的是LCD寄存器命令號;lcd_get_value是一個(gè)數(shù)組,表示寄存器要初始化的值,這是因?yàn)閷τ谝粋€(gè)LCD寄存器,可能要初始化多個(gè)字節(jié),這是硬件特性決定的;lcd_value_num是指一個(gè)寄存器要多少個(gè)字節(jié)的初值,這是因?yàn)槊恳粋€(gè)寄存器的初值數(shù)目是不同的,我們用同一個(gè)方法處理數(shù)據(jù)時(shí),是需要這個(gè)信息的。

就本例而言,我們將要處理的數(shù)據(jù)都是事先固定的,所以定義好數(shù)據(jù)結(jié)構(gòu)后,我們可以將這些數(shù)據(jù)組織成表格:

  /*LCD部分寄存器設(shè)置值列表*/  
  lcd_redu_list_struct const lcd_redu_list_str[]=  
  {  
    {SSD1963_Get_Address_Mode,{0x20}                                   ,1}, /*1*/ 
    {SSD1963_Get_Pll_Mn      ,{0x3b,0x02,0x04}                         ,3}, /*2*/ 
    {SSD1963_Get_Pll_Status  ,{0x04}                                   ,1}, /*3*  
    {SSD1963_Get_Lcd_Mode    ,{0x24,0x20,0x01,0xdf,0x01,0x0f,0x00}     ,7}, /*4*/ 
    {SSD1963_Get_Hori_Period ,{0x02,0x0c,0x00,0x2a,0x07,0x00,0x00,0x00},8}, /*5*/ 
    {SSD1963_Get_Vert_Period ,{0x01,0x1d,0x00,0x0b,0x09,0x00,0x00}     ,7}, /*6*/ 
    {SSD1963_Get_Power_Mode  ,{0x1c}                                   ,1}, /*7*/ 
    {SSD1963_Get_Display_Mode,{0x03}                                   ,1}, /*8*/ 
    {SSD1963_Get_Gpio_Conf   ,{0x0F,0x01}                              ,2}, /*9*/ 
    {SSD1963_Get_Lshift_Freq ,{0x00,0xb8}                              ,2}, /*10* 
  };  

至此,我們就可以用一個(gè)處理過程來完成數(shù)十個(gè)LCD寄存器的讀取、判斷和異常處理了:

	/** 
	* lcd 顯示冗余 
	* 每隔一段時(shí)間調(diào)用該程序一次 
	*/  
	void lcd_redu(void)  
	{  
	    uint8_t  tmp[8];  
	    uint32_t i,j;  
	    uint32_t lcd_init_flag;  
	    lcd_init_flag =0;  
	    for(i=0;i<sizeof(lcd_redu_list_str)/sizeof(lcd_redu_list_str[0]);i++)  
	    {  
	        LCD_SendCommand(lcd_redu_list_str[i].lcd_command);  
	        uyDelay(10);  
	        for(j=0;j<lcd_redu_list_str[i].lcd_value_num;j++)  
	        {  
	            tmp[j]=LCD_ReadData();  
	            if(tmp[j]!=lcd_redu_list_str[i].lcd_get_value[j])  
	            {  
	                lcd_init_flag=0x55;  
	                //一些調(diào)試語句,打印出錯(cuò)的具體信息
	                goto handle_lcd_init;  
	            }  
	        }  
	    }  
	    handle_lcd_init:  
	    if(lcd_init_flag==0x55)  
	    {  
	        //重新初始化LCD  
	        //一些必要的恢復(fù)措施  
	    }     
	}  

通過合理的數(shù)據(jù)結(jié)構(gòu),我們可以將數(shù)據(jù)和處理過程分開,LCD冗余判斷過程可以用很簡潔的代碼來實(shí)現(xiàn)。更重要的是,將數(shù)據(jù)和處理過程分開更有利于代碼的維護(hù)。比如,通過實(shí)驗(yàn)發(fā)現(xiàn),我們還需要增加一個(gè)LCD寄存器的值進(jìn)行判斷,這時(shí)候只需要將新增加的寄存器信息按照數(shù)據(jù)結(jié)構(gòu)格式,放到LCD寄存器設(shè)置值列表中的任意位置即可,不用增加任何處理代碼即可實(shí)現(xiàn)!這僅僅是數(shù)據(jù)結(jié)構(gòu)的優(yōu)勢之一,使用數(shù)據(jù)結(jié)構(gòu)還能簡化編程,使復(fù)雜過程變的簡單,這個(gè)只有實(shí)際編程后才會(huì)有更深的理解。

7.總結(jié)和閱讀書目

本文介紹了編寫優(yōu)質(zhì)嵌入式C程序涉及的多個(gè)方面。每年都有億萬計(jì)的C程序運(yùn)行在單片機(jī)、ARM7、Cortex-M3這些微處理器上,但在這些處理器上如何編寫優(yōu)質(zhì)高效的C程序,幾乎沒有書籍做專門介紹。本文試圖在這方面做一些努力。編寫優(yōu)質(zhì)嵌入式C程序需要大量的專業(yè)知識,本文雖盡力描述編寫嵌入式C程序所需要的各種技能,但本文卻無力將每一個(gè)方面都面面俱到的描述出來,所以本文最后會(huì)列舉一些閱讀書目,這些書大多都是真正大師的經(jīng)驗(yàn)之談。站在巨人的肩膀上,可以看的更遠(yuǎn)。

7.1關(guān)于語言特性

  • Stephen Prata 著 云巔工作室 譯 《C Primer Plus(第五版)中文版》
  • Andrew Koenig 著 高巍 譯 《C陷阱與缺陷》
  • Peter Van Der Linden 著 徐波 譯 《C專家編程》
  • 陳正沖 編著 《C語言深度解剖》

7.2關(guān)于編譯器

  • 杜春雷 編著 《ARM體系結(jié)構(gòu)與編程》
  • Keil MDK 編譯器幫助手冊

7.3關(guān)于防御性編程

  • MISRA-C-:2004 Guidelines for the use of the C language in criticalsystems
  • Robert C.Seacord 著 徐波 譯 《C安全編碼標(biāo)準(zhǔn)》

7.4關(guān)于編程思想

  • Pete Goodliffe 著 韓江、陳玉 譯 《編程匠藝---編寫卓越的代碼》
  • Susan Lammers 著 李琳驍、吳詠煒、張菁《編程大師訪談錄

以上就是嵌入式C程序優(yōu)質(zhì)編寫教程的詳細(xì)內(nèi)容,更多關(guān)于嵌入式C程序編寫的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 一篇文章帶你了解C語言的選擇結(jié)構(gòu)

    一篇文章帶你了解C語言的選擇結(jié)構(gòu)

    這篇文章主要為大家介紹了C語言的選擇結(jié)構(gòu),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-01-01
  • C++中replace()函數(shù)使用方法匯總

    C++中replace()函數(shù)使用方法匯總

    這篇文章主要介紹了C++中replace()函數(shù)使用方法匯總,在這篇文章中為大家詳細(xì)介紹C++ replace()函數(shù)的各種應(yīng)用方式,希望朋友們可以從這里介紹的內(nèi)容充分掌握這一應(yīng)用技巧
    2015-11-11
  • C語言實(shí)現(xiàn)井字棋游戲

    C語言實(shí)現(xiàn)井字棋游戲

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)井字棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-04-04
  • C語言設(shè)計(jì)模式之命令模式介紹

    C語言設(shè)計(jì)模式之命令模式介紹

    大家好,本篇文章主要講的是C語言設(shè)計(jì)模式之命令模式介紹,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2022-01-01
  • notepad介紹及插件cmake編譯過程(替代notepad++)

    notepad介紹及插件cmake編譯過程(替代notepad++)

    這篇文章主要介紹了notepad介紹及插件cmake編譯過程(替代notepad++),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • C語言實(shí)現(xiàn)實(shí)驗(yàn)設(shè)備管理系統(tǒng)

    C語言實(shí)現(xiàn)實(shí)驗(yàn)設(shè)備管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)實(shí)驗(yàn)設(shè)備管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • C語言結(jié)構(gòu)體鏈表和指針實(shí)現(xiàn)學(xué)生管理系統(tǒng)

    C語言結(jié)構(gòu)體鏈表和指針實(shí)現(xiàn)學(xué)生管理系統(tǒng)

    這篇文章主要介紹了C語言結(jié)構(gòu)體鏈表和指針實(shí)現(xiàn)學(xué)生管理系統(tǒng),包括學(xué)生檔案管理子系統(tǒng)和學(xué)生成績管理子系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • C語言中fopen()函數(shù)的使用方法示例詳解

    C語言中fopen()函數(shù)的使用方法示例詳解

    這篇文章主要介紹了C語言中fopen()函數(shù)的使用方法,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-06-06
  • C語言拓展實(shí)現(xiàn)Lua sleep函數(shù)

    C語言拓展實(shí)現(xiàn)Lua sleep函數(shù)

    這篇文章主要介紹了C語言拓展實(shí)現(xiàn)Lua sleep函數(shù),本文使用C語言寫出sleep函數(shù),編譯后在Lua中調(diào)用,需要的朋友可以參考下
    2015-04-04
  • C語言實(shí)現(xiàn)控制臺版貪吃蛇游戲

    C語言實(shí)現(xiàn)控制臺版貪吃蛇游戲

    這篇文章主要為大家詳細(xì)介紹了c語言貪吃蛇控制臺版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07

最新評論

成人24小时免费视频| 日韩av中文在线免费观看| 国产精品黄大片在线播放| 人妻少妇精品久久久久久 | 青娱乐在线免费视频盛宴| 欧美精品 日韩国产| 精品国产成人亚洲午夜| www天堂在线久久| 成人av亚洲一区二区| 绝顶痉挛大潮喷高潮无码 | 欧美亚洲一二三区蜜臀| 天天日天天舔天天射进去| av日韩在线观看大全| 青青青aaaa免费| 粉嫩小穴流水视频在线观看| 99人妻视频免费在线| 亚洲午夜在线视频福利| 二区中出在线观看老师| xxx日本hd高清| 亚洲一区二区人妻av| 欧美精品久久久久久影院| 青青草在观免费国产精品| 色综合天天综合网国产成人| 80电影天堂网官网| 青青青青青手机视频| 97人妻夜夜爽二区欧美极品| av网址国产在线观看| 人人妻人人澡人人爽人人dvl| 黄网十四区丁香社区激情五月天 | 青青青青在线视频免费观看| 丝袜美腿视频诱惑亚洲无| 91九色porny国产蝌蚪视频| 天天干天天爱天天色| 日本特级片中文字幕| 中文字幕亚洲中文字幕| 最近的中文字幕在线mv视频| 北条麻妃高跟丝袜啪啪| 天天干天天插天天谢| 国产免费高清视频视频| 视频在线免费观看你懂得| 男人天堂最新地址av| 99re久久这里都是精品视频| 激情人妻校园春色亚洲欧美| 日本阿v视频在线免费观看| 亚洲精品乱码久久久本| 亚洲人成精品久久久久久久| 免费大片在线观看视频网站| av中文字幕电影在线看| 视频啪啪啪免费观看| av在线播放国产不卡| 91麻豆精品秘密入口在线观看| 亚洲综合在线视频可播放| 黑人巨大精品欧美视频| 自拍 日韩 欧美激情| 岳太深了紧紧的中文字幕| 老司机福利精品视频在线| 亚洲高清视频在线不卡| lutube在线成人免费看| 黑人3p华裔熟女普通话| 水蜜桃一区二区三区在线观看视频 | 爆乳骚货内射骚货内射在线| 在线观看成人国产电影| 亚洲欧美激情中文字幕| 欧美怡红院视频在线观看| 38av一区二区三区| 欧美精品免费aaaaaa| 把腿张开让我插进去视频| 亚洲综合色在线免费观看| 国产成人午夜精品福利| 一色桃子久久精品亚洲| 国产超码片内射在线| 亚洲中文字幕乱码区| 午夜精品一区二区三区城中村| 三级黄色亚洲成人av| 青青青青草手机在线视频免费看| 人妻少妇av在线观看| 夜鲁夜鲁狠鲁天天在线| 熟女国产一区亚洲中文字幕| 国产三级片久久久久久久| 亚洲一区二区三区偷拍女厕91| 偷拍自拍亚洲美腿丝袜| av大全在线播放免费| 美洲精品一二三产区区别| 婷婷久久久综合中文字幕| 国产一区自拍黄视频免费观看 | 日本丰满熟妇BBXBBXHD| 国产日韩一区二区在线看| 在线播放国产黄色av| 亚洲区欧美区另类最新章节| 韩国黄色一级二级三级| 不卡一区一区三区在线| 亚洲成人熟妇一区二区三区| 一区二区三区视频,福利一区二区| 成熟熟女国产精品一区| 亚洲图片欧美校园春色| 影音先锋女人av噜噜色| 天天操天天污天天射| 亚洲欧美综合在线探花| 人妻少妇中文有码精品| 人妻少妇性色欲欧美日韩| 国产污污污污网站在线| 国产第一美女一区二区三区四区 | 亚洲熟女女同志女同| 国产日本欧美亚洲精品视| 欧美地区一二三专区| 成熟丰满熟妇高潮xx×xx| 精品一区二区三区午夜| 中文字幕高清在线免费播放 | 国际av大片在线免费观看| 国产在线观看黄色视频| 久久这里有免费精品| 青春草视频在线免费播放| 久久久久久九九99精品| 91精品国产黑色丝袜| 天堂va蜜桃一区入口| 精品亚洲中文字幕av| 999九九久久久精品| 91九色国产熟女一区二区| 中文字幕午夜免费福利视频| 国产揄拍高清国内精品对白| 一区二区三区美女毛片| 北条麻妃高跟丝袜啪啪| 91免费观看国产免费| 馒头大胆亚洲一区二区| 日视频免费在线观看| 成年女人免费播放视频| 久久麻豆亚洲精品av| 蜜桃视频在线欧美一区| 丰满的继坶3中文在线观看| 大肉大捧一进一出好爽在线视频| 一级黄片久久久久久久久| 亚洲专区激情在线观看视频| 日本精品一区二区三区在线视频。| 精品一区二区三区欧美| av中文字幕电影在线看| 在线新三级黄伊人网| 婷婷综合蜜桃av在线| 国产精品久久久久久久精品视频| 亚洲最大黄了色网站| 美女张开腿让男生操在线看| 超碰在线观看免费在线观看| 2020国产在线不卡视频| 夜夜嗨av蜜臀av| 中文字幕免费在线免费| 国产精品久久久久网| 久久这里只有精彩视频免费| 色婷婷久久久久swag精品| 青草久久视频在线观看| 欧美一区二区中文字幕电影| 青青青激情在线观看视频| 亚洲av日韩av网站| av在线播放国产不卡| 91中文字幕免费在线观看| 女同久久精品秋霞网| 天天干天天操天天扣| 青青青激情在线观看视频| 国产精品视频欧美一区二区| 初美沙希中文字幕在线| 成人av免费不卡在线观看| 久久精品亚洲国产av香蕉| 色综合天天综合网国产成人| aⅴ五十路av熟女中出| 欧美区一区二区三视频| aiss午夜免费视频| 欧美男同性恋69视频| 97人妻夜夜爽二区欧美极品| 毛片一级完整版免费| aiss午夜免费视频| 岳太深了紧紧的中文字幕| 中文字幕av一区在线观看| free性日本少妇| 久久永久免费精品人妻专区| 最近中文2019年在线看| 久久热这里这里只有精品| 91久久综合男人天堂| 日本性感美女写真视频| 清纯美女在线观看国产| 中文字幕第1页av一天堂网 | 天天日天天天天天天天天天天 | 亚洲综合自拍视频一区| 日本免费视频午夜福利视频| 午夜在线观看一区视频| 91精品国产综合久久久蜜| 中文字幕视频一区二区在线观看| 丝袜肉丝一区二区三区四区在线看| 日本中文字幕一二区视频| 超pen在线观看视频公开97| 日本一区精品视频在线观看| 欧美伊人久久大香线蕉综合| 91色九色porny| 日本av熟女在线视频| 日本韩国免费福利精品| 激情啪啪啪啪一区二区三区 | 日本人妻少妇18—xx| 免费无码人妻日韩精品一区二区| 97人妻人人澡爽人人精品| 啊啊好大好爽啊啊操我啊啊视频| 亚洲男人让女人爽的视频| 国产 在线 免费 精品| 亚洲午夜伦理视频在线| 一色桃子久久精品亚洲| wwwxxx一级黄色片| 国产精品大陆在线2019不卡| 偷拍自拍亚洲美腿丝袜| 欧美精品欧美极品欧美视频 | 午夜91一区二区三区| 水蜜桃国产一区二区三区| 色综合久久无码中文字幕波多| jiujiure精品视频在线| 日本三极片中文字幕| gav成人免费播放| 日本阿v视频在线免费观看| 91精品激情五月婷婷在线| 啪啪啪操人视频在线播放| free性日本少妇| 亚洲一区二区三区五区| rct470中文字幕在线| 精品一区二区三区欧美| 老司机99精品视频在线观看| 国产日韩欧美美利坚蜜臀懂色| 狠狠的往里顶撞h百合| 亚洲精品三级av在线免费观看| 国产白嫩美女一区二区| 最新中文字幕乱码在线| 99精品视频之69精品视频| 北条麻妃肉色丝袜视频| 大鸡八强奸视频在线观看| 无码中文字幕波多野不卡| 黄色无码鸡吧操逼视频| 亚洲人成精品久久久久久久| 绯色av蜜臀vs少妇| 51国产成人精品视频| 人人妻人人爽人人添夜| 在线播放一区二区三区Av无码| 国产熟妇人妻ⅹxxxx麻豆| 亚洲精品一区二区三区老狼| 最新中文字幕免费视频| 中文字幕在线视频一区二区三区| 狍和女人的王色毛片| 91精品国产综合久久久蜜| 啊啊啊想要被插进去视频| 天天日天天操天天摸天天舔| 国内自拍第一页在线观看| 天天做天天干天天舔| 懂色av之国产精品| 色秀欧美视频第一页| 色秀欧美视频第一页| 欧美一区二区三区四区性视频| 亚洲最大免费在线观看| 成人18禁网站在线播放| 天天日天天干天天干天天日| 成人av天堂丝袜在线观看| 日本中文字幕一二区视频| 亚洲女人的天堂av| 国产精品人妻66p| 激情国产小视频在线| 99精品视频在线观看婷婷| 婷婷综合亚洲爱久久| 大香蕉玖玖一区2区| 国产在线免费观看成人| 国产精品黄色的av| 玩弄人妻熟妇性色av少妇| 夜色撩人久久7777| 午夜蜜桃一区二区三区| 97资源人妻免费在线视频| 人妻丝袜精品中文字幕| 天天日天天敢天天干| 免费费一级特黄真人片| 欧美韩国日本国产亚洲| 午夜久久久久久久99| 免费无码人妻日韩精品一区二区 | 偷拍3456eee| 六月婷婷激情一区二区三区| 强行扒开双腿猛烈进入免费版| 最新黄色av网站在线观看| 色偷偷伊人大杳蕉综合网| 国内自拍第一页在线观看| 欧美成人黄片一区二区三区| av大全在线播放免费| 亚洲国产在线精品国偷产拍| 青草亚洲视频在线观看| 久久久久久9999久久久久| 熟女人妻一区二区精品视频| 日本www中文字幕| 亚洲欧美激情人妻偷拍| 欧美成一区二区三区四区| 97青青青手机在线视频| 特大黑人巨大xxxx| 久久精品亚洲国产av香蕉| 免费男阳茎伸入女阳道视频| 日韩a级黄色小视频| 国产大学生援交正在播放| 97青青青手机在线视频| 国产清纯美女al在线| 久久久久久久久久一区二区三区 | 国产欧美日韩第三页| 韩国三级aaaaa高清视频| 色综合久久久久久久久中文| 色97视频在线播放| 亚洲国产在线精品国偷产拍| 国产日本精品久久久久久久| 亚洲 欧美 精品 激情 偷拍| 人人在线视频一区二区| 国产免费高清视频视频| av无限看熟女人妻另类av| 亚洲av日韩av第一区二区三区| 日本高清在线不卡一区二区| 国产第一美女一区二区三区四区| 中文字幕无码一区二区免费| 久久尻中国美女视频| 青青草人人妻人人妻| 日韩中文字幕在线播放第二页| 国产 在线 免费 精品| 日韩二区视频一线天婷婷五| 欧美乱妇无乱码一区二区| 婷婷六月天中文字幕| 午夜极品美女福利视频| 亚洲一区二区三区在线高清| 色花堂在线av中文字幕九九| 久久午夜夜伦痒痒想咳嗽P| 亚洲区欧美区另类最新章节| 亚洲的电影一区二区三区| 人妻少妇中文有码精品| 91国内精品久久久久精品一| 日本女人一级免费片| 久久一区二区三区人妻欧美| 精彩视频99免费在线| 青青热久免费精品视频在线观看| 99热碰碰热精品a中文| 亚洲一级av大片免费观看| 欧美日韩中文字幕欧美| 国产又色又刺激在线视频| 亚洲男人在线天堂网| 欧美特色aaa大片| 国产亚洲欧美视频网站| 国产黄色片在线收看| 一区二区视频在线观看视频在线| 青青青国产免费视频| aaa久久久久久久久| 少妇人妻久久久久视频黄片| 免费观看丰满少妇做受| 欧美激情电影免费在线| 精品美女久久久久久| 粉嫩小穴流水视频在线观看| 色婷婷六月亚洲综合香蕉| 国产黄色大片在线免费播放| 日韩不卡中文在线视频网站| 丝袜亚洲另类欧美变态| v888av在线观看视频| 亚洲一区二区三区久久受| eeuss鲁片一区二区三区| 人妻凌辱欧美丰满熟妇| 青青青青爽手机在线| 久久这里只有精彩视频免费| www日韩毛片av| 午夜影院在线观看视频羞羞羞| av在线播放国产不卡| 91av精品视频在线| 日韩中文字幕精品淫| 日韩不卡中文在线视频网站| 国产精品自拍在线视频| 亚洲综合一区二区精品久久| 亚洲免费福利一区二区三区| 天天日天天爽天天干| 天天日天天干天天插舔舔| 又黄又刺激的午夜小视频| 日本性感美女视频网站| 亚洲中文字幕人妻一区| 久草视频在线免播放| 日本韩国亚洲综合日韩欧美国产| 最新国产精品网址在线观看| 亚洲麻豆一区二区三区| 在线免费观看日本伦理| 18禁网站一区二区三区四区| av欧美网站在线观看| 国产久久久精品毛片| 最新国产精品网址在线观看| 青青尤物在线观看视频网站| 天天日天天敢天天干| 老司机福利精品免费视频一区二区| 青青草精品在线视频观看| 100%美女蜜桃视频| 精产国品久久一二三产区区别| 在线制服丝袜中文字幕| 在线成人日韩av电影| 韩国亚洲欧美超一级在线播放视频| 黄色片黄色片wyaa| 大鸡吧插逼逼视频免费看| 熟女俱乐部一二三区| 亚洲成人黄色一区二区三区 | 国产aⅴ一线在线观看| 东游记中文字幕版哪里可以看到 | 国产精品自拍在线视频| 中文字幕第三十八页久久 | 欧美视频中文一区二区三区| 福利视频网久久91| 国产精品久久久黄网站| 男人插女人视频网站| 黄色成年网站午夜在线观看| 99热99re在线播放| 一区二区三区综合视频| 亚洲丝袜老师诱惑在线观看| 天天操天天干天天艹| 2022国产综合在线干| 亚洲国产成人av在线一区| 国产日本精品久久久久久久| 日本av熟女在线视频| 久草视频在线看免费| 福利片区一区二体验区| 日本熟妇色熟妇在线观看| 伊拉克及约旦宣布关闭领空| 亚洲成人免费看电影| 欧美 亚洲 另类综合| 亚洲欧美在线视频第一页| 亚国产成人精品久久久| 大肉大捧一进一出好爽在线视频 | 色哟哟在线网站入口| 91精品国产黑色丝袜| 天干天天天色天天日天天射| 99久久成人日韩欧美精品| 天天射夜夜操狠狠干| 国产精品熟女久久久久浪潮| 二区中出在线观看老师| 日本一区精品视频在线观看| 人妻自拍视频中国大陆| 美女视频福利免费看| 日韩欧美一级aa大片| 好男人视频在线免费观看网站| 亚洲欧美激情中文字幕| 日日夜夜狠狠干视频| 啪啪啪啪啪啪啪免费视频| 日本av高清免费网站| 亚洲在线免费h观看网站| 亚洲图库另类图片区| 亚欧在线视频你懂的| 日本一道二三区视频久久| 黄色av网站免费在线| 动漫美女的小穴视频| 婷婷久久一区二区字幕网址你懂得| 欧美男人大鸡吧插女人视频| 天堂资源网av中文字幕| 97人人模人人爽人人喊| 在线免费91激情四射| 亚洲老熟妇日本老妇| 免费高清自慰一区二区三区网站| 天天日天天天天天天天天天天| 欧美精产国品一二三区| 99久久超碰人妻国产| 国产福利小视频大全| 亚洲欧美人精品高清| 粉嫩av蜜乳av蜜臀| 午夜场射精嗯嗯啊啊视频| 女生自摸在线观看一区二区三区| 日日操综合成人av| 亚洲精品精品国产综合| 国产日韩一区二区在线看 | 国产在线观看黄色视频| 亚洲免费成人a v| 精品乱子伦一区二区三区免费播| 综合精品久久久久97| 亚洲免费福利一区二区三区| 2020久久躁狠狠躁夜夜躁| 日本中文字幕一二区视频| 又粗又硬又猛又黄免费30| 日本xx片在线观看| 欧美日韩人妻久久精品高清国产 | 亚洲欧美国产麻豆综合| 真实国产乱子伦一区二区| xxx日本hd高清| 国产性色生活片毛片春晓精品| 亚洲精品国产久久久久久| 国产1区,2区,3区| 亚洲成人情色电影在线观看| 亚洲成人线上免费视频观看| ka0ri在线视频| 蜜桃专区一区二区在线观看| 日本福利午夜电影在线观看| 亚洲精品精品国产综合| 亚洲免费视频欧洲免费视频| 91在线免费观看成人| 成人sm视频在线观看| 亚洲综合乱码一区二区| 中字幕人妻熟女人妻a62v网| 午夜在线一区二区免费| 亚洲av自拍偷拍综合| 精品国产午夜视频一区二区| av日韩在线免费播放| 区一区二区三国产中文字幕| 青青青青青免费视频| www天堂在线久久| 一区二区三区久久中文字幕| 人妻自拍视频中国大陆| 福利在线视频网址导航| okirakuhuhu在线观看| 熟女91pooyn熟女| 中文字幕午夜免费福利视频| 中国老熟女偷拍第一页| 欧美精品一二三视频| 韩国女主播精品视频网站| 黑人3p华裔熟女普通话| 97少妇精品在线观看| 水蜜桃国产一区二区三区| 久久久久久久精品老熟妇| 美日韩在线视频免费看| 在线新三级黄伊人网| 超级福利视频在线观看| 无码中文字幕波多野不卡| 国产麻豆乱子伦午夜视频观看| 美女大bxxxx内射| 成人高清在线观看视频| 香港一级特黄大片在线播放 | 亚洲午夜高清在线观看| sejizz在线视频| 2020韩国午夜女主播在线| www日韩毛片av| 亚洲嫩模一区二区三区| 天天干天天爱天天色| 国产刺激激情美女网站| 天天干天天操天天爽天天摸| 姐姐的朋友2在线观看中文字幕| 成人伊人精品色xxxx视频| 青青热久免费精品视频在线观看| 欧美一区二区三区久久久aaa| 天天射夜夜操综合网| 综合激情网激情五月天| 青青在线视频性感少妇和隔壁黑丝| 午夜影院在线观看视频羞羞羞| 中文字幕一区二区三区人妻大片| 孕妇奶水仑乱A级毛片免费看| 日韩精品中文字幕福利| 日韩中文字幕精品淫| 亚洲天堂第一页中文字幕| 久久久人妻一区二区| 亚洲的电影一区二区三区| 成年美女黄网站18禁久久| 欧美久久久久久三级网| www,久久久,com| 2022天天干天天操| 成年人的在线免费视频| 日韩美女搞黄视频免费| 欧美一区二区中文字幕电影| 亚洲欧美色一区二区| 成人av免费不卡在线观看| 97精品成人一区二区三区| www,久久久,com| 天天干天天插天天谢| 国产高清女主播在线| 亚洲一区二区三区久久受| 午夜福利资源综合激情午夜福利资 | 久草视频中文字幕在线观看| 亚洲av日韩高清hd| 国产麻豆剧传媒精品国产av蜜桃| 久久香蕉国产免费天天| 黑人乱偷人妻中文字幕| 97国产福利小视频合集| 亚洲av第国产精品| 最近的中文字幕在线mv视频| 最新欧美一二三视频 | 国产又粗又硬又大视频| 最后99天全集在线观看| 成人国产影院在线观看| 亚洲激情,偷拍视频| 久久精品国产999| 少妇高潮一区二区三区| 鸡巴操逼一级黄色气| 男人的网址你懂的亚洲欧洲av| 九色视频在线观看免费| 在线免费观看99视频| 性感美女福利视频网站| 午夜dv内射一区区| 欧美日本在线视频一区| 亚洲天堂成人在线观看视频网站| 久久www免费人成一看片| 亚洲精品高清自拍av| 天天综合天天综合天天网| 亚洲区欧美区另类最新章节| 18禁美女黄网站色大片下载| 男人天堂av天天操| 国产熟妇人妻ⅹxxxx麻豆| mm131美女午夜爽爽爽| 青青草亚洲国产精品视频| 少妇一区二区三区久久久| 天天干天天操天天扣| 91成人精品亚洲国产| 国产成人精品av网站| 欧美黑人巨大性xxxxx猛交| 精品日产卡一卡二卡国色天香| 中文字幕人妻熟女在线电影| 黄色片年轻人在线观看| 熟女视频一区,二区,三区| 丝袜美腿视频诱惑亚洲无| 国产在线免费观看成人| 日韩欧美国产一区ab| 日韩美av高清在线| 家庭女教师中文字幕在线播放| 自拍 日韩 欧美激情| 国产高清97在线观看视频| 国产综合精品久久久久蜜臀| 天天干天天操天天扣| 啊啊啊想要被插进去视频| 精品美女福利在线观看| 精品一区二区三区午夜| 男人靠女人的逼视频| 日本少妇人妻xxxxx18| 中文字幕人妻三级在线观看| 男人天堂色男人av| av在线资源中文字幕| 噜噜色噜噜噜久色超碰| 日韩精品中文字幕福利| 色呦呦视频在线观看视频| 国产精品久久久久久美女校花| 黄色片年轻人在线观看| 国产精品精品精品999| 国产女孩喷水在线观看| 爆乳骚货内射骚货内射在线 | 1000小视频在线| 激情五月婷婷综合色啪| 天天做天天爽夜夜做少妇| 天天艹天天干天天操| 视频一区 视频二区 视频| 搡老妇人老女人老熟女| 3D动漫精品啪啪一区二区下载| 女同性ⅹxx女同h偷拍| 天天操夜夜骑日日摸| 18禁美女黄网站色大片下载| 亚洲精品 日韩电影| 天天日天天干天天要| 中文 成人 在线 视频| 中文字母永久播放1区2区3区| 在线 中文字幕 一区| 国产高清在线在线视频| 亚洲天堂有码中文字幕视频 | 男大肉棒猛烈插女免费视频| 日本一二三区不卡无| 精品久久婷婷免费视频| 日本人妻少妇18—xx| 国产普通话插插视频| 三级av中文字幕在线观看| 97小视频人妻一区二区| 国产实拍勾搭女技师av在线| 区一区二区三国产中文字幕| jiujiure精品视频在线| 91中文字幕最新合集| 白嫩白嫩美女极品国产在线观看| 国产美女一区在线观看| 91精品激情五月婷婷在线| 中文字幕人妻被公上司喝醉在线| 老司机99精品视频在线观看| 玩弄人妻熟妇性色av少妇| 经典国语激情内射视频| 欧洲欧美日韩国产在线| 丝袜长腿第一页在线| 日韩av中文在线免费观看| 91精品国产黑色丝袜| 国产精品久久综合久久| 亚洲av琪琪男人的天堂| 4个黑人操素人视频网站精品91| 国产一区二区欧美三区| 青娱乐在线免费视频盛宴| 亚洲国产成人无码麻豆艾秋| 日本在线一区二区不卡视频| 一区二区三区日韩久久| 欧美精品资源在线观看| 亚洲精品在线资源站| 成人蜜臀午夜久久一区| 4个黑人操素人视频网站精品91| 亚洲精品成人网久久久久久小说| 欧美精品免费aaaaaa| 国产性色生活片毛片春晓精品| 欧美乱妇无乱码一区二区| 久久久久久9999久久久久| 亚洲精品国品乱码久久久久| 熟妇一区二区三区高清版| 亚洲午夜伦理视频在线| 黑人性生活视频免费看| 馒头大胆亚洲一区二区| 视频二区在线视频观看| 女生自摸在线观看一区二区三区| xxx日本hd高清| 天天日天天干天天要| 国产极品精品免费视频| 偷青青国产精品青青在线观看| 欧美怡红院视频在线观看| 特大黑人巨大xxxx| 中文字幕av熟女人妻| 人妻少妇亚洲一区二区| 91精品国产高清自在线看香蕉网 | 中文字幕人妻熟女在线电影| 日韩剧情片电影在线收看| 蜜桃久久久久久久人妻| 久草电影免费在线观看| 五十路熟女人妻一区二区9933| 超碰在线中文字幕一区二区| 蜜桃视频在线欧美一区| 岛国av高清在线成人在线| 五月精品丁香久久久久福利社| 中文字幕第一页国产在线| 新97超碰在线观看| 天天躁夜夜躁日日躁a麻豆| 亚洲区美熟妇久久久久| 888亚洲欧美国产va在线播放| 国产清纯美女al在线| 日本一本午夜在线播放| 国产刺激激情美女网站| 日本三极片视频网站观看| 东京干手机福利视频| 天天干天天日天天干天天操| 蜜桃色婷婷久久久福利在线| 天天艹天天干天天操| 天天操天天爽天天干| 欧美亚洲少妇福利视频| 91免费放福利在线观看| 亚洲熟妇久久无码精品| 韩国AV无码不卡在线播放| 亚洲精品无码久久久久不卡| 成人高潮aa毛片免费| 日本福利午夜电影在线观看| 国产超码片内射在线| 国产欧美精品不卡在线| 日韩少妇人妻精品无码专区| www,久久久,com| 亚洲成人情色电影在线观看| 91精品资源免费观看| 中国黄色av一级片| 老师啊太大了啊啊啊尻视频| 一区二区视频视频视频| 青草久久视频在线观看| 三上悠亚和黑人665番号| 亚洲国产精品免费在线观看| 18禁无翼鸟成人在线| 中文字幕高清资源站| 视频 一区二区在线观看| 中文字幕一区二区三区人妻大片 | 欧美男人大鸡吧插女人视频| 亚洲美女高潮喷浆视频| av中文字幕网址在线| 内射久久久久综合网| 国产一区成人在线观看视频| 大鸡巴操娇小玲珑的女孩逼| 自拍 日韩 欧美激情| 91传媒一区二区三区| 日本性感美女写真视频| 欧美日韩熟女一区二区三区| 水蜜桃国产一区二区三区| 这里有精品成人国产99| 成人在线欧美日韩国产| 天堂av在线播放免费| 天天日天天透天天操| 国产女人被做到高潮免费视频 | 2o22av在线视频| 在线观看免费av网址大全| 国产高清精品极品美女| 中文字幕av熟女人妻| 国产精品久久久久久久久福交| 香蕉av影视在线观看| 亚洲欧美激情人妻偷拍| 丝袜肉丝一区二区三区四区在线 | 亚洲熟女综合色一区二区三区四区| 亚洲第17页国产精品| 扒开腿挺进肉嫩小18禁视频| 亚洲区美熟妇久久久久| 亚洲免费国产在线日韩| 亚洲欧美清纯唯美另类| 直接能看的国产av| 香港三日本三韩国三欧美三级| 亚洲精品av在线观看| 成人精品在线观看视频| 日韩美女精品视频在线观看网站 | 这里只有精品双飞在线播放| 天堂v男人视频在线观看| 人妻少妇精品久久久久久| 亚洲福利午夜久久久精品电影网| 中出中文字幕在线观看 | 一区二区三区麻豆福利视频| 日韩影片一区二区三区不卡免费| 午夜毛片不卡在线看| 亚洲欧美另类自拍偷拍色图| 亚洲av日韩精品久久久久久hd| 国产美女一区在线观看| 伊人网中文字幕在线视频| 国产V亚洲V天堂无码欠欠| 日韩中文字幕精品淫| 中文字幕第1页av一天堂网| av在线观看网址av| 成人sm视频在线观看| av天堂中文字幕最新| 久久综合老鸭窝色综合久久| 色哟哟在线网站入口| 精品久久久久久久久久久a√国产| av日韩在线免费播放| 天天干狠狠干天天操| 国产熟妇一区二区三区av | 精品av国产一区二区三区四区| 99热色原网这里只有精品| 可以在线观看的av中文字幕| 国产精品人妻66p| 日韩影片一区二区三区不卡免费| 中文字幕第1页av一天堂网| 一区二区三区麻豆福利视频| 黑人借宿ntr人妻的沦陷2| 天天日天天干天天要| japanese日本熟妇另类| 97精品人妻一区二区三区精品| yy96视频在线观看| 青青尤物在线观看视频网站| 2021最新热播中文字幕| 欧美地区一二三专区| 97人妻夜夜爽二区欧美极品| 香蕉片在线观看av| 国产精品久久久久久久精品视频| 在线播放 日韩 av| 女同久久精品秋霞网| 中文字幕一区二区自拍| 日本性感美女写真视频| 黑人3p华裔熟女普通话| 99精品免费观看视频| 精品美女久久久久久| 日韩二区视频一线天婷婷五| 99热这里只有精品中文| 顶级尤物粉嫩小尤物网站| 亚洲免费国产在线日韩| 亚洲天堂第一页中文字幕| 精品日产卡一卡二卡国色天香| 九色porny九色9l自拍视频| 亚洲高清一区二区三区视频在线| 岛国黄色大片在线观看| 九九视频在线精品播放| 欧美激情精品在线观看| 美女福利视频导航网站| 97超碰免费在线视频| 亚洲av日韩av第一区二区三区| 日韩美女精品视频在线观看网站 | 午夜精品亚洲精品五月色| 午夜精彩视频免费一区| 93精品视频在线观看| 欧美国产亚洲中英文字幕| 青青青青操在线观看免费| 免费啪啪啪在线观看视频| av天堂中文免费在线| 免费在线看的黄片视频| 日本乱人一区二区三区| 亚洲国产欧美一区二区三区久久| 天天日天天干天天搡| 国产亚洲欧美另类在线观看| 免费岛国喷水视频在线观看| ka0ri在线视频| 亚洲一区二区三区av网站| 成人av在线资源网站| 女生自摸在线观看一区二区三区 | 女生被男生插的视频网站| 大屁股肉感人妻中文字幕在线| 91p0rny九色露脸熟女| 2020韩国午夜女主播在线| 91久久精品色伊人6882| 天天干天天啪天天舔| 亚洲 图片 欧美 图片| 精品av久久久久久久| 黑人巨大精品欧美视频| 91天堂天天日天天操| 亚洲高清国产自产av| 在线观看亚洲人成免费网址| 阴茎插到阴道里面的视频| 色哟哟在线网站入口| 免费岛国喷水视频在线观看| 日曰摸日日碰夜夜爽歪歪| 成人免费公开视频无毒| 国产在线91观看免费观看| 动色av一区二区三区| 男大肉棒猛烈插女免费视频| 老鸭窝日韩精品视频观看| 护士特殊服务久久久久久久| 亚洲中文字幕综合小综合| 99精品视频之69精品视频| 欧亚乱色一区二区三区| 精品久久久久久高潮| 久草视频首页在线观看| 搡老熟女一区二区在线观看| 最新97国产在线视频| 老司机福利精品免费视频一区二区| 成人sm视频在线观看| 欧美亚洲国产成人免费在线 | 青青草人人妻人人妻| 玖玖一区二区在线观看| 五月激情婷婷久久综合网| 日本裸体熟妇区二区欧美| av在线shipin| 在线免费观看靠比视频的网站| 国产露脸对白在线观看| 亚洲av人人澡人人爽人人爱| 成人av久久精品一区二区| 懂色av蜜桃a v| 国产成人一区二区三区电影网站| 久久一区二区三区人妻欧美| 2022天天干天天操| 久久精品亚洲成在人线a| 午夜在线观看一区视频| 99人妻视频免费在线| 黄色大片免费观看网站| 久久亚洲天堂中文对白| 在线不卡成人黄色精品| 88成人免费av网站| 亚洲国产精品美女在线观看| 国产三级精品三级在线不卡| 欧美视频一区免费在线| 精品久久久久久久久久久久人妻| 任你操任你干精品在线视频 | 黄色三级网站免费下载| 瑟瑟视频在线观看免费视频| 啊慢点鸡巴太大了啊舒服视频| 亚洲午夜电影在线观看| 中文字幕熟女人妻久久久| 9色精品视频在线观看| 天天干夜夜操啊啊啊| 18禁免费av网站| 操人妻嗷嗷叫视频一区二区 | 国产精品熟女久久久久浪潮| 成人免费做爰高潮视频| 亚洲 清纯 国产com| 亚洲精品av在线观看| 欧美国品一二三产区区别| 亚洲精品三级av在线免费观看| 国产亚洲视频在线观看| 亚洲2021av天堂| 大香蕉大香蕉大香蕉大香蕉大香蕉| 粗大的内捧猛烈进出爽大牛汉子| 免费在线观看视频啪啪| 欧美亚洲中文字幕一区二区三区| 99热碰碰热精品a中文| 日韩欧美一级精品在线观看| 精品人妻伦一二三区久| 午夜精品九一唐人麻豆嫩草成人| 欧美亚洲一二三区蜜臀| 绝色少妇高潮3在线观看| 国产又粗又猛又爽又黄的视频在线 | 天天摸天天干天天操科普| 岛国免费大片在线观看| 黄色黄色黄片78在线| 69精品视频一区二区在线观看| 中文字幕AV在线免费看 | 午夜精彩视频免费一区| 绯色av蜜臀vs少妇| 自拍偷区二区三区麻豆| 亚洲欧美色一区二区| 中文字幕之无码色多多| 精品一区二区三区三区88 | 在线观看操大逼视频| 亚洲一区二区激情在线| 欧美日韩v中文在线| 青青青青青青青青青国产精品视频| 亚洲欧美久久久久久久久| 狠狠嗨日韩综合久久| 特一级特级黄色网片| 护士特殊服务久久久久久久| 午夜精品九一唐人麻豆嫩草成人| 鸡巴操逼一级黄色气| 国际av大片在线免费观看| 国产chinesehd精品麻豆| 一级a看免费观看网站| av资源中文字幕在线观看| 欧美特色aaa大片| 精品国产乱码一区二区三区乱| 99热色原网这里只有精品| 在线观看的a站 最新| 日本韩国免费一区二区三区视频 | 888欧美视频在线| 97超碰人人搞人人| 桃色视频在线观看一区二区| 一区二区三区毛片国产一区| 精品人妻伦一二三区久 | 无码国产精品一区二区高潮久久4| 风流唐伯虎电视剧在线观看| 精品久久久久久久久久久久人妻| 亚洲中文精品字幕在线观看| 亚洲美女高潮喷浆视频| 亚洲精品乱码久久久久久密桃明| 天堂资源网av中文字幕| 骚货自慰被发现爆操| 综合国产成人在线观看| 免费看高清av的网站| 日韩近亲视频在线观看| 日韩欧美高清免费在线| 国产福利在线视频一区| 色噜噜噜噜18禁止观看| 欧美天堂av无线av欧美| 天天插天天狠天天操| 欧美男人大鸡吧插女人视频| 中文字幕免费福利视频6| 国产欧美精品不卡在线| 亚洲一区二区三区精品乱码| 亚洲午夜电影之麻豆| 国产成人综合一区2区| 久久精品视频一区二区三区四区| 色婷婷精品大在线观看| 大香蕉玖玖一区2区| 亚洲另类伦春色综合小| 97精品综合久久在线| 成人久久精品一区二区三区| 青青青青在线视频免费观看| 老司机99精品视频在线观看| 人妻自拍视频中国大陆| 人妻素人精油按摩中出| 色婷婷久久久久swag精品| 亚洲自拍偷拍综合色| 午夜在线观看岛国av,com| 亚洲精品高清自拍av| 国产欧美精品不卡在线| 欧美一区二区三区高清不卡tv| 国产福利小视频大全| 男人天堂av天天操| 早川濑里奈av黑人番号| 一区二区三区视频,福利一区二区| 国产精品自偷自拍啪啪啪| 午夜久久久久久久99| 韩国爱爱视频中文字幕| 精品欧美一区二区vr在线观看| 最新国产亚洲精品中文在线| 人妻激情图片视频小说| 免费在线福利小视频| 青青草原色片网站在线观看| 天天射,天天操,天天说| 香蕉91一区二区三区| av高潮迭起在线观看| 中文字幕一区二区亚洲一区| 日本福利午夜电影在线观看| 亚洲日本一区二区久久久精品| 国产精品久久9999| 老司机午夜精品视频资源| 精品人妻每日一部精品| 亚洲综合另类欧美久久| 一区二区三区麻豆福利视频| 久久永久免费精品人妻专区| 成熟熟女国产精品一区| 人妻熟女在线一区二区| 亚国产成人精品久久久| 人妻无码色噜噜狠狠狠狠色| tube69日本少妇| 又粗又硬又猛又爽又黄的| 在线观看的a站 最新| 亚洲成人午夜电影在线观看| 亚洲福利精品福利精品福利| 亚洲精品久久综合久| 亚洲精品成人网久久久久久小说| 亚洲熟妇x久久av久久| 午夜蜜桃一区二区三区| 欧美国品一二三产区区别| 国产又粗又黄又硬又爽| 爱有来生高清在线中文字幕| 亚洲国产中文字幕啊啊啊不行了 | 蜜桃色婷婷久久久福利在线| 2021久久免费视频| 大香蕉大香蕉大香蕉大香蕉大香蕉| 成人精品视频99第一页| 激情综合治理六月婷婷| 亚洲国产精品美女在线观看| 2020中文字幕在线播放| 亚洲熟妇久久无码精品| 天天躁日日躁狠狠躁躁欧美av | 中文字幕人妻三级在线观看| 99视频精品全部15| 成人网18免费视频版国产| 青青草视频手机免费在线观看| 亚洲国际青青操综合网站| 欧美久久久久久三级网| 亚洲视频在线视频看视频在线| 不卡精品视频在线观看| 精品一区二区三区欧美| 男人在床上插女人视频| 成年午夜影片国产片| 日本熟女50视频免费| 青青青国产片免费观看视频| 美女在线观看日本亚洲一区| 美女日逼视频免费观看| 国产精品sm调教视频| 婷婷色国产黑丝少妇勾搭AV | 中国黄色av一级片| 北条麻妃高跟丝袜啪啪| 偷拍自拍亚洲美腿丝袜| 一区二区视频视频视频| 3337p日本欧洲大胆色噜噜| 在线观看免费视频网| 日本人妻欲求不满中文字幕| 又粗又长 明星操逼小视频| 福利片区一区二体验区| 一本一本久久a久久精品综合不卡 亚洲另类综合一区小说 | 在线视频精品你懂的| 欧美精品资源在线观看| 91久久人澡人人添人人爽乱| 同居了嫂子在线播高清中文| 18禁美女黄网站色大片下载| 国产精品一区二区三区蜜臀av| 天天操天天射天天操天天天| 欲乱人妻少妇在线视频裸| 天天艹天天干天天操| 熟女人妻一区二区精品视频| 日本啪啪啪啪啪啪啪| 姐姐的朋友2在线观看中文字幕| 亚洲va天堂va国产va久| 欧美综合婷婷欧美综合| 亚洲国产精品美女在线观看| 欧美香蕉人妻精品一区二区| 欧洲精品第一页欧洲精品亚洲| 天天日夜夜干天天操| 日韩欧美制服诱惑一区在线| 日曰摸日日碰夜夜爽歪歪| 涩爱综合久久五月蜜臀| 888欧美视频在线| 成人在线欧美日韩国产| 亚洲精品精品国产综合| 99热久久这里只有精品8| 2020国产在线不卡视频| 亚洲欧美成人综合在线观看| 热久久只有这里有精品| 91av中文视频在线| 亚洲视频在线视频看视频在线| 亚洲在线一区二区欧美| 好太好爽好想要免费| 亚洲综合图片20p| 超级福利视频在线观看| 日韩精品二区一区久久| 欧美黑人巨大性xxxxx猛交| 中国产一级黄片免费视频播放| 欧美色呦呦最新网址| 亚洲男人在线天堂网| 视频在线免费观看你懂得| 午夜在线一区二区免费| 色爱av一区二区三区| 天堂av在线官网中文| 天天日天天干天天要| 91成人在线观看免费视频| 91福利视频免费在线观看| 一区二区在线观看少妇| 欧美精品国产综合久久| huangse网站在线观看| 中文字幕免费在线免费| 亚洲福利精品福利精品福利| 国产乱弄免费视频观看| 日本在线一区二区不卡视频| 久久精品亚洲国产av香蕉| 国产女人被做到高潮免费视频| 免费看高清av的网站| 中文字幕一区二区三区蜜月| 亚洲男人在线天堂网| 精彩视频99免费在线| 天天干天天爱天天色| 午夜青青草原网在线观看| 亚洲一区二区久久久人妻| 日韩欧美高清免费在线| 亚洲精品ww久久久久久| 成人30分钟免费视频| 五十路av熟女松本翔子| 午夜av一区二区三区| 国产亚州色婷婷久久99精品| 一区二区三区综合视频| 久久久精品欧洲亚洲av| 亚洲一区二区人妻av| 亚洲人妻30pwc| 日韩写真福利视频在线观看| 蜜桃色婷婷久久久福利在线| 人妻素人精油按摩中出| 肏插流水妹子在线乐播下载| 国产在线拍揄自揄视频网站| 日韩成人性色生活片| 精品区一区二区三区四区人妻 | 57pao国产一区二区| 国产精品黄页网站视频| 中文字幕1卡1区2区3区| 国产精品久久久久国产三级试频| 黄网十四区丁香社区激情五月天 | 天天操夜夜操天天操天天操| 国产伦精品一区二区三区竹菊| 日韩影片一区二区三区不卡免费 | 国产视频网站国产视频| 国产aⅴ一线在线观看| 超碰在线中文字幕一区二区| 亚洲成av人无码不卡影片一| 亚洲精品一线二线在线观看| 黑人变态深video特大巨大| 亚洲国产精品中文字幕网站| 国产成人精品av网站| 欧美天堂av无线av欧美| 欧美特级特黄a大片免费| 人人妻人人澡人人爽人人dvl| 日韩av熟妇在线观看| 中国熟女一区二区性xx| 亚洲av男人天堂久久| 国产91嫩草久久成人在线视频| 久久久久久性虐视频| 最近中文2019年在线看| 精品黑人一区二区三区久久国产| 午夜精品一区二区三区城中村| 美女被肏内射视频网站| 在线观看欧美黄片一区二区三区| 91在线视频在线精品3| 国产实拍勾搭女技师av在线| 91人妻精品一区二区在线看| 91快播视频在线观看| 国产精品人妻66p| 2021天天色天天干| av无限看熟女人妻另类av| 五十路老熟女码av| 好太好爽好想要免费| 51精品视频免费在线观看| 二区中出在线观看老师| 啪啪啪18禁一区二区三区| 福利视频广场一区二区| 男人天堂色男人av| 亚洲免费国产在线日韩| aiss午夜免费视频| 18禁网站一区二区三区四区| 一区二区三区日韩久久| 国产成人精品福利短视频| 中文字幕—97超碰网| 国产亚洲欧美视频网站| 免费69视频在线看| 欧美 亚洲 另类综合| 亚洲一级美女啪啪啪| 五十路熟女av天堂| 黑人3p华裔熟女普通话| 中文字幕奴隷色的舞台50| 自拍偷拍一区二区三区图片| 成人蜜桃美臀九一一区二区三区| 99久久激情婷婷综合五月天| 亚洲一级 片内射视正片| 综合色区亚洲熟妇shxstz| 最新91精品视频在线| 亚洲色偷偷综合亚洲AV伊人| 国产chinesehd精品麻豆| 亚洲欧美国产麻豆综合| 人妻丝袜诱惑我操她视频| tube69日本少妇| 岳太深了紧紧的中文字幕| 国产免费高清视频视频| 亚洲一区二区三区精品视频在线| 国产精品久久久久久久精品视频| 中文字幕av熟女人妻| 日本少妇人妻xxxxxhd| 亚洲中文字字幕乱码| 美女操逼免费短视频下载链接| 91传媒一区二区三区| 国产精品自偷自拍啪啪啪| 2020国产在线不卡视频| 人妻无码色噜噜狠狠狠狠色| 初美沙希中文字幕在线| 欧美精产国品一二三产品价格| 超碰公开大香蕉97| 亚洲av黄色在线网站| 国产黄色a级三级三级三级| 国产亚洲视频在线观看| av无限看熟女人妻另类av| 色在线观看视频免费的| 亚国产成人精品久久久| 97人人妻人人澡人人爽人人精品| 78色精品一区二区三区| 唐人色亚洲av嫩草| 熟女俱乐部一二三区| 93视频一区二区三区| jul—619中文字幕在线| 偷拍3456eee| 天天干天天操天天摸天天射| 亚洲国产成人在线一区| 日本阿v视频在线免费观看| 福利午夜视频在线观看| 超黄超污网站在线观看| aⅴ精产国品一二三产品| 久久久精品欧洲亚洲av| 蜜桃色婷婷久久久福利在线| 亚洲少妇高潮免费观看| 福利视频广场一区二区| 色综合色综合色综合色| 任我爽精品视频在线播放| 国产女人叫床高潮大片视频| 亚洲一区二区久久久人妻| japanese日本熟妇另类| aⅴ精产国品一二三产品| 欧美另类重口味极品在线观看| 操人妻嗷嗷叫视频一区二区| 国产一线二线三线的区别在哪| 精产国品久久一二三产区区别 | 91欧美在线免费观看| 98视频精品在线观看| 国产无遮挡裸体免费直播视频| 人人爱人人妻人人澡39| 91大神福利视频网| 丝袜肉丝一区二区三区四区在线看| 91小伙伴中女熟女高潮| 国产日韩av一区二区在线| 久久尻中国美女视频| 欧美少妇性一区二区三区| 777奇米久久精品一区| 少妇人妻二三区视频| 亚洲一区二区三区在线高清| 日本一区精品视频在线观看| 日韩av熟妇在线观看| 人人爽亚洲av人人爽av| 欧美一区二区三区久久久aaa| 日本三极片视频网站观看| 日韩av中文在线免费观看| 天天草天天色天天干| 男人操女人的逼免费视频| 91亚洲手机在线视频播放| 宅男噜噜噜666免费观看| 亚洲综合乱码一区二区| 亚洲欧美色一区二区| sspd152中文字幕在线| 免费在线观看污污视频网站| 在线不卡日韩视频播放| 久草视频在线一区二区三区资源站 | 亚洲精品国产综合久久久久久久久| 一区二区三区国产精选在线播放| 青青青青爽手机在线| 欧美黑人巨大性xxxxx猛交| 亚洲最大黄了色网站| 91精品激情五月婷婷在线| 亚洲激情av一区二区| 日比视频老公慢点好舒服啊| 婷婷色国产黑丝少妇勾搭AV| 日韩美女福利视频网| 亚洲国产精品久久久久蜜桃| 日本丰满熟妇BBXBBXHD| 五月激情婷婷久久综合网| 国产欧美精品一区二区高清 | 伊人综合免费在线视频| 11久久久久久久久久久| 亚洲欧美国产综合777| 91小伙伴中女熟女高潮| 欧美区一区二区三视频| 91色老99久久九九爱精品| 无码日韩人妻精品久久| 日本成人不卡一区二区| 中文字幕中文字幕人妻| 五月婷婷在线观看视频免费| 巨乳人妻日下部加奈被邻居中出| 精品一线二线三线日本| 青青草原网站在线观看| 日本三极片视频网站观看| 亚洲自拍偷拍综合色| 成熟丰满熟妇高潮xx×xx| 蜜桃色婷婷久久久福利在线| 18禁美女羞羞免费网站| 国产在线观看黄色视频| 国产揄拍高清国内精品对白| 亚洲一级特黄特黄黄色录像片| 久久久久久久精品老熟妇| 91社福利《在线观看| 亚洲无线观看国产高清在线| 77久久久久国产精产品| 中文字幕+中文字幕| 沈阳熟妇28厘米大战黑人| 免费高清自慰一区二区三区网站| 丁香花免费在线观看中文字幕| 激情图片日韩欧美人妻| 人妻熟女中文字幕aⅴ在线| 久久久久久97三级| 自拍偷拍亚洲欧美在线视频| 岛国黄色大片在线观看| 不戴胸罩引我诱的隔壁的人妻| www久久久久久久久久久| 日本人竟这样玩学生妹| 熟女人妻三十路四十路人妻斩| 午夜精品久久久久麻豆影视| 97a片免费在线观看| 国产精品久久久久久久精品视频| 天天干夜夜操啊啊啊| 黄色成人在线中文字幕| 国产视频一区二区午夜| 最新91九色国产在线观看| 精品人人人妻人人玩日产欧| 男人操女人逼逼视频网站| 欧美日韩情色在线观看| 欧美日韩一区二区电影在线观看| 成人高潮aa毛片免费| 大香蕉伊人国产在线| 国产福利小视频大全| 国产密臀av一区二区三| 亚洲2021av天堂| 久久永久免费精品人妻专区| 丰满的子国产在线观看| 日韩三级黄色片网站| av久久精品北条麻妃av观看| 蜜桃专区一区二区在线观看| 欧美麻豆av在线播放| 亚洲精品在线资源站| 亚洲精品国产久久久久久| gav成人免费播放| 少妇人妻真实精品视频| 国产精品亚洲а∨天堂免| 欧美精品中文字幕久久二区| 插小穴高清无码中文字幕| 国产日韩一区二区在线看| 黄色片黄色片wyaa| 国产亚州色婷婷久久99精品| 日韩欧美国产一区不卡| a v欧美一区=区三区| 99热久久这里只有精品8| 国产精品欧美日韩区二区| 成年人黄色片免费网站| 视频一区 二区 三区 综合| 国产 在线 免费 精品| xxx日本hd高清| 欧美在线精品一区二区三区视频 | 大香蕉伊人中文字幕| 综合色区亚洲熟妇shxstz| 国产麻豆乱子伦午夜视频观看| 热思思国产99re| 欧洲亚洲欧美日韩综合| 国产普通话插插视频| 国产91精品拍在线观看| 亚洲高清一区二区三区视频在线| 日韩欧美国产精品91| 少妇ww搡性bbb91| 国产午夜福利av导航| 亚洲成人精品女人久久久| 婷婷色中文亚洲网68| 99re国产在线精品| 亚洲粉嫩av一区二区三区| 中文字幕人妻熟女在线电影| 天天操,天天干,天天射| 日韩一区二区电国产精品| 国产精品视频欧美一区二区 | 免费观看理论片完整版| 国产欧美日韩第三页| 亚洲一区二区人妻av| 91亚洲手机在线视频播放| 免费看国产av网站| 国产欧美精品不卡在线| 乱亲女秽乱长久久久| 在线不卡成人黄色精品| 日本啪啪啪啪啪啪啪| 五十路av熟女松本翔子| 国内自拍第一页在线观看| 白白操白白色在线免费视频| 成人av天堂丝袜在线观看| 欧美综合婷婷欧美综合| 日本av在线一区二区三区| 亚洲精品成人网久久久久久小说 | 狠狠鲁狠狠操天天晚上干干| av大全在线播放免费| heyzo蜜桃熟女人妻| 天天射,天天操,天天说| 亚洲av极品精品在线观看| 97年大学生大白天操逼| 亚洲美女美妇久久字幕组| 久久久人妻一区二区| 欧美一级片免费在线成人观看| 好太好爽好想要免费| 清纯美女在线观看国产| 中文字幕亚洲久久久| 岛国毛片视频免费在线观看| 免费在线观看视频啪啪| 狠狠嗨日韩综合久久| yy6080国产在线视频| 班长撕开乳罩揉我胸好爽| 家庭女教师中文字幕在线播放| 日本最新一二三区不卡在线 | 人妻少妇性色欲欧美日韩| 五十路人妻熟女av一区二区| sw137 中文字幕 在线| 特黄老太婆aa毛毛片| 二区中出在线观看老师 | 精品一区二区三区午夜| 97精品综合久久在线| 大黑人性xxxxbbbb| 欧美精产国品一二三区| 欧美在线精品一区二区三区视频 | 亚洲丝袜老师诱惑在线观看| 亚洲视频在线观看高清| 可以免费看的www视频你懂的| 欧美成人综合视频一区二区| 五十路息与子猛烈交尾视频| 美日韩在线视频免费看| 特大黑人巨大xxxx| 日本三极片中文字幕| 日韩精品中文字幕播放| 可以在线观看的av中文字幕| 性欧美日本大妈母与子| 亚洲免费av在线视频| 日本av高清免费网站| 91国内精品久久久久精品一| 国产老熟女伦老熟妇ⅹ| 亚洲欧美清纯唯美另类 | 欧美激情电影免费在线| 天天摸天天干天天操科普| 91av中文视频在线| 91国偷自产一区二区三区精品| 一区二区三区另类在线| 激情综合治理六月婷婷| 2021年国产精品自拍| 天天日天天舔天天射进去| 青青操免费日综合视频观看| 国产精品人妻熟女毛片av久| 日韩激情文学在线视频| 又粗又硬又猛又黄免费30| 国产精品一区二区av国| 亚洲av日韩av网站| 在线成人日韩av电影| 国产精品自拍偷拍a| 视频在线亚洲一区二区| 国产精品国产精品一区二区| 国产亚洲精品品视频在线| 欧美国产亚洲中英文字幕| 婷婷久久久综合中文字幕| 国产熟妇人妻ⅹxxxx麻豆| 一区二区三区蜜臀在线| 亚洲国产欧美一区二区三区久久 | 精产国品久久一二三产区区别| 亚洲欧美国产综合777| 日韩成人性色生活片| 国产精品久久久黄网站| 青娱乐在线免费视频盛宴| 成年午夜免费无码区| 午夜精品九一唐人麻豆嫩草成人| 99精品免费观看视频| 黄色片黄色片wyaa| 又色又爽又黄的美女裸体| 91精品国产观看免费| 人妻少妇亚洲一区二区| 无套猛戳丰满少妇人妻| 青青尤物在线观看视频网站| 国产在线91观看免费观看| 天天色天天爱天天爽| 欧美偷拍自拍色图片| 国语对白xxxx乱大交| 福利在线视频网址导航 | 青青青青在线视频免费观看| 非洲黑人一级特黄片| 最后99天全集在线观看| 人妻最新视频在线免费观看| 国产三级影院在线观看| av网址国产在线观看| 75国产综合在线视频| av俺也去在线播放| 男人操女人的逼免费视频| 偷拍自拍福利视频在线观看| 日韩av中文在线免费观看| 视频久久久久久久人妻| 午夜精品久久久久久99热| 六月婷婷激情一区二区三区| 任你操视频免费在线观看| 岛国青草视频在线观看| 人妻丝袜诱惑我操她视频| 男大肉棒猛烈插女免费视频| 最新国产精品网址在线观看| 美女福利视频导航网站| 日本性感美女视频网站| 在线免费观看日本伦理| 在线免费观看亚洲精品电影 | 水蜜桃一区二区三区在线观看视频 | 亚洲av天堂在线播放| 日韩成人综艺在线播放| 日本在线一区二区不卡视频| 亚洲天堂精品久久久| 粉嫩小穴流水视频在线观看| 欧美亚洲少妇福利视频| 少妇人妻二三区视频| 久久久久久久久久久久久97| 精品久久久久久久久久久a√国产| 无套猛戳丰满少妇人妻| 亚洲免费福利一区二区三区| 天天躁日日躁狠狠躁av麻豆| 骚逼被大屌狂草视频免费看| 色狠狠av线不卡香蕉一区二区| 欧美特色aaa大片| 日本特级片中文字幕| 东京热男人的av天堂| 黄片色呦呦视频免费看| 自拍偷拍,中文字幕| 99久久99一区二区三区| 无码精品一区二区三区人| 91一区精品在线观看| 97超碰免费在线视频| 亚洲av日韩av网站| 欧美国产亚洲中英文字幕| 11久久久久久久久久久| 日韩特级黄片高清在线看| 97色视频在线观看| 人人超碰国字幕观看97| 少妇人妻100系列| 中文字幕在线观看国产片| 亚洲伊人色一综合网| 欧美精产国品一二三产品区别大吗| 99视频精品全部15| 亚洲精品国产久久久久久| 少妇被强干到高潮视频在线观看 | 91精品国产麻豆国产| 久久精品在线观看一区二区| 无码日韩人妻精品久久| 精品一区二区亚洲欧美| 国产一区av澳门在线观看| 久久精品视频一区二区三区四区| 亚洲综合乱码一区二区| 日本性感美女写真视频| 亚洲推理片免费看网站| 国产一区二区在线欧美| av无限看熟女人妻另类av| 91亚洲精品干熟女蜜桃频道| 人妻无码色噜噜狠狠狠狠色| 一区二区三区日韩久久| av黄色成人在线观看| 涩爱综合久久五月蜜臀| 国产午夜无码福利在线看| 亚洲成人精品女人久久久| 天天操天天干天天艹| 93精品视频在线观看| 国产精彩对白一区二区三区| 制丝袜业一区二区三区| 中文字幕午夜免费福利视频| 亚洲精品中文字幕下载| 国产精品女邻居小骚货| 亚洲美女自偷自拍11页| 国产污污污污网站在线| 亚洲一区二区三区久久受| 深夜男人福利在线观看| 日韩av免费观看一区| 成人综合亚洲欧美一区| 亚洲天堂第一页中文字幕| 亚洲综合另类欧美久久| 后入美女人妻高清在线| 人妻少妇av在线观看| 国产自拍黄片在线观看| 天天色天天操天天透| 日韩精品二区一区久久| 成年人黄色片免费网站| 精品首页在线观看视频| 班长撕开乳罩揉我胸好爽| 日本熟女精品一区二区三区| 99精品国自产在线人| 干逼又爽又黄又免费的视频| 欧美成人小视频在线免费看| 人妻另类专区欧美制服| 国产片免费观看在线观看| 福利一二三在线视频观看| 一区二区麻豆传媒黄片| 国产片免费观看在线观看| 黄色三级网站免费下载| 亚洲高清自偷揄拍自拍| 国产精品一二三不卡带免费视频| 日本在线一区二区不卡视频| 欧美3p在线观看一区二区三区| 国产又大又黄免费观看| 高潮喷水在线视频观看| 天码人妻一区二区三区在线看| 色偷偷伊人大杳蕉综合网| 一区二区三区四区五区性感视频| 99人妻视频免费在线| 4个黑人操素人视频网站精品91| 岛国免费大片在线观看| 亚洲熟妇x久久av久久| 免费岛国喷水视频在线观看| 国产普通话插插视频| 北条麻妃高跟丝袜啪啪| 综合国产成人在线观看| 国产高潮无码喷水AV片在线观看| 一区二区三区精品日本| 在线观看911精品国产| 日本人竟这样玩学生妹| 激情内射在线免费观看| www骚国产精品视频| 亚洲一区二区三区久久午夜 | 最新国产精品拍在线观看| 一本久久精品一区二区| 最新97国产在线视频| 青青青青青手机视频| 激情五月婷婷综合色啪| 都市激情校园春色狠狠| 国产夫妻视频在线观看免费| 搡老妇人老女人老熟女| 黄色成人在线中文字幕| 超污视频在线观看污污污| 亚洲视频乱码在线观看| 日日夜夜大香蕉伊人| 97人妻总资源视频| 中文字幕人妻三级在线观看| av中文字幕国产在线观看| 18禁美女羞羞免费网站| 9国产精品久久久久老师| 11久久久久久久久久久| 一区二区三区av高清免费| 在线观看黄色成年人网站| 一区二区三区 自拍偷拍| 午夜蜜桃一区二区三区| 亚洲超碰97人人做人人爱| 午夜精品久久久久麻豆影视| 久久精品国产23696| 亚洲中文字字幕乱码| 黄色成年网站午夜在线观看| 国产va精品免费观看| 国产精品三级三级三级| 瑟瑟视频在线观看免费视频| 欧美中国日韩久久精品| 99精品久久久久久久91蜜桃| 中国无遮挡白丝袜二区精品 | 久久麻豆亚洲精品av| 色婷婷精品大在线观看| 日本阿v视频在线免费观看| av日韩在线观看大全| 午夜久久香蕉电影网| 亚洲av色香蕉一区二区三区| 国产老熟女伦老熟妇ⅹ| 超黄超污网站在线观看| 亚洲精品乱码久久久本| 国产日韩一区二区在线看| 国产清纯美女al在线| 久久精品国产亚洲精品166m| 久草视频在线看免费| 黄色片黄色片wyaa| 日本男女操逼视频免费看| 精品国产亚洲av一淫| 亚洲 欧美 精品 激情 偷拍| 日本a级视频老女人| 午夜的视频在线观看| av男人天堂狠狠干| 韩国一级特黄大片做受| 日韩亚国产欧美三级涩爱| 久久丁香婷婷六月天| 天天操夜夜操天天操天天操| 天天操天天射天天操天天天| 国产自拍黄片在线观看| 韩国AV无码不卡在线播放| 在线免费观看欧美小视频| 美女 午夜 在线视频| 精产国品久久一二三产区区别| 国产乱子伦精品视频潮优女| 国产福利小视频免费观看| 成人亚洲国产综合精品| 天天干天天操天天摸天天射| 国产亚洲成人免费在线观看 | 中文字幕国产专区欧美激情 | 国产亚州色婷婷久久99精品| 伊人情人综合成人久久网小说 | 绝顶痉挛大潮喷高潮无码| 自拍 日韩 欧美激情| 好太好爽好想要免费| 国产在线自在拍91国语自产精品| 加勒比视频在线免费观看| 国产性感美女福利视频| 天天日天天摸天天爱| 精内国产乱码久久久久久| 阴茎插到阴道里面的视频| 93精品视频在线观看| 色综合天天综合网国产成人 | 91九色porny蝌蚪国产成人| 国产成人午夜精品福利| 夜鲁夜鲁狠鲁天天在线| 欧美日韩人妻久久精品高清国产| 天天日天天玩天天摸| 大屁股熟女一区二区三区| 亚洲国产美女一区二区三区软件 | 免费高清自慰一区二区三区网站 | 性欧美日本大妈母与子| 青青青视频自偷自拍38碰| 激情综合治理六月婷婷| 日韩三级黄色片网站| 绝色少妇高潮3在线观看| 人人人妻人人澡人人| 精品国产污污免费网站入口自| 青青草成人福利电影| 又粗又硬又猛又黄免费30| 亚洲成人精品女人久久久| 少妇被强干到高潮视频在线观看| 可以免费看的www视频你懂的| 欧美成人综合视频一区二区| 午夜av一区二区三区| 亚洲精品色在线观看视频| 久久久久久久99精品| av在线免费中文字幕| 国产97在线视频观看| 久久久久久久99精品| 青娱乐最新视频在线| 亚洲av日韩av第一区二区三区| 青青青视频自偷自拍38碰| 中文字幕一区二区人妻电影冢本 | 国产一区二区三免费视频| 精品一区二区三四区| 国产成人自拍视频在线免费观看| 黄色的网站在线免费看| 啪啪啪啪啪啪啪啪啪啪黄色| www天堂在线久久| 国产va精品免费观看| 91久久人澡人人添人人爽乱| 婷婷激情四射在线观看视频| 欧美黄片精彩在线免费观看| 国产日韩精品免费在线| 天天日天天透天天操| 精品高潮呻吟久久av| 日韩三级黄色片网站| 亚洲高清免费在线观看视频| 亚洲一区二区三区精品视频在线| 一区二区三区四区五区性感视频| 91大屁股国产一区二区| 日韩不卡中文在线视频网站| 中文字幕高清在线免费播放| 男人的天堂一区二区在线观看| 免费在线黄色观看网站| 少妇系列一区二区三区视频| 久久热这里这里只有精品| 这里有精品成人国产99| 国产午夜男女爽爽爽爽爽视频| 高潮喷水在线视频观看| 久久久久久九九99精品| 日日爽天天干夜夜操| 中文字幕在线欧美精品| 全国亚洲男人的天堂| 黑人巨大的吊bdsm| 中国产一级黄片免费视频播放| 2022中文字幕在线| 成人国产小视频在线观看| 免费看国产av网站| 成人免费毛片aaaa| 欧美黑人与人妻精品| 色在线观看视频免费的| 日韩在线中文字幕色| 91精品视频在线观看免费| 天天日天天干天天要| 经典亚洲伊人第一页| 四虎永久在线精品免费区二区| 久久久精品精品视频视频| 大鸡巴后入爆操大屁股美女| 亚洲久久午夜av一区二区| 99精品视频之69精品视频| 国产午夜无码福利在线看| 久久久久久久久久一区二区三区 | 日本韩国在线观看一区二区| 1区2区3区不卡视频| 狠狠操狠狠操免费视频| 99久久中文字幕一本人| 亚洲精品ww久久久久久| 任你操任你干精品在线视频| 欧美日本在线视频一区| 国产精品自偷自拍啪啪啪| av男人天堂狠狠干| chinese国产盗摄一区二区| 粉嫩av蜜乳av蜜臀| 人妻熟女中文字幕aⅴ在线| jiuse91九色视频| 国产午夜福利av导航| 97精品成人一区二区三区| 五十路在线观看完整版| av乱码一区二区三区| 午夜蜜桃一区二区三区| 国产精品国产精品一区二区| 国产片免费观看在线观看| 少妇人妻久久久久视频黄片| 青青社区2国产视频| 亚洲 中文字幕在线 日韩| 无码中文字幕波多野不卡| 免费观看成年人视频在线观看| 久久免费看少妇高潮完整版| 天天干天天操天天插天天日| 青青草亚洲国产精品视频| 九色精品视频在线播放| 亚洲欧洲一区二区在线观看| 午夜精品久久久久久99热| 男大肉棒猛烈插女免费视频 | 2021天天色天天干| 57pao国产一区二区| 国产又色又刺激在线视频| 自拍偷拍亚洲精品第2页| av成人在线观看一区| 大香蕉大香蕉大香蕉大香蕉大香蕉 | 真实国产乱子伦一区二区| 视频一区二区在线免费播放| 国产a级毛久久久久精品| 国产精品成久久久久三级蜜臀av| 亚洲蜜臀av一区二区三区九色| 欧美特级特黄a大片免费| 亚洲成人国产综合一区| 久草视频在线看免费| 精品欧美一区二区vr在线观看| 护士特殊服务久久久久久久| 自拍偷拍亚洲欧美在线视频| 亚洲综合图片20p| 亚洲精品国品乱码久久久久| 天天干夜夜操天天舔| av中文在线天堂精品| 91极品新人『兔兔』精品新作 | 美日韩在线视频免费看| 日韩少妇人妻精品无码专区 | 福利午夜视频在线观看| av完全免费在线观看av| 青青草人人妻人人妻| 人人妻人人人操人人人爽| 国产一线二线三线的区别在哪| 久久这里只有精品热视频| 色综合久久五月色婷婷综合| 中文字幕第1页av一天堂网| 91国产在线免费播放| av一区二区三区人妻| 亚洲欧美另类自拍偷拍色图| 婷婷午夜国产精品久久久| 日本少妇人妻xxxxx18| 99久久99一区二区三区| 家庭女教师中文字幕在线播放| 国产精品黄色的av| 成人国产小视频在线观看| 日本在线一区二区不卡视频| 中国熟女@视频91| 亚洲av日韩精品久久久久久hd| 免费在线看的黄片视频| 66久久久久久久久久久| 亚洲av可乐操首页| 日本人竟这样玩学生妹| 精品成人午夜免费看| 欧美在线精品一区二区三区视频 | 免费在线看的黄网站| 国产精品午夜国产小视频| 19一区二区三区在线播放| 唐人色亚洲av嫩草| 国产日韩精品电影7777| 肏插流水妹子在线乐播下载| 日韩a级精品一区二区| 性欧美激情久久久久久久| 中文字幕无码日韩专区免费| 激情人妻校园春色亚洲欧美 | 在线免费观看黄页视频| 亚洲av人人澡人人爽人人爱 | 在线观看免费视频网| 久久久精品国产亚洲AV一| av完全免费在线观看av| 欧美在线精品一区二区三区视频| 98精产国品一二三产区区别| 亚洲自拍偷拍综合色| 青青青青青青青青青国产精品视频| 午夜精品一区二区三区福利视频| 久久久久久久99精品| 日本性感美女三级视频| 深夜男人福利在线观看| 人妻少妇中文有码精品| 动漫黑丝美女的鸡巴| 天天草天天色天天干| 中文字幕高清在线免费播放| 亚洲av色香蕉一区二区三区| 99热99这里精品6国产| 亚洲成人av一区在线| 一二三中文乱码亚洲乱码one | 中英文字幕av一区| 黄色资源视频网站日韩| 欧美精品国产综合久久| 很黄很污很色的午夜网站在线观看| 日韩写真福利视频在线观看| 一区二区三区四区视频在线播放| 日本免费午夜视频网站| 黑人乱偷人妻中文字幕| 婷婷综合亚洲爱久久| 成年人该看的视频黄免费| 果冻传媒av一区二区三区| 免费无码人妻日韩精品一区二区| 天堂av在线播放免费| 国产精品入口麻豆啊啊啊| 日本少妇人妻xxxxxhd| 人妻少妇av在线观看| 国产精品久久久久国产三级试频 | 天天日天天摸天天爱| 欲满人妻中文字幕在线| 亚洲视频在线观看高清| 熟女国产一区亚洲中文字幕| 97精品综合久久在线| 91av精品视频在线| 啊啊啊想要被插进去视频| av天堂中文字幕最新| 国产自拍黄片在线观看| 亚洲国产成人在线一区| 国产精品久久久久久美女校花| 免费成人va在线观看| 日本女人一级免费片| 日韩av中文在线免费观看| 欧美少妇性一区二区三区| 91欧美在线免费观看| 亚洲国产欧美国产综合在线| 中文字幕av男人天堂| 小泽玛利亚视频在线观看| 最新国产精品网址在线观看| 亚洲的电影一区二区三区| 黄色三级网站免费下载| 成人精品视频99第一页| 中文字幕av男人天堂| 亚洲变态另类色图天堂网| brazzers欧熟精品系列| 粉嫩欧美美人妻小视频| 91久久国产成人免费网站| 国产麻豆剧传媒精品国产av蜜桃| 任你操任你干精品在线视频| 成人av中文字幕一区| 国产无遮挡裸体免费直播视频| 性欧美激情久久久久久久| 美女视频福利免费看| 国产福利小视频免费观看| 啪啪啪18禁一区二区三区| 人妻少妇av在线观看| 亚洲欧美自拍另类图片| 日本真人性生活视频免费看| 最新91精品视频在线| 国产一区二区三免费视频| 亚洲欧美综合在线探花| 夜色17s精品人妻熟女| 91国产在线视频免费观看| www天堂在线久久| rct470中文字幕在线| 91啪国自产中文字幕在线| 夜夜骑夜夜操夜夜奸| 人妻少妇中文有码精品| 国产91久久精品一区二区字幕| 国产白袜脚足J棉袜在线观看| 亚洲欧美国产麻豆综合| 国产a级毛久久久久精品| 亚洲精品 欧美日韩| 亚洲欧美色一区二区| 97精品综合久久在线| 无套猛戳丰满少妇人妻| 亚洲精品 欧美日韩| 大香蕉大香蕉在线看| 亚洲中文字幕人妻一区| 亚洲综合一区成人在线| 不卡日韩av在线观看| 69精品视频一区二区在线观看| 新婚人妻聚会被中出| 欧美视频中文一区二区三区| 性色蜜臀av一区二区三区| 在线新三级黄伊人网| 51国产成人精品视频| 东游记中文字幕版哪里可以看到| 偷拍自拍亚洲视频在线观看| 精品日产卡一卡二卡国色天香 | 经典av尤物一区二区| 久久久91蜜桃精品ad| 一区二区在线观看少妇| 深田咏美亚洲一区二区| 熟妇一区二区三区高清版| 亚洲一区久久免费视频| 日本特级片中文字幕| 日本一二三区不卡无| 国产精品福利小视频a| 91九色porny国产在线| 亚洲一区二区三区精品乱码| 一区二区三区av高清免费| 日韩精品电影亚洲一区| 沈阳熟妇28厘米大战黑人| 午夜国产福利在线观看| 爱有来生高清在线中文字幕| 2018最新中文字幕在线观看| 一级a看免费观看网站| 香蕉av影视在线观看| 欧美视频综合第一页| 国产麻豆剧果冻传媒app| 特级无码毛片免费视频播放| 啪啪啪啪啪啪啪啪av| 黄色片一级美女黄色片| 91国内精品自线在拍白富美| 大鸡吧插逼逼视频免费看| 男人和女人激情视频| 亚洲综合自拍视频一区| 国产欧美精品免费观看视频| 日本乱人一区二区三区| av在线免费资源站| 亚洲精品一线二线在线观看| 啊慢点鸡巴太大了啊舒服视频| 精品国产午夜视频一区二区| 在线免费91激情四射 | 国产黄色高清资源在线免费观看| 在线播放国产黄色av| 亚洲中文精品字幕在线观看| 国产精品自拍视频大全| 亚洲第17页国产精品| 经典亚洲伊人第一页| 中文字幕人妻一区二区视频| 一区二区三区 自拍偷拍| 亚洲男人在线天堂网| 19一区二区三区在线播放| 任我爽精品视频在线播放| 国产精品免费不卡av| 一区二区三区美女毛片| 不卡日韩av在线观看| 在线观看免费av网址大全| 男人操女人的逼免费视频| 制丝袜业一区二区三区| 天天干天天插天天谢| 亚洲最大黄 嗯色 操 啊| 精品一区二区三区三区88| 99久久成人日韩欧美精品| 免费啪啪啪在线观看视频| 亚洲欧美激情人妻偷拍| 亚洲精品欧美日韩在线播放| 自拍偷拍亚洲另类色图| 精品日产卡一卡二卡国色天香| 精品日产卡一卡二卡国色天香 | 亚洲国产欧美一区二区三区久久| 不卡日韩av在线观看| 亚洲2021av天堂| 日本高清撒尿pissing| 精品一区二区三区在线观看| 天天日夜夜操天天摸| 9久在线视频只有精品| 热思思国产99re| 91免费福利网91麻豆国产精品 | 一区二区三区美女毛片| 青青操免费日综合视频观看| 黑人巨大精品欧美视频| 51国产偷自视频在线播放| 天天操夜夜操天天操天天操| 亚洲第一伊人天堂网| 日本少妇人妻xxxxx18| 中文字幕乱码人妻电影| 日视频免费在线观看| 成人在线欧美日韩国产| av在线免费中文字幕| 亚洲国产成人无码麻豆艾秋| 欧美久久一区二区伊人| 天天插天天狠天天操| 中文字幕人妻一区二区视频| 天天色天天舔天天射天天爽| 国产精品久久久黄网站| 亚洲va国产va欧美精品88| 免费大片在线观看视频网站| 亚洲高清一区二区三区视频在线| 91免费观看在线网站| 亚洲欧美人精品高清| 亚洲激情av一区二区| 超碰97免费人妻麻豆| 青青青国产片免费观看视频| 亚洲第一伊人天堂网| 东京干手机福利视频| 亚洲一区二区三区精品乱码| av中文字幕在线导航| 欧美视频一区免费在线| 色在线观看视频免费的| 久久久久久九九99精品| 在线播放国产黄色av| 青青操免费日综合视频观看| 亚洲免费国产在线日韩| 少妇ww搡性bbb91| 亚洲青青操骚货在线视频| 唐人色亚洲av嫩草| 精品91高清在线观看| 黄色资源视频网站日韩| 日本韩国免费一区二区三区视频| 亚洲精品国产久久久久久| 中文字母永久播放1区2区3区| 激情伦理欧美日韩中文字幕| 一级黄片久久久久久久久| 欧洲国产成人精品91铁牛tv| 日本熟妇丰满厨房55| 亚洲精品麻豆免费在线观看| 亚洲一级av大片免费观看| 青青草在观免费国产精品| 9色在线视频免费观看| 免费黄页网站4188| 天天日天天干天天插舔舔| 在线免费观看黄页视频| 免费在线黄色观看网站| 中文字幕无码日韩专区免费| 福利一二三在线视频观看| 精品视频一区二区三区四区五区| 国产视频一区二区午夜| 超碰公开大香蕉97| 午夜美女少妇福利视频| 91九色国产porny蝌蚪| 亚洲av自拍偷拍综合| 97精品综合久久在线| 骚货自慰被发现爆操| 老司机免费视频网站在线看| 午夜激情精品福利视频| 大陆胖女人与丈夫操b国语高清| 少妇被强干到高潮视频在线观看 | 黄色片黄色片wyaa| 亚洲天堂av最新网址| 男女第一次视频在线观看| 超黄超污网站在线观看| 日本裸体熟妇区二区欧美| 午夜大尺度无码福利视频| 激情人妻校园春色亚洲欧美| 动漫美女的小穴视频| 午夜在线观看岛国av,com| 黄色在线观看免费观看在线| 国产欧美日韩第三页| 伊人综合aⅴ在线网| 蜜桃色婷婷久久久福利在线| av新中文天堂在线网址| 五月激情婷婷久久综合网| 一区二区三区欧美日韩高清播放| 日本人妻少妇18—xx| 狠狠的往里顶撞h百合| 国内精品在线播放第一页| 国产精品精品精品999| 亚洲国产欧美一区二区三区…| 91中文字幕免费在线观看| 天天躁日日躁狠狠躁av麻豆| av黄色成人在线观看| 大香蕉伊人国产在线| 伊人综合aⅴ在线网| 中文字幕一区二区人妻电影冢本| 日本欧美视频在线观看三区| 少妇被强干到高潮视频在线观看| 在线新三级黄伊人网| 女同性ⅹxx女同hd| 男女啪啪啪啪啪的网站| 亚洲护士一区二区三区| 国产夫妻视频在线观看免费| 亚洲精品国产综合久久久久久久久 | 亚洲天堂第一页中文字幕| 首之国产AV医生和护士小芳| 国产日韩一区二区在线看| 国产视频一区二区午夜| 色吉吉影音天天干天天操 | 视频一区二区综合精品| 99精品国产免费久久| 男大肉棒猛烈插女免费视频| 成人性爱在线看四区| 视频一区二区综合精品| 亚洲国产最大av综合| 亚洲av无码成人精品区辽| 88成人免费av网站| 天天摸天天亲天天舔天天操天天爽 | 91社福利《在线观看| www日韩毛片av| 日本午夜爽爽爽爽爽视频在线观看| 日本精品视频不卡一二三| 久久久久国产成人精品亚洲午夜| 国产精品亚洲在线观看| 91国语爽死我了不卡| 国产午夜福利av导航| 国语对白xxxx乱大交| 日本av在线一区二区三区| av在线shipin| 韩国女主播精品视频网站| 中出中文字幕在线观看| 77久久久久国产精产品| 大陆胖女人与丈夫操b国语高清 | 精品国产在线手机在线| 国产av国片精品一区二区| 综合色区亚洲熟妇shxstz| 天天干天天爱天天色| 国产精彩对白一区二区三区| 5528327男人天堂| 99精品亚洲av无码国产另类| 国产九色91在线观看精品| 18禁美女无遮挡免费| 色爱av一区二区三区| 亚洲中文字幕国产日韩| 亚洲精品乱码久久久久久密桃明| 免费看高清av的网站| 午夜在线精品偷拍一区二| 国产高清在线观看1区2区| 老司机福利精品免费视频一区二区| 内射久久久久综合网| 亚洲熟妇无码一区二区三区| sspd152中文字幕在线| 亚洲图片欧美校园春色| 国产1区,2区,3区| 亚洲av日韩精品久久久| 偷拍自拍亚洲美腿丝袜| 少妇与子乱在线观看| 天天插天天狠天天操| 国产av国片精品一区二区| 88成人免费av网站| 日韩激情文学在线视频| 国产综合高清在线观看| 真实国产乱子伦一区二区| 亚洲国产成人无码麻豆艾秋| 久久一区二区三区人妻欧美| 欧美爆乳肉感大码在线观看| 亚洲一区二区激情在线| 成人av天堂丝袜在线观看| 亚洲精品一区二区三区老狼| 黄色无码鸡吧操逼视频| 欧美久久久久久三级网| 亚洲图片欧美校园春色| 偷拍自拍视频图片免费| 亚洲一区二区三区久久午夜| 任你操视频免费在线观看| 免费一级特黄特色大片在线观看| 精品久久久久久高潮| 欧洲国产成人精品91铁牛tv| 人妻少妇一区二区三区蜜桃| 偷拍3456eee| 免费人成黄页网站在线观看国产 | 玩弄人妻熟妇性色av少妇| 北条麻妃高跟丝袜啪啪| 被大鸡吧操的好舒服视频免费| 亚洲免费va在线播放| 国产使劲操在线播放| 一区二区三区日本伦理| 一二三中文乱码亚洲乱码one| 欧美久久久久久三级网| 自拍偷拍亚洲精品第2页| 少妇人妻100系列| 亚洲国产欧美一区二区丝袜黑人 | 成人色综合中文字幕| 国产极品精品免费视频| 中文字幕熟女人妻久久久| 综合国产成人在线观看| 97资源人妻免费在线视频| 人人妻人人澡欧美91精品 | 最新91九色国产在线观看| 成人国产影院在线观看| 自拍偷拍亚洲另类色图| 午夜场射精嗯嗯啊啊视频| 久久久久久久久久久久久97| av在线播放国产不卡| 中文字幕在线视频一区二区三区| 亚洲国产40页第21页| 日本成人不卡一区二区| 日本乱人一区二区三区| 97人人妻人人澡人人爽人人精品| 亚洲av午夜免费观看| 亚洲国产精品久久久久蜜桃| 福利视频广场一区二区| 大鸡巴操b视频在线| 2021国产一区二区| 精品亚洲在线免费观看| 18禁美女黄网站色大片下载| 日本啪啪啪啪啪啪啪| 亚洲国产第一页在线观看| 成人性爱在线看四区| sw137 中文字幕 在线| 十八禁在线观看地址免费| 2020av天堂网在线观看| 超级碰碰在线视频免费观看| 欧美精品欧美极品欧美视频| 熟女少妇激情五十路| 成人30分钟免费视频| 三级av中文字幕在线观看| 绝顶痉挛大潮喷高潮无码| 成人在线欧美日韩国产| 亚洲av男人天堂久久| 非洲黑人一级特黄片| 久久精品在线观看一区二区| 午夜久久久久久久精品熟女| 999久久久久999| 青青伊人一精品视频| 五十路人妻熟女av一区二区| 精品区一区二区三区四区人妻| 欧美成人一二三在线网| 97超碰免费在线视频| 中文 成人 在线 视频| 在线国产日韩欧美视频| 九九热99视频在线观看97| 人人人妻人人澡人人| 在线免费观看靠比视频的网站| 日本熟女50视频免费| 国产成人综合一区2区| 搡老熟女一区二区在线观看| 亚洲国产成人无码麻豆艾秋| 精品91自产拍在线观看一区| 丰满少妇翘臀后进式| 亚洲欧洲一区二区在线观看| 久久久精品欧洲亚洲av| 亚洲一区二区人妻av| 97超碰免费在线视频| 视频在线亚洲一区二区| 不卡精品视频在线观看| 五十路熟女av天堂| 国产密臀av一区二区三| 欧美乱妇无乱码一区二区| 欧美色婷婷综合在线| 色哟哟在线网站入口| 天堂av中文在线最新版| 亚洲午夜电影在线观看| 最近中文字幕国产在线| 最新国产亚洲精品中文在线| 一区二区三区四区视频| 操的小逼流水的文章| 一二三中文乱码亚洲乱码one| 激情国产小视频在线| 午夜在线一区二区免费| 91精品国产观看免费| 国产 在线 免费 精品| 岛国黄色大片在线观看| 欧美亚洲少妇福利视频| 激情五月婷婷综合色啪| 92福利视频午夜1000看| 天天日天天舔天天射进去| 男人的天堂av日韩亚洲| 天天干天天操天天扣| 老司机福利精品免费视频一区二区 | 国产剧情演绎系列丝袜高跟| 免费成人va在线观看| 日韩熟女av天堂系列| 91人妻精品久久久久久久网站| 99一区二区在线观看| 日韩熟女av天堂系列| 国产麻豆乱子伦午夜视频观看| 视频 国产 精品 熟女 | 年轻的人妻被夫上司侵犯| 亚洲一级美女啪啪啪| 欧美日韩情色在线观看| 热99re69精品8在线播放| 国产精品探花熟女在线观看| 国产女人叫床高潮大片视频| 国产日韩av一区二区在线| 一区二区视频在线观看视频在线| 蜜臀av久久久久久久| 国产亚洲天堂天天一区| 国产欧美精品不卡在线| 欲满人妻中文字幕在线| 第一福利视频在线观看| 精品国产午夜视频一区二区| av日韩在线免费播放| 亚洲精品麻豆免费在线观看| 欧美久久久久久三级网| 午夜久久久久久久99| 91人妻精品一区二区久久| 2017亚洲男人天堂| 天天摸天天日天天操| 北条麻妃肉色丝袜视频| 91麻豆精品秘密入口在线观看| 中文字幕熟女人妻久久久| 亚洲成人av一区久久| 淫秽激情视频免费观看| 91chinese在线视频| 免费观看污视频网站| 1区2区3区不卡视频| 国产在线免费观看成人| 91在线视频在线精品3| 精品人人人妻人人玩日产欧| 国产福利在线视频一区| 狠狠鲁狠狠操天天晚上干干| 欧美va亚洲va天堂va| 男人天堂最新地址av| 国产精品黄色的av| 久草视频在线免播放| 天堂资源网av中文字幕| 少妇ww搡性bbb91| 东京热男人的av天堂| 国产精品一区二区三区蜜臀av| 熟女人妻一区二区精品视频| 久久精品久久精品亚洲人| 亚洲 图片 欧美 图片| 欧美 亚洲 另类综合| 99热碰碰热精品a中文| 91人妻精品一区二区久久|