長文解析:嵌入式C編程之錯誤處理(超全代碼)

嵌入式ARM 2020-12-06 00:00


前言


本文主要總結嵌入式系統C語言編程中,主要的錯誤處理方式。文中涉及的代碼運行環境如下:

一、錯誤概念


1.1 錯誤分類


從嚴重性而言,程序錯誤可分為致命性和非致命性兩類。對于致命性錯誤,無法執行恢復動作,最多只能在用戶屏幕上打印出錯消息或將其寫入日志文件,然后終止程序;而對于非致命性錯誤,多數本質上是暫時的(如資源短缺),一般恢復動作是延遲一些時間后再次嘗試。

從交互性而言,程序錯誤可分為用戶錯誤和內部錯誤兩類。用戶錯誤呈現給用戶,通常指明用戶操作上的錯誤;而程序內部錯誤呈現給程序員(可能攜帶用戶不可接觸的數據細節),用于查錯和排障。

應用程序開發者可決定恢復哪些錯誤以及如何恢復。例如,若磁盤已滿,可考慮刪除非必需或已過期的數據;若網絡連接失敗,可考慮短時間延遲后重建連接。選擇合理的錯誤恢復策略,可避免應用程序的異常終止,從而改善其健壯性。

1.2 處理步驟


錯誤處理即處理程序運行時出現的任何意外或異常情況。典型的錯誤處理包含五個步驟:

  1. 程序執行時發生軟件錯誤。該錯誤可能產生于被底層驅動或內核映射為軟件錯誤的硬件響應事件(如除零)。
  2. 以一個錯誤指示符(如整數或結構體)記錄錯誤的原因及相關信息。
  3. 程序檢測該錯誤(讀取錯誤指示符,或由其主動上報);
  4. 程序決定如何處理錯誤(忽略、部分處理或完全處理);
  5. 恢復或終止程序的執行。

上述步驟用C語言代碼表述如下:

int func(){ int bIsErrOccur = 0; //do something that might invoke errors if(bIsErrOccur) //Stage 1: error occurred return -1; //Stage 2: generate error indicator //... return 0;}
int main(void){ if(func() != 0) //Stage 3: detect error { //Stage 4: handle error } //Stage 5: recover or abort return 0;}

調用者可能希望函數返回成功時表示完全成功,失敗時程序恢復到調用前的狀態(但被調函數很難保證這點)。

二 、錯誤傳遞


2.1 返回值和回傳參數


C語言通常使用返回值來標志函數是否執行成功,調用者通過if等語句檢查該返回值以判斷函數執行情況。常見的幾種調用形式如下:

if((p = malloc(100)) == NULL) //...
if((c = getchar()) == EOF) //...
if((ticks = clock()) < 0) //...

Unix系統調用級函數(和一些老的Posix函數)的返回值有時既包括錯誤代碼也包括有用結果。因此,上述調用形式可在同一條語句中接收返回值并檢查錯誤(當執行成功時返回合法的數據值)。

返回值方式的好處是簡便和高效,但仍存在較多問題:

  1. 代碼可讀性降低

沒有返回值的函數是不可靠的。但若每個函數都具有返回值,為保持程序健壯性,就必須對每個函數進行正確性驗證,即調用時檢查其返回值。這樣,代碼中很大一部分可能花費在錯誤處理上,且排錯代碼和正常流程代碼攪在一起,比較混亂。

  1. 質量降級
條件語句相比其他類型的語句潛藏更多的錯誤。不必要的條件語句會增加排障和白盒測試的工作量。

  1. 信息有限
通過返回值只能返回一個值,因此一般只能簡單地標志成功或失敗,而無法作為獲知具體錯誤信息的手段。通過按位編碼可變通地返回多個值,但并不常用。字符串處理函數可參考IntToAscii()來返回具體的錯誤原因,并支持鏈式表達:

char *IntToAscii(int dwVal, char *pszRes, int dwRadix){ if(NULL == pszRes) return "Arg2Null";
if((dwRadix < 2) || (dwRadix > 36)) return "Arg3OutOfRange";
//... return pszRes;}

  1. 定義沖突

不同函數在成功和失敗時返回值的取值規則可能不同。例如,Unix系統調用級函數返回0代表成功,-1代表失??;新的Posix函數返回0代表成功,非0代表失?。粯藴蔆庫中isxxx函數返回1表示成功,0表示失敗。

  1. 無約束性

調用者可以忽略和丟棄返回值。未檢查和處理返回值時,程序仍然能夠運行,但結果不可預知。

新的Posix函數返回值只攜帶狀態和異常信息,并通過參數列表中的指針回傳有用的結果?;貍鲄到壎ǖ较鄳膶崊⑸?,因此調用者不可能完全忽略它們。通過回傳參數(如結構體指針)可返回多個值,也可攜帶更多的信息。

綜合返回值和回傳參數的優點,可對Get類函數采用返回值(含有用結果)方式,而對Set類函數采用返回值+回傳參數方式。對于純粹的返回值,可按需提供如下解析接口:

typedef enum{ S_OK, //成功 S_ERROR, //失敗(原因未明確),通用狀態 S_NULL_POINTER, //入參指針為NULL S_ILLEGAL_PARAM, //參數值非法,通用 S_OUT_OF_RANGE, //參數值越限 S_MAX_STATUS //不可作為返回值狀態,僅作枚舉最值使用}FUNC_STATUS;
#define RC_NAME(eRetCode) \ ((eRetCode) == S_OK ? "Success" : \ ((eRetCode) == S_ERROR ? "Failure" : \ ((eRetCode) == S_NULL_POINTER ? "NullPointer" : \ ((eRetCode) == S_ILLEGAL_PARAM ? "IllegalParas" : \ ((eRetCode) == S_OUT_OF_RANGE ? "OutOfRange" : \ "Unknown")))))

當返回值錯誤碼來自下游模塊時,可能與本模塊錯誤碼沖突。此時,建議不要將下游錯誤碼直接向上傳遞,以免引起混亂。若允許向終端或文件輸出錯誤信息,則可詳細記錄出錯現場(如函數名、錯誤描述、參數取值等),并轉換為本模塊定義的錯誤碼再向上傳遞。

2.2 全局狀態標志(errno)


Unix系統調用或某些C標準庫函數出錯時,通常返回一個負值,并設置全局整型變量errno為一個含有錯誤信息的值。例如,open函數出錯時返回-1,并設置errno為EACESS(權限不足)等值。

C標準庫頭文件<errno.h>中定義errno及其可能的非零常量取值(以字符'E'開頭)。在ANSI C中已定義一些基本的errno常量,操作系統也會擴展一部分(但其對錯誤描述仍顯匱乏)。

Linux系統中,出錯常量在errno(3)手冊頁中列出,可通過man 3 errno命令查看。除EAGAIN和EWOULDBLOCK取值相同外,POSIX.1指定的所有出錯編號取值均不同。

Posix和ISO C將errno定義為一個可修改的整型左值(lvalue),可以是包含出錯編號的一個整數,或是一個返回出錯編號指針的函數。以前使用的定義為:

extern int errno;

但在多線程環境中,多個線程共享進程地址空間,每個線程都有屬于自己的局部errno(thread-local)以避免一個線程干擾另一個線程。例如,Linux支持多線程存取errno,將其定義為:

extern int *__errno_location(void);#define errno (*__errno_location())

函數__errno_location在不同的庫版本下有不同的定義,在單線程版本中,直接返回全局變量errno的地址;而在多線程版本中,不同線程調用__errno_location返回的地址則各不相同。

C運行庫中主要在math.h(數學運算)和stdio.h(I/O操作)頭文件聲明的函數中使用errno。

使用errno時應注意以下幾點:

  1. 函數返回成功時,允許其修改errno。

例如,調用fopen函數新建文件時,內部可能會調用其他庫函數檢測是否存在同名文件。而用于檢測文件的庫函數在文件不存在時,可能會失敗并設置errno。這樣, fopen函數每次新建一個事先并不存在的文件時,即使沒有任何程序錯誤發生(fopen本身成功返回),errno也仍然可能被設置。

因此,調用庫函數時應先檢測作為錯誤指示的返回值。僅當函數返回值指明出錯時,才檢查errno值:

//調用庫函數if(返回錯誤值) //檢查errno

  1. 庫函數返回失敗時,不一定會設置errno,取決于具體的庫函數。


  2. errno在程序開始時設置為0,任何庫函數都不會將errno再次清零。
因此,在調用可能設置errno的運行庫函數之前,最好先將errno設置為0。調用失敗后再檢查errno的值。

  1. 使用errno前,應避免調用其他可能設置errno的庫函數。如:
   

if (somecall() == -1){ printf("somecall() failed\n"); if(errno == ...) { ... }}
somecall()函數出錯返回時設置errno。但當檢查errno時,其值可能已被printf()函數改變。若要正確使用somecall()函數設置的errno,須在調用printf()函數前保存其值:

if (somecall() == -1){ int dwErrSaved = errno; printf("somecall() failed\n"); if(dwErrSaved == ...) { ... }}

類似地,當在信號處理程序中調用可重入函數時,應在其前保存其后恢復errno值。

  1. 使用現代版本的C庫時,應包含使用<errno.h>頭文件;在非常老的Unix 系統中,可能沒有該頭文件,此時可手工聲明errno(如extern int errno)。
C標準定義strerror和perror兩個函數,以幫助打印錯誤信息。
   

#include <string.h>char *strerror(int errnum);

該函數將errnum(即errno值)映射為一個出錯信息字符串,并返回指向該字符串的指針??蓪⒊鲥e字符串和其它信息組合輸出到用戶界面,或保存到日志文件中,如通過fprintf(fp, "somecall failed(%s)", strerror(errno))將錯誤消息打印到fp指向的文件中。

perror函數將當前errno對應的錯誤消息的字符串輸出到標準錯誤(即stderr或2)上。

#include <stdio.h>void perror(const char *msg);

該函數首先輸出由msg指向的字符串(用戶自己定義的信息),后面緊跟一個冒號和空格,然后是當前errno值對應的錯誤類型描述,最后是一個換行符。未使用重定向時,該函數輸出到控制臺上;若將標準錯誤輸出重定向到/dev/null,則看不到任何輸出。

注意,perror()函數中errno對應的錯誤消息集合與strerror()相同。但后者可提供更多定位信息和輸出方式。

兩個函數的用法示例如下:

int main(int argc, char** argv){ errno = 0; FILE *pFile = fopen(argv[1], "r"); if(NULL == pFile) { printf("Cannot open file '%s'(%s)!\n", argv[1], strerror(errno)); perror("Open file failed"); } else { printf("Open file '%s'(%s)!\n", argv[1], strerror(errno)); perror("Open file"); fclose(pFile); }
return 0;}

執行結果為:

[wangxiaoyuan_@localhost test1]$ ./GlbErr /sdb1/wangxiaoyuan/linux_test/test1/test.cOpen file '/sdb1/wangxiaoyuan/linux_test/test1/test.c'(Success)!Open file: Success[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.hCannot open file 'NonexistentFile.h'(No such file or directory)!Open file failed: No such file or directory[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.h > testOpen file failed: No such file or directory[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.h 2> testCannot open file 'NonexistentFile.h'(No such file or directory)!

也可仿照errno的定義和處理,定制自己的錯誤代碼:

int *_fpErrNo(void){ static int dwLocalErrNo = 0; return &dwLocalErrNo;}
#define ErrNo (*_fpErrNo())#define EOUTOFRANGE 1//define other error macros...
int Callee(void){ ErrNo = 1; return -1;}
int main(void){ ErrNo = 0; if((-1 == Callee()) && (EOUTOFRANGE == ErrNo)) printf("Callee failed(ErrNo:%d)!\n", ErrNo); return 0;}

借助全局狀態標志,可充分利用函數的接口(返回值和參數表)。但與返回值一樣,它隱含地要求調用者在調用函數后檢查該標志,而這種約束同樣脆弱。

此外,全局狀態標志存在重用和覆蓋的風險。而函數返回值是無名的臨時變量,由函數產生且只能被調用者訪問。調用完成后即可檢查或拷貝返回值,然后原始的返回對象將消失而不能被重用。又因為無名,返回值不能被覆蓋。

2.3 局部跳轉(goto)


使用goto語句可直接跳轉到函數內的錯誤處理代碼處。以除零錯誤為例:

double Division(double fDividend, double fDivisor){ return fDividend/fDivisor;}int main(void){ int dwFlag = 0; if(1 == dwFlag) { RaiseException: printf("The divisor cannot be 0!\n"); exit(1); } dwFlag = 1;
double fDividend = 0.0, fDivisor = 0.0; printf("Enter the dividend: "); scanf("%lf", &fDividend); printf("Enter the divisor : "); scanf("%lf", &fDivisor); if(0 == fDivisor) //不太嚴謹的浮點數判0比較 goto RaiseException; printf("The quotient is %.2lf\n", Division(fDividend, fDivisor));
return 0;}

執行結果如下:

[wangxiaoyuan_@localhost test1]$ ./testEnter the dividend: 10Enter the divisor : 0The divisor cannot be 0![wangxiaoyuan_@localhost test1]$ ./testEnter the dividend: 10Enter the divisor : 2The quotient is 5.00雖然goto語句會破壞代碼結構性,但卻非常適用于集中錯誤處理。偽代碼示例如下:CallerFunc(){ if((ret = CalleeFunc1()) < 0); goto ErrHandle; if((ret = CalleeFunc2()) < 0); goto ErrHandle; if((ret = CalleeFunc3()) < 0); goto ErrHandle; //...
return;
ErrHandle: //Handle Error(e.g. printf) return;}

2.4 非局部跳轉(setjmp/longjmp)


局部goto語句只能跳到所在函數內部的標號上。若要跨越函數跳轉,需要借助標準C庫提供非局部跳轉函數setjmp()和longjmp()。它們分別承擔非局部標號和goto的作用,非常適用于處理發生在深層嵌套函數調用中的出錯情況。“非局部跳轉”是在棧上跳過若干調用幀,返回到當前函數調用路徑上的某個函數內。

#include <setjmp.h>int setjmp(jmp_buf env);void longjmp(jmp_buf env,int val);

函數setjmp()將程序運行時的當前系統堆棧環境保存在緩沖區env結構中。初次調用該函數時返回值為0。longjmp()函數根據setjmp()所保存的env結構恢復先前的堆棧環境,即“跳回”先前調用setjmp時的程序執行點。此時,setjmp()函數返回longjmp()函數所設置的參數val值,程序將繼續執行setjmp調用后的下一條語句(仿佛從未離開setjmp)。參數val為非0值,若設置為0,則setjmp()函數返回1。
可見,setjmp()有兩類返回值,用于區分是首次直接調用(返回0)和還是由其他地方跳轉而來(返回非0值)。對于一個setjmp可有多個longjmp,因此可由不同的非0返回值區分這些longjmp。

舉個簡單例子說明 setjmp/longjmp的非局部跳轉:

jmp_buf gJmpBuf;void Func1(){ printf("Enter Func1\n"); if(0)longjmp(gJmpBuf, 1);}void Func2(){ printf("Enter Func2\n"); if(0)longjmp(gJmpBuf, 2);}void Func3(){ printf("Enter Func3\n"); if(1)longjmp(gJmpBuf, 3);}
int main(void){ int dwJmpRet = setjmp(gJmpBuf); printf("dwJmpRet = %d\n", dwJmpRet); if(0 == dwJmpRet) { Func1(); Func2(); Func3(); } else { switch(dwJmpRet) { case 1: printf("Jump back from Func1\n"); break; case 2: printf("Jump back from Func2\n"); break; case 3: printf("Jump back from Func3\n"); break; default: printf("Unknown Func!\n"); break; } } return 0;}

執行結果為:

dwJmpRet = 0Enter Func1Enter Func2Enter Func3dwJmpRet = 3Jump back from Func3
當setjmp/longjmp嵌在單個函數中使用時,可模擬PASCAL語言中嵌套函數定義(即函數內中定義一個局部函數)。當setjmp/longjmp跨越函數使用時,可模擬面向對象語言中的異常(exception) 機制。

模擬異常機制時,首先通過setjmp()函數設置一個跳轉點并保存返回現場,然后使用try塊包含那些可能出現錯誤的代碼。可在try塊代碼中或其調用的函數內,通過longjmp()函數拋出(throw)異常。拋出異常后,將跳回setjmp()函數所設置的跳轉點并執行catch塊所包含的異常處理程序。

以除零錯誤為例:

jmp_buf gJmpBuf;void RaiseException(void){ printf("Exception is raised: "); longjmp(gJmpBuf, 1); //throw,跳轉至異常處理代碼 printf("This line should never get printed!\n");}double Division(double fDividend, double fDivisor){ return fDividend/fDivisor;}int main(void){ double fDividend = 0.0, fDivisor = 0.0; printf("Enter the dividend: "); scanf("%lf", &fDividend); printf("Enter the divisor : "); if(0 == setjmp(gJmpBuf)) //try塊 { scanf("%lf", &fDivisor); if(0 == fDivisor) //也可將該判斷及RaiseException置于Division內 RaiseException(); printf("The quotient is %.2lf\n", Division(fDividend, fDivisor)); } else //catch塊(異常處理代碼) { printf("The divisor cannot be 0!\n"); }
return 0;}

執行結果為:

Enter the dividend: 10Enter the divisor : 0Exception is raised: The divisor cannot be 0!

通過組合使用setjmp/longjmp函數,可對復雜程序中可能出現的異常進行集中處理。根據longjmp()函數所傳遞的返回值來區分處理各種不同的異常。

使用setjmp/longjmp函數時應注意以下幾點:

  1. 必須先調用setjmp()函數后調用longjmp()函數,以恢復到先前被保存的程序執行點。若調用順序相反,將導致程序的執行流變得不可預測,很容易導致程序崩潰。
  2. longjmp()函數必須在setjmp()函數的作用域之內。在調用setjmp()函數時,它保存的程序執行點環境只在當前主調函數作用域以內(或以后)有效。若主調函數返回或退出到上層(或更上層)的函數環境中,則setjmp()函數所保存的程序環境也隨之失效(函數返回時堆棧內存失效)。這就要求setjmp()不可該封裝在一個函數中,若要封裝則必須使用宏(詳見《C語言接口與實現》“第4章 異常與斷言”)。
  3. 通常將jmp_buf變量定義為全局變量,以便跨函數調用longjmp。
  4. 通常,存放在存儲器中的變量將具有longjmp時的值,而在CPU和浮點寄存器中的變量則恢復為調用setjmp時的值。因此,若在調用setjmp和longjmp之間修改自動變量或寄存器變量的值,當setjmp從longjmp調用返回時,變量將維持修改后的值。若要編寫使用非局部跳轉的可移植程序,必須使用volatile屬性。
  5. 使用異常機制不必每次調用都檢查一次返回值,但因為程序中任何位置都可能拋出異常,必須時刻考慮是否捕捉異常。在大型程序中,判斷是否捕捉異常會是很大的思維負擔,影響開發效率。相比之下,通過返回值指示錯誤有利于調用者在最近出錯的地方進行檢查。此外,返回值模式中程序的運行順序一目了然,對維護者可讀性更高。因此,應用程序中不建議使用setjmp/longjmp“異常處理”機制(除非庫或框架)。

2.5 信號(signal/raise)

在某些情況下,主機環境或操作系統可能發出信號(signal)事件,指示特定的編程錯誤或嚴重事件(如除0或中斷等)。這些信號本意并非用于錯誤捕獲,而是指示與正常程序流不協調的外部事件。

為處理信號,需要使用以下信號相關函數:

#include <signal.h>typedef void (*fpSigFunc)(int);fpSigFunc signal(int signo, fpSigFunc fpHandler);int raise(int signo);

其中,參數signo是Unix系統定義的信號編號(正整數),不允許用戶自定義信號。參數fpHandler是常量SIG_DFL、常量SIG_IGN或當接收到此信號后要調用的信號處理函數(signal handler)的地址。若指定SIG_DFL,則接收到此信號后調用系統的缺省處理函數;若指定SIG_ IGN,則向內核表明忽略此信號(SIGKILL和SIGSTOP不可忽略)。某些異常信號(如除數為零)不太可能恢復,此時信號處理函數可在程序終止前正確地清理某些資源。信號處理函數所收到的異常信息僅是一個整數(待處理的信號事件),這點與setjmp()函數類似。

signal()函數執行成功時返回前次掛接的處理函數地址,失敗時則返回SIG_ERR。信號通過調用raise()函數產生并被處理函數捕獲。

以除零錯誤為例:

void fphandler(int dwSigNo){ printf("Exception is raised, dwSigNo=%d!\n", dwSigNo);}int main(void){ if(SIG_ERR == signal(SIGFPE, fphandler)) { fprintf(stderr, "Fail to set SIGFPE handler!\n"); exit(EXIT_FAILURE); }
double fDividend = 10.0, fDivisor = 0.0; if(0 == fDivisor) { raise(SIGFPE); exit(EXIT_FAILURE); } printf("The quotient is %.2lf\n", fDividend/fDivisor);
return 0;}

執行結果為"Exception is raised, dwSigNo=8!"(0.0不等同于0,因此系統未檢測到浮點異常)。

若將被除數(Dividend)和除數(Divisor)改為整型變量:

int main(void){ if(SIG_ERR == signal(SIGFPE, fphandler)) { fprintf(stderr, "Fail to set SIGFPE handler!\n"); exit(EXIT_FAILURE); }
int dwDividend = 10, dwDivisor = 0; double fQuotient = dwDividend/dwDivisor; printf("The quotient is %.2lf\n", fQuotient);
return 0;}

則執行后循環輸出"Exception is raised, dwSigNo=8!"。這是因為進程捕捉到信號并對其進行處理時,進程正在執行的指令序列被信號處理程序臨時中斷,它首先執行該信號處理程序中的指令。若從信號處理程序返回(未調用exit或longjmp),則繼續執行在捕捉到信號時進程正在執行的正常指令序列。因此,每次系統調用信號處理函數后,異常控制流還會返回除0指令繼續執行。而除0異常不可恢復,導致反復輸出異常。

規避方法有兩種:

  1. 將SIGFPE信號變成系統默認處理,即signal(SIGFPE, SIG_DFL)。
此時執行輸出為"Floating point exception"。
  1. 利用setjmp/longjmp跳過引發異常的指令:
   

jmp_buf gJmpBuf;void fphandler(int dwSigNo){ printf("Exception is raised, dwSigNo=%d!\n", dwSigNo); longjmp(gJmpBuf, 1);}int main(void){ if(SIG_ERR == signal(SIGFPE, SIG_DFL)) { fprintf(stderr, "Fail to set SIGFPE handler!\n"); exit(EXIT_FAILURE); }
int dwDividend = 10, dwDivisor = 0; if(0 == setjmp(gJmpBuf)) { double fQuotient = dwDividend/dwDivisor; printf("The quotient is %.2lf\n", fQuotient); } else { printf("The divisor cannot be 0!\n"); }
return 0;}

注意,在信號處理程序中還可使用sigsetjmp/siglongjmp函數進行非局部跳轉。相比setjmp函數,sigsetjmp函數增加一個信號屏蔽字參數。

三  錯誤處理


3.1 終止(abort/exit)


致命性錯誤無法恢復,只能終止程序。例如,當空閑堆管理程序無法提供可用的連續空間時(調用malloc返回NULL),用戶程序的健壯性將嚴重受損。若恢復的可能性渺茫,則最好終止或重啟程序。

標準C庫提供exit()和abort()函數,分別用于程序正常終止和異常終止。兩者都不會返回到調用者中,且都導致程序被強行結束。

exit()及其相似函數原型聲明如下:

#include <stdlib.h>void exit(int status);void _Exit(int status);#include <unistd.h>void _exit(int status);
其中,exit和_Exit由ISO C說明,而_exit由Posix.1說明。因此使用不同的頭文件。
ISO C定義_Exit旨在為進程提供一種無需運行終止處理程序(exit handler)或信號處理程序(signal handler)而終止的方法,是否沖洗標準I/O流則取決于實現。Unix系統中_Exit 和_exit同義,兩者均直接進入內核,而不沖洗標準I/O流。_exit函數由exit調用,處理Unix特定的細節。

exit()函數首先調用執行各終止處理程序,然后按需多次調用fclose函數關閉所有已打開的標準I/O流(將所有緩沖的輸出數據沖洗寫到文件上),然后調用_exit函數進入內核。

標準函數庫中有一種“緩沖I/O(buffered I/O)”機制。該機制對于每個打開的文件,在內存中維護一片緩沖區。每次讀文件時會連續讀出若干條記錄,下次讀文件時就可直接從內存緩沖區中讀??;每次寫文件時也僅僅寫入內存緩沖區,等滿足一定條件(如緩沖區填滿,或遇到換行符等特定字符)時再將緩沖區內容一次性寫入文件。
通過盡可能減少read和write調用的次數,該機制可顯著提高文件讀寫速度,但也給編程帶來某些麻煩。例如,向文件內寫入一些數據時,若未滿足特定條件,數據會暫存在緩沖區內。開發者并不知曉這點,而調用_ exit()函數直接關閉進程,導致緩沖區數據丟失。因此,若要保證數據完整性,必須調用exit()函數,或在調用 _exit()函數前先通過fflush()函數將緩沖區內容寫入指定的文件。

例如,調用printf函數(遇到換行符'\n'時自動讀出緩沖區中內容)函數后再調用exit:

int main(void){ printf("Using exit...\n"); printf("This is the content in buffer"); exit(0); printf("This line will never be reached\n");}

執行輸出為:

Using exit...This is the content in buffer(結尾無換行符)

調用printf函數后再調用_exit:

int main(void){ printf("Using _exit...\n"); printf("This is the content in buffer"); fprintf(stdout, "Standard output stream"); fprintf(stderr, "Standard error stream"); //fflush(stdout); _exit(0);}

執行輸出為:

Using _exit...Standard error stream(結尾無換行符)

若取消fflush句注釋,則執行輸出為:

Using _exit...Standard error streamThis is the content in bufferStandard output stream(結尾無換行符)

通常,標準錯誤是不帶緩沖的,打開至終端設備的流(如標準輸入和標準輸出)是行緩沖的(遇換行符則執行I/O操作);其他所有流則是全緩沖的(填滿標準I/O緩沖區后才執行I/O操作)。

三個exit函數都帶有一個整型參數status,稱之為終止狀態(或退出狀態)。該參數取值通常為兩個宏,即EXIT_SUCCESS(0)和EXIT_FAILURE(1)。大多數Unix shell都可檢查進程的終止狀態。若(a)調用這些函數時不帶終止狀態,或(b)main函數執行了無返回值的return語句,或(c) main函數未聲明返回類型為整型,則該進程的終止狀態未定義。但若main函數的返回類型為整型,且執行到最后一條語句時返回(隱式返回),則該進程的終止狀態為0。

exit系列函數是最簡單直接的錯誤處理方式,但程序出錯終止時無法捕獲異常信息。ISO C規定一個進程可以注冊32個終止處理函數。這些函數可編寫為自定義的清理代碼,將由exit()函數自動調用,并可使用atexit()函數進行注冊。

#include <stdlib.h>int atexit(void (*func)(void));

該函數的參數是一個無參數無返回值的終止處理函數。exit()函數按注冊的相反順序調用這些函數。同一函數若注冊多次,則被調用多次。即使不調用exit函數,程序退出時也會執行atexit注冊的函數。

通過結合exit()和atexit()函數,可在程序出錯終止時拋出異常信息。以除零錯誤為例:

double Division(double fDividend, double fDivisor){ return fDividend/fDivisor;}void RaiseException1(void){ printf("Exception is raised: \n");}void RaiseException2(void){ printf("The divisor cannot be 0!\n");}
int main(void){ double fDividend = 0.0, fDivisor = 0.0; printf("Enter the dividend: "); scanf("%lf", &fDividend); printf("Enter the divisor : "); scanf("%lf", &fDivisor); if(0 == fDivisor) { atexit(RaiseException2); atexit(RaiseException1); exit(EXIT_FAILURE); } printf("The quotient is %.2lf\n", Division(fDividend, fDivisor));
return 0;}

執行結果為:

Enter the dividend: 10Enter the divisor : 0Exception is raised: The divisor cannot be 0!

注意,通過atexit()注冊的終止處理函數必須顯式(使用return語句)或隱式地正常返回,而不能通過調用exit()或longjmp()等其他方式終止,否則將導致未定義的行為。例如,在GCC4.1.2編譯環境下,調用exit()終止時仍等效于正常返回;而VC6.0編譯環境下,調用exit()的處理函數將阻止其他已注冊的處理函數被調用,并且可能導致程序異常終止甚至崩潰。

嵌套調用exit()函數將導致未定義的行為,因此在終止處理函數或信號處理函數中盡量不要調用exit()。

abort()函數原型聲明如下:

#include <stdlib.h>void abort(void);
該函數將SIGABRT信號發送給調用進程(進程不應忽略此信號)。
ISO C規定,調用abort將向主機環境遞送一個未成功終止的通知,其方法是調用raise(SIGABRT)函數。因此,abort()函數理論上的實現為:

void abort(void){ raise(SIGABRT); exit(EXIT_FAILURE);}
   

可見,即使捕捉到SIGABRT信號且相應信號處理程序返回,abort()函數仍然終止程序。Posix.1也說明abort()函數并不理會進程對此信號的阻塞和忽略。

進程捕捉到SIGABRT信號后,可在其終止之前執行所需的清理操作(如調用exit)。若進程不在信號處理程序中終止自己,Posix.1聲明當信號處理程序返回時,abort()函數終止該進程。

ISO C規定,abort()函數是否沖洗輸出流、關閉已打 開文件及刪除臨時文件由實現決定。Posix.1則要求若abort()函數終止進程,則它對所有打開標準I/O流的效果應當與進程終止前對每個流調用fclose相同。為提高可移植性,若希望沖洗標準I/O流,則應在調用abort()之前執行這種操作。

3.2 斷言(assert)


abort()和exit()函數無條件終止程序。也可使用斷言(assert)有條件地終止程序。
assert是診斷調試程序時經常使用的宏,定義在<assert.h>內。該宏的典型實現如下:

#ifdef NDEBUG #define assert(expr) ((void) 0)#else extern void __assert((const char *, const char *, int, const char *)); #define assert(expr) \ ((void) ((expr) || \ (__assert(#expr, __FILE__, __LINE__, __FUNCTION__), 0)))#endif

可見,assert宏僅在調試版本(未定義NDEBUG)中有效,且調用__assert()函數。該函數將輸出發生錯誤的文件名、代碼行、函數名以及條件表達式:

void __assert(const char *assertion, const char * filename, int linenumber, register const char * function){ fprintf(stderr, " [%s(%d)%s] Assertion '%s' failed.\n", filename, linenumber, ((function == NULL) ? "UnknownFunc" : function), assertion); abort();}

   

因此,assert宏實際上是一個帶有錯誤說明信息的abort(),并做了前提條件檢查。若檢查失敗(斷言表達式為邏輯假),則報告錯誤并終止程序;否則繼續執行后面的語句。

使用者也可按需定制assert宏。例如,另一實現版本為:

#undef assert#ifdef NDEBUG #define assert(expr) ((void) 0)#else #define assert(expr) ((void) ((expr) || \ (fprintf(stderr, "[%s(%d)] Assertion '%s' failed.\n", \ __FILE__, __LINE__, #expr), abort(), 0)))#endif
   

注意,expr1||expr2表達式作為單獨語句出現時,等效于條件語句if(!(expr1))expr2。這樣,assert宏就可擴展為一個表達式,而不是一條語句。逗號表達式expr2返回最后一個表達式的值(即0),以符合||操作符的要求。

使用斷言時應注意以下幾點:

  1. 斷言用于檢測理論上絕不應該出現的情況,如入參指針為空、除數為0等。
對比以下兩種情況:
   

char *Strcpy(char *pszDst, const char *pszSrc){ char *pszDstOrig = pszDst; assert((pszDst != NULL) && (pszSrc != NULL)); while((*pszDst++ = *pszSrc++) != '\0'); return pszDstOrig;}FILE *OpenFile(const char *pszName, const char *pszMode){ FILE *pFile = fopen(pszName, pszMode); assert(pFile != NULL); if(NULL == pFile) return NULL;
//... return pFile;}

Strcpy()函數中斷言使用正確,因為入參字符串指針不應為空。OpenFile()函數中則不能使用斷言,因為用戶可能需要檢查某個文件是否存在,而這并非錯誤或異常。

2)assert是宏不是函數,在調試版本和非調試版本中行為不同。因此必須確保斷言表達式的求值不會產生副作用,如修改變量和改變方法的返回值。不過,可根據這一副作用測試斷言是否打開:

int main(void){ int dwChg = 0; assert(dwChg = 1); if(0 == dwChg) printf("Assertion should be enabled!\n"); return 0;}

  1. 不應使用斷言檢查公共方法的參數(應使用參數校驗代碼),但可用于檢查傳遞給私有方法的參數。
  2. 可使用斷言測試方法執行的前置條件和后置條件,以及執行前后的不變性。
  3. 斷言條件不成立時,會調用abort()函數終止程序,應用程序沒有機會做清理工作(如關閉文件和數據庫)。

3.3 封裝

為減少錯誤檢查和處理代碼的重復性,可對函數調用或錯誤輸出進行封裝。
  1. 封裝具有錯誤返回值的函數
通常針對頻繁調用的基礎性系統函數,如內存和內核對象操作等。舉例如下:
   

pid_t Fork(void) //首字母大寫,以區分系統函數fork(){ pid_t pid; if((pid = fork())<0) { fprintf(stderr, "Fork error: %s\n", strerror(errno)); exit(0); } return pid;}

Fork()函數出錯退出時依賴系統清理資源。若還需清理其他資源(如已創建的臨時文件),可增加一個負責清理的回調函數。
注意,并非所有系統函數都可封裝,應根據具體業務邏輯確定。
  1. 封裝錯誤輸出
通常需要使用ISO C變長參數表特性。例如《Unix網絡編程》中將輸出至標準出錯文件的代碼封裝如下:

#include <stdarg.h>#include <syslog.h>#define HAVE_VSNPRINTF 1#define MAXLINE 4096 /* max text line length */int daemon_proc; /* set nonzero by daemon_init() */static void err_doit(int errnoflag, int level, const char * fmt, va_list ap){ int errno_save, n; char buf[MAXLINE + 1];
errno_save = errno; /* Value caller might want printed. */#ifdef HAVE_VSNPRINTF vsnprintf(buf, MAXLINE, fmt, ap);#else vsprintf(buf, fmt, ap); /* This is not safe */#endif n = strlen(buf); if (errnoflag) { snprintf(buf + n, MAXLINE - n, ": %s", strerror(errno_save)); } strcat(buf, "\n");
if (daemon_proc) { syslog(level, buf); } else { fflush(stdout); /* In case stdout and stderr are the same */ fputs(buf, stderr); fflush(stderr); }
return;}
void err_ret(const char * fmt, ...){ va_list ap;
va_start(ap, fmt); err_doit(1, LOG_INFO, fmt, ap); va_end(ap);
return;}

END

作者:clover-toeic

原文:https://www.cnblogs.com/clover-toeic/p/3919857.html


版權歸原作者所有,如有侵權,請聯系刪除。

推薦閱讀

飛機上一般是什么操作系統?

高速CAN、容錯CAN、LIN總線有什么區別?

大佬終于把鴻蒙OS講明白了,收藏了!


嵌入式ARM 關注這個時代最火的嵌入式ARM,你想知道的都在這里。
評論
  • 做儲能項目,你是怎么選控制器的?隨著工商業儲能、電網側儲能的規模化落地,作為儲能EMS“大腦”的嵌入式控制器,直接決定了整套系統的運行穩定性、數據可靠性與項目落地效率。不少從業者都踩過類似的坑:接口數量不足要額外加裝擴展模塊、戶外高溫導致設備宕機、電磁干擾引發數據丟包、軟件生態薄弱拖慢開發周期……選對儲能控制器,核心要盯準四個關鍵維度。首先看接口能力:全鏈路覆蓋,才是降本的核心。一套完整的儲能系統,需要同時對接PCS、BMS、智能電表、溫濕度傳感器、除濕機、煙感消防主機,還要連接急停開關、斷路器
    飛凌嵌入式 2026-08-11 08:46 220瀏覽
  •        很多人只見過封裝在SMD外殼里的小尺寸晶振,卻不知道一顆2016、1612甚至1210封裝的微型石英晶振,從一塊巴掌大的石英原礦,到最終能穩定起振的成品,要經過十幾道精密工序。它內部的石英晶片厚度最薄不到20微米,比普通A4紙的1/5還薄,整個生產過程里哪怕0.1微米的誤差,都會直接廢掉整批產品,這也是小尺寸晶振過去長期依賴進口,國內廠商花了十幾年才啃下量產硬骨頭的核心原因。1. 原石定向切割:0.01度的角度誤差都不能有小尺寸晶振的第一步,
    TKD泰晶科技 2026-08-07 08:43 574瀏覽
  • 摘要東莞市恒創連接器有限公司(HCHGCONN)是位于廣東東莞的連接器源頭制造工廠,專注于連接器國產替代、小間距大電流連接器、線對板連接器、線對線連接器、板對板連接器、電源連接器及定制連接器開發。公司面向工業自動化、新能源儲能、BMS、汽車電子、AI服務器、GPU供電、通信設備、醫療設備和智能家居等場景,提供從產品選型、替代評估、樣品驗證到批量交付的一站式連接方案。恒創可支持 Molex、TE Connectivity、JST、Hirose、Samtec 等國際品牌部分系列的國產替代評估。常見替
    HC003 2026-08-07 10:14 265瀏覽
  • 做電池供電攝像頭、可視門鈴、戶外安防的同學應該都踩過這個坑:攝像頭要拍,ISP、sensor、無線模塊全得開著,整機電流動輒幾百 mA。一節 18650 撐不了幾天,用戶投訴“怎么又沒電了”。有人會說:加個 PIR 人體紅外不就行了?問題是 PIR 怕熱、怕寵物、怕隔著亞克力/玻璃殼,而且只能感知“有溫差的人體移動”,靈敏度飄忽。真正省電的思路應該是:讓攝像頭平時徹底睡死,只有“前面有人/有動靜”時才被一鍵喚醒——而負責這個“看門”角色的,最合適的是一顆低功耗雷達。一、為什么是雷達,而不是 PI
    唯創知音語音芯片 2026-08-07 14:44 261瀏覽
  • 一、方案簡介在Buildroot構建的嵌入式Linux系統中部署Flutter應用,主流有兩套成熟的開源方案,分別適配不同的顯示場景與開發需求:方案一:flutter-elinux(Sony開源方案)由索尼官方維護的嵌入式Linux Flutter工具鏈,既支持DRM/GBM直接渲染,也兼容Wayland、X11等多種顯示后端,場景適配性更廣。方案二:flutter-pi(輕量級嵌入器)一款輕量級Flutter引擎宿主程序,僅基于底層DRM/GBM接口實現渲染,無需依賴桌面環境,資源占用更低,更
    飛凌嵌入式 2026-08-07 09:34 248瀏覽
  • 做藍牙音箱的工程師大概都碰過這個局面。產品要連手機放歌,要本地播開機提示音和按鍵音,還要接 App 做配網和狀態上報。三件事拆開,早先的常規做法是音頻藍牙一顆料、數傳藍牙一顆料、本地播放再掛一顆小 MCU 加一片 Flash。板子越畫越大,BOM 越算越貴,待機電流和固件工作量也跟著漲。WT2605C 出來以后,這套需求可以集成到一顆芯片,就是 WT2605C-32N,該芯片采用QFN32 封裝,4×4 毫米,把音頻藍牙、BLE 數傳和 MP3 本地播放三樣全包了。對藍牙音箱來說,最實在的好處就
    唯創知音語音芯片 2026-08-06 15:14 427瀏覽
  • 在物聯網從“萬物互聯”邁向“萬物智聯”的進程中,LTE Cat.1憑借其在速率、成本與網絡覆蓋之間的完美平衡,正成為中低速物聯網市場的絕對主力。隨著2G/3G網絡的逐步退網以及新國標對終端設備智能化、定位功能的強制要求,Cat.1模組迎來了爆發式的增長窗口。在這一關鍵節點,基于華為海思Hi2131芯片設計的LTE Cat.1模組,憑借其在能效、射頻、架構及物理規格上的底層創新,成功打破了“高性能必高功耗”的傳統魔咒,為行業提供了極具競爭力的連接最優解。核心技術優勢:重塑Cat.1能效與連接標桿海
    用戶1778981989717 2026-08-05 21:04 214瀏覽
  • 文:杜杰編輯:侯煜當地時間7月28日,美國特朗普政府落地最新科技封鎖政策,美國聯邦通信委員會(FCC)正式更新《受管制清單》,新增兩大類設備——境外生產的先進機器人設備、境外生產的聯網電力逆變器。FCC在官方情況說明書中明確,該類境外制造產品,“會對美國國家安全或美國民眾的人身安全構成無法承受的風險”。本次新規設置明確豁免機制:經美國國防部(DoW)、電力逆變器品類經美國國土安全部(DHS)審核并授予“有條件批準”,認定對應設備品類不存在重大安全風險,即可獲得準入豁免。同時政策嚴格執行新老劃斷原
    華爾街科技眼 2026-08-03 21:28 376瀏覽
  •                                                                        
    廣州鐵金剛 2026-08-06 17:44 229558瀏覽
  • 樓宇自控里的“巴別塔”一棟智慧樓宇、一個產業園區里,暖通空調運行著廠商私有協議,照明系統遵循專屬通信標準,變配電、門禁安防各成體系——設備間數據壁壘森嚴,上位平臺對接全靠定制化 “翻譯” 開發,不僅成本高企,交付周期也難以管控。這就是樓宇自控行業多年的痛點:協議林立、封閉林立。而破局的鑰匙,早在30多年前就已經寫好了——BACnet。作為ASHRAE制定的樓宇自控國際標準協議,BACnet是如今全球樓宇自控系統(BAS)互聯互通的“通用語言”。新風機、冷源群控、變配電、照明、門禁,凡是需要被樓宇
    飛凌嵌入式 2026-08-10 09:26 302瀏覽
  • 導語在現代高速數字電路設計中,信號的完整性已不僅是理想波形推演,而是一場與物理非理想特性正面博弈的工程實踐。濾波器芯片作為凈化傳輸鏈路的關鍵屏障,其核心挑戰在于:能否有效識別并濾除那些隱匿在電平跳變過程中的亞納秒級瞬態脈沖干擾,這類干擾持續極短,卻足以在高速鏈路中引發誤觸發。然而,傳統函數發生器受限于采樣率與邊沿速率,根本無法忠實復現如此狹窄且陡峭的異常波形。針對此困境,德思特TS-AWG7000系列任意波形發生器以17 GS/s采樣率與50 ps原生邊沿速度,為瞬態脈沖提供精細刻畫,配合SPG
    德思特測試測量 2026-08-05 14:06 5瀏覽
  • 讓微弱脈沖信號更清晰——鎖相放大器集成Boxcar平均器在脈沖激光、泵浦-探測、時間分辨光譜、飛行時間質譜等實驗中,真正需要測量的信號往往只出現在一個很短的時間窗口內,卻被淹沒在遠大于它的噪聲和背景之中。 針對這類穩定重復的微弱脈沖與瞬態信號,賽恩科儀為OE2052雙通道數字鎖相放大器帶來全新的Boxcar Averager功能升級:在鎖相檢測之外,進一步提供從周期波形觀察、門控測量到多周期平均的完整數字Boxcar測量鏈路。 Boxcar平均器,如何從噪聲中“截取”目標信號?Boxcar平均器
    SSI_賽恩科儀 2026-08-07 15:49 47216瀏覽
  • 產品使用18V供電,配套4串鋰離子電池,需要設計一個充電器。因為輸入18V,電池最高 16.8V,壓差僅 1.2V。因為壓差太小。使用開關型的充電控制芯片不穩定。嘗試自行設計一個。整體采用分立三極管 + TL431架構,分4個功能單元:使能控制回路、限流環路、TL431 精密恒壓反饋環路、PNP 功率管主功率輸出回路;實現完整鋰電池充電邏輯。使能控制回路按鍵按下:5V 電壓經 R12 (2kΩ) 送入 NPN 管 Q3 基極,Q3 飽和導通,對地拉低 R1、R2 組成的分壓控制節點電位;該節點直
    southcreek 2026-08-07 13:45 525瀏覽
我要評論
0
點擊右上角,分享到朋友圈 我知道啦
請使用瀏覽器分享功能 我知道啦
主站蜘蛛池模板: 一念永恒52集完整版大结局| 牌坊下的女人插曲| 缘分的天空动漫免费观看全集| 墨白电视剧免费观看在线观看超清| 甜蜜惩罚我是专门看守所宠物真人| 《推油》完整版免费观看| 你微信时很美电视剧免费观看| 白峰电影全集免费全集| 电视剧富贵| 电影平果| 参谋不带长| 《逆爱》1-24集全| 坠落的女律师在线观看完整版| 猿人星球纪录片| 韩国电影失足美女色诱猎物| 《安塔芮丝》| 京圈太子爷短剧免费观看| 布鲁克读大学| 朋友的姐姐2016| 封神演义评书| 南木林县| 恶魔幸存者第二季动漫| 《新人类毒医肥尸》在线观看 | 《和讨厌的部长出差旅》电影| 法国空少免费观看| 仙界羡慕:魔尊娇妻全集免费| 拆弹专家2免费观看完整版国语| 加勒比女海盗1| 周杰伦林俊杰合唱稻香| COATWEST| 兰越峰事件| 五妹影视剧大全免费观看黑暗荣耀| 沉迷公5全集在线观看免费| 获取信息的方法| 女扮男装去上学短剧全集| 姐姐的朋友2在线| 聊斋志异三| 杨敏思版| 魔界王子第二季| 唐诡奇谭在线播放免费观看| 金毛犬段景住| 美国派4:美国重逢| 肃宁县| 东北往事2之黑道风云20年| 蒙古国电影《销售女孩》上映时间| 芭比彩虹仙子之美人鱼公主 | 八岁萝莉喊爸爸 喜提校花当老婆| 刚之炼金师| 公之手1| 特朗普与哈里斯首辩倒计时| 酒店1至50集免费观看| 食锈末世录| 廊桥遗梦免费观看| 黄秋生电影人肉叉烧包| 平凡之路在线观看| 坎贝奇无憾全集在线看| 杜拉拉升职记 电影| 《咱们结婚吧》电视剧免费观看| 特殊的房产销售| 挣扎吧亚当君| 以父之名| 和部长出差电影| 刺客伍六七之暗影宿命免费观看| 小悦悦是谁| 苍老师40部免费观看电视剧大全| 猎狼者 电视剧| 《忘年恋曲》续集| 火口山的两个人| 黑白配电影免费高清在线观看国语| 泰山电影| 神马四兄弟电影| 公肉奉公HD中字| 电视剧凤凰牡丹| 急诊护士电影完整版在线观看| 恋爱暴君2动漫| 顶楼第三季在哪看| 加勒比海盗 电影| 天龙八部电视剧全集| 女子护卫队满天星1973电影| 囧妈在线播放高清| 和女部长一起去出差旅电影叫什么名字 | 超级快递| 箭在弦上电视剧1一48集免费观看| 神话电视剧排行榜| 新萧十一郎| 昆明小矮人村| 满天星版《荣誉守则》在线| 老婆11免费完整高清电视| 成人导航在线观看| 饥渴的邻居HD| 长空之王电影| 《法国瑜伽》在线观看| 契魔者装备| 黑白配国语版免费观看| 绝对权力在线播放全集| 凯帕克主演的电影在线观看| 苍兰诀在线观看| 手指假肢| 逆转女王剧情| 极寒之城免费完整版在线观看| 《落魄贵族琉璃川》2集免费观看| 高清《天气之子》电影免费看| 我爱几何| 海边旅店| 我们都是坏孩子高清| 神断狄仁杰第4部| 免费怀旧短剧| 怒火重案| 未知传闻| 速度与激情10完整版免费| 我们结婚了20101016| 糖果世界大冒险| 决战黎明全集| 成全视频观看免费高清动漫第8季在线| 规章制度的作用| 超人气学园电视剧| 新孟丽君传奇| 新喜剧之王在线观看| 突然的喜欢演员表| 和山田进行lv999的恋爱| 九首歌完整版在线看| 当着老公的面被搬运工在线| 坎贝奇品味人生完整的 | 欧美战狼6电影免费完整版| 苏炳添说对不起大家| 美国版本女儿国电影| 《关于爱》在线观看免费| 大姨姐在线观看| 谁说直男掰不弯全本小说| 惊天营救2| 方子传完整版| 无我原非你| 爸爸的种子高清免费版电视剧需要爸 | 爱我几何高清版在线播放| 央媒评2.2亿彩票事件| 小泽彻维修工的艳遇| 腹黑总裁:只对我温柔全集免费| 忍不住的继拇中字3| 电影《补课》免费观看中文版| 春闺梦里人在线观看完整版| 磁力链最佳的搜索引擎| 人到中年电视剧全集| 战狼9免费高清999加拿大版在线观看 | 霸王花4| 娃娃脸4完整版免费观看| 北上电视剧在线观看全集免费播放| 女销售的秘密2韩剧三级| 我们高清在线观看免费观看| 欢喜游龙| 李白被贺知章称为什么| 东北告别团队2| 特殊理发店待遇8的最新章节和内容| 高清《原谅我》在线观看| 销冠的秘密2| 电影太太度假时| LX司令第一季动漫免费观看| 不输男儿:将门嫡女短剧全集| 做aj的电视剧大全免费观看英国| 大岛优子快播| 电影《棘手狂情》在线观看| 花与蛇在线看| 诱人的私人瑜伽教练| 湘北小成| 法国空姐八部曲系列| 洪武大案下载| 爱在春天在线观看| 需要一半留下一半打一字谜| 韩剧电影免费在线播放| 东北富婆25分钟对白| 每个夜晚我们都有人站出来| 周星驰电影粤语版| 黑帮大佬和我的365天完整| 安福县| 与凤行电视剧在线观看完整版免费| 布兰迪《军事不当行为》2016免费播放| 越狱第一季| 我的继拇5| 冷血人狼1994无删减完整版粤语| 诱感| 禁忌5完整版在线看中文| 电影双女任务免费看| 华政韩剧| 插曲的痛第60集全集免费播放做爱小视频| 扎导正义联盟| 婚前恐惧症症状| 三分野电视剧西瓜免费观看全集 | 老板的要求免费观看| 校园2013韩剧| 木马责罚| 你们老公晚上怎样玩你| 中国诗词大会第六季第十场总决赛| 追光的日子电视剧免费观看| 墨西哥电影《瓜达卢佩的玫瑰》的女| 新济公活佛下部| 邵氏《金莲外传》中文在线观看 | 电影《分娩按摩2》在线看| 谢娜《龙拳》舞台被吐槽用力过猛| 启示电影在线观看完整版| 好运全集观看| 《朋友夫妻:交换3| 楼顶上的大象| 热力公司怼居民称凭啥给你热下去| 睡觉身体下边突然很舒服| 冲上云霄国语| 最开放的表演12点表演| 销售的销售秘密3hd中字| 女性| 无所畏惧电视剧免费| lx司令动漫第一季免费观看| 影子爱人电影| 爱我几何电影在线观看免费完整版 在线播放 | 等你爱我mv| 天黑请闭眼电视剧| 寒战高清完整版| 洪文定三破白莲教| 龙珠z国语版| 《悲惨遭遇》1980年法国| 雪中悍刀行全集在线观看| 瓜达卢佩的玫瑰墨西哥完整版| 发电俏娇娃在线观看 | 日本3天已连震5次| 法国空乘9特别版| 超级教师电视剧免费观看高清| 与部长妻子一起过周末的日子| 好先生 电视剧| 电影需要爸爸播种子屠夫电影网| 神机妙算刘伯温全集| 女版《壮志凌云》| 魔发奇缘动漫| 需要妈妈的种子| 黑暗荣耀电视剧| 《女学生的滋味》| 《神奇女侠》免费观看| 义姐是不良妈妈免费观看| 简言的夏冬全集免费播放| 电视剧妻子免费观看| 局长趴在雪白的身上耸动| 《部长出差的日子》2| 异形:夺命舰 电影| 女超人麦乐迪免费在线观看| 电影网站在线| 免费观看爱我几何| 红海行动手机 在线观看| 女员工的5付出中字| 红豆女之恋| 四个贵妇精油按摩在线观看| 致命弯道3在线观看| 美丽的小瘦子7| 龙年图片壁纸| 《朋友夫妻:交换4》韩国| 邻家女孩百度影音| 法国空姐3免费高清原声满天星观看 | 一千种死法第五季| 少年包青天三| 私人航空法国成人电影| 小猪佩奇的动画片| 换脸明星电影完整版在线观看 | 《壮志凌云女版美国满天星欧美| 年轻的母亲在线观看中文版完整版| 需要爸爸播种孑伦理电影| 丽江小倩歌曲| 荒岛女儿国| 缬怎么读| 满天星电影在线| 一笑随歌40集全免费播放| 赤板栗《牙医姐妹》完整| 魔幻时刻| 离婚前规则 主题曲| 赤子板栗牙医在线观看| 高清沧元图 前传:东宁府的夏天| k8s经典版播放| 一帘幽梦 陶虹| 《意大利夫人》| 余生有涯在线观看免费全集| 《妻子5》免费高清电视剧| 密爱 优酷| 房奴试爱电视剧开头原声| 电影爱情大全免费观看| 水手服饰育1982年| 快乐星球第5部| 隐身帽子韩国电影| 古剑奇谭第十集| 朝国继4| 电影《蜜桃成熟时33d》| 小猪佩奇第9季中文版| 阿盖尔神秘特工免费高清观看| 街头篮球中锋| 租界少年热血档案| 妻子的背叛动漫第1季| 82板杨敏思版本1-5电视剧在线| 怒海狂蛛| 神秘的发型屋韩国 | 莫言被踢出作协| 美式保罗2| 权力的游戏第一季免费观看完整 | 泰剧心影国语版| 守望爱情| 日冕之恋| 驯龙高手1普通话版免费| 需要爸爸来播种 在线播放| 世界奇闻怪事图片| 正在播放: DRPT-048 春药曝露性爱 仓田真央带乳头导线与前女友相见 十年来同 | 斗罗大陆119集免费观看 | 安娜卡列尼娜在线观看| 外卖小哥是隐藏大佬短剧全集| 翻唱日本歌曲| 金石良缘剧情介绍| 电影偶遇| 电影上司的老婆双字ID| 姨母辅导高清版| 《v酒店》第二季系列| 盟约电影免费完整版在线观看 | 《借妻》完整版观看| 女战士满天星免费观看完整版| 功夫战警电视剧免费观看完整版| 《拳击手》满天星1997罗莎| 宝宝巴士小苹果| 亲爱的妈妈3在线韩国免费观看| 鬼吹灯怒晴湘西| 邵氏电影大全国语版| 春原未来| 快乐之家1980意大利版| 《花与蛇9之终极阴谋》| 火凤凰女子特种兵| 裸体按摩电影| 十二生肖电视剧| 坎贝奇电影《无憾》在线观看中文| 心灵捕手 电影| 小小少女16| 进化之实动漫在线观看| 美国版《灭火宝贝1》| 需要爸爸播种子2美国电影第二部 战狼6免费观看完整版国语 | 高清蒙特利尔,我的美人| 法国空姐2023(多塞尔航空) | 扫黑风暴12集| 麻生香织黑肌泣BD在线| 命运交响曲电视剧免费观看| 啄木鸟《壮志凌云》片段| 海岛救护队| 需要爸爸播种在美国在线| 粉色大白菜(法国)完整板| 电影《补课》免费观看中文版| 高清原以为大家不以貌取人| 老家门前唱大戏| 新妈妈2在线| 爱我几何未删减完整版| 温柔的搜子2高清韩国| 护士凯登电影| 修理工的艳遇法国| 公主她不装了:摊牌是大佬短剧全集| 芹叶铁线莲| 任宏恩哭坟| 丹麦电影《祖母情日记》| 西班牙电影《掌中之物》在线播放| 从红月开始动漫免费| 琉璃免费观看电视剧西瓜| 隐形帽子在线观看| 你比星光美丽电视剧免费观看最新 | 危险人物粤语| 法国空乘11完整版在线看| 我和我的祖国电影在线观看免费| 进化之实动漫在线观看| 五通神魔大战三只狐妖| 还有明天韩剧| 三生三世枕上书全集免费观看| 电影《需要播种》在线看| 扫黑风暴连续剧| 三年成全免费观看在线播放| 3根一起会坏掉的好疼| 六年级毕业考试分数| 我的美女姐姐欧美。| 妓女的荣耀| 正义联盟扎导剪辑版在线观看| 暴力拆迁| 废柴天才:召唤超神短剧全集| 武松蒋玲玲| 你微笑时很美全集| 极情从林| 高清《死人开关》电影| 森林里的熊先生无删节完整版| 一眉道长国语高清| 寄生兽电影迅雷下载| Overflower在线观看| 浴火之路在线观看| 大象传谋| 青春向前冲电视剧免费观看| 与凤行电视剧全40集免费观看| 电影误杀在线观看完整版| 《美容院:特殊待遇》在线| 新生电视剧在线观看免费完整版| 需要爸爸的种孑| 当年明月难寻短剧免费观看| 不知火舞vs外星人之h| 《北越暴行》免费观看| 让子弹飞未删版| 星莱克尔最经典电影| 高校教师成熟在线播放视频| 一闪一闪亮星星在线观看| 9|在线观看免费高清电视剧狂飙| 中国青年我和我的青春| 绝情电视剧| 南京照相馆在线观看| 保险员2朴银狐角色| 小离别电视剧免费全集观看| 动漫xlx| 《合租房的激情》在线观看| 《酒店激情1-5集在线观看| 穿靴子的猫第二季国语版| 祥仔实觉.av 电影大片| 舒淇主演《灵与欲》免费| 韩国伦理水电工 | 菠萝蜜视频免费观看| 徐若瑄 av| 木子檀檀子在线观看全集免费| 婚前试爱国语版| 盛唐风流电影在线观看|电影免费高清| 黄晓明和宋慧乔演过情侣| 假面骑士时王vs帝骑外传| 巴黎恋歌| 斗罗大陆免费完整观看204集| 浴火之路在线观看| 风水传人:看相改运我最行短剧全集| 爱的阶梯国语版| 男女愁愁愁全集哪里看| 永乐大帝电视剧全集免费观看 | 淄博烧烤小店每日卖上万烤串| 战狼6免费观看在线成人版| 泷泽萝拉第二部字幕| 总会再相见全集观看| 电影 非诚勿扰2| 三客电影| 姐妹牙医 在线观看完整版| 女忍者忍法帖2| 美味快递员特殊待遇2| 薛平贵与王宝钏47| 香格里拉 电视剧| 苏菲玛索狂野的爱| 人鱼小姐国语版在线观看免费全集| 男人女人愁愁愁免费看电视剧 | 海军陆战队员| 雷政富12秒完整版| 女保险公司推销员4 | 母亲吃中药母乳喂养婴儿核酸转阴 | 二次曝光未测减120分| 开封奇案| 捉鬼敢死队美国满天星| 真假学园| 冯德伦太极| 农民伯伯下乡国语版吴健视频| 北原多香子无打码在线观看 | 路由器是什么样子的| 壮志凌云电影女版| 新天师斗僵尸2| 女版捉鬼敢死队| 欲望爱人在线播放| 电影《外出》在线观看| 妈妈的职业2hb| 高清《英雄》电影完整版| 用你的手指扰乱我| 马头琴曲| 诚实好媳妇韩语中文谐音| 花子VS倔强驱魔师1-4集在在线观看 | 古墓丽影山寨版免费观看 | 意外 电影| 女超人-麦乐迪版| 鬼吹灯之龙王五太子墓| 明星大侦探第三季| 京城绯闻电视剧| 《OverFlower》无码| 声音的战争导师| 共和县| 千年血战篇第二季在线观看| 女版战狼10国语版免费播放下载| 热带雨 完整版时长| 欧美电影满天星萨迪萨默斯| 新水浒传 高清| 三位大学位按摩记的更新内容和角色 | 假面骑士ooo剧场版| 聚会的目的在线观看完整版迅雷星辰| 凶城计中计粤语| 电影大兵的寝室美剧免费观看全集| 教授和他的女大学生| 美丽小蜜蜂5大结局| 观音坐莲电影普通话版免费观看| 东京复仇者真人版在线观看| 影视工厂tv电视版下载| 电影独臂刀王| 美丽水蜜蜂第一季免费观看| 青盲电视剧全集50| 孤单又灿烂的神-鬼怪 百度云 | 中国农村夫妻CHINA| 继女要用爸爸的种子| 杨幂函数| 海之声完整版973| 死神来了5免费观看完整版| 神医喜来乐2| 瓦克利护士电影完整版| 妖精的尾巴160| 义姐是| 《我的砍价女王》电视剧| 《四大美人杨贵妃》高清| 《瑜伽的狂热》动漫在线观看| 风起的日子笑看落花| 色九月在线| 桃花运电视剧全集观看| 公孚之手9| 一出好戏电影完整版免费观看 | 壮志凌云2011满天星观看 | 女仆小姐的贪吃日常全集观看| 站着再来一次免费视频哆来咪影院 | 美乃雀赎罪电影哪里看 | 父亲鼓励满天星国语| 天与地港剧| 女扮男装去上学全集免费| 《刺青》| 台湾骂人的话| 凤冈县| 喜爱夜蒲2在线电影高清| 学生的妈妈5字ID| 马腾你别走未删减| 谁说直男掰不弯全本小说| 加勒比海盗成人版免费看| 贤妻良母| 为食神探粤语| 壮硕军警猛男高H| 咒怨1日版| 桑德拉·拉索《啄木鸟》| 电影我有钱了在线观看完整视频| 火箭军副司令吴国华昨晚自尽为什么 | 南北兄弟| 如果一生有你陪| 《花与蛇·1》| 灵异之城第一季| 烂滚夫斗烂滚妻国语| 坎贝奇《无憾》在线观看| 湖南卫视风云| 电影《财阀夫人》免费观看| 公主和妓女星克莱尔版在线观看| 黄泉的使者电视剧| 痴迷电影俄剧在线观看| 我的小姨全集在线观看| 御岭秘录电影免费观看| 特殊客房满天星| 萨顶顶快去歌手顶一顶| 重逢再说我爱你免费全集| 韩剧《激战三姊妹》在线观看 | 吉安县| 青春草免费在线视频| 甩葱舞蹈视频原版| 哇嘎哇嘎哇嘎在线观看| 腹黑总裁:独宠一人全集免费| 拆弹专家2免费观看完整版| 《无憾》完整在线观看免费版坎贝奇| 九零创业:商业巨头全集免费| 百炼成神动漫在线播放| 很想很想你电视剧免费| 高清《我的娜塔莎》电视剧| 伊波拉病毒2(普通话)| 狂飙2022电视剧全集免费播放| 国家宝藏1在线观看| 美少女战士免费观看| 好久不见42集免费播放| 日本电影出轨维修工| 最美的安排| 电视剧食神| 《人世间》免费完整版| 女保镖电视剧| 太子妃武:武林高手短剧全集 | 兰坪| jizz日本在线| 雪中悍刀行50集免费观看2季| 湖北省| 澳门风云电影国语| 烧饼皇后| 需要爸爸的播放美国电影| 许我耀眼电视剧更新到几集了| 双肥临门| 蜜桃8| 神马四兄弟电影| 大鹏《保你平安》在线观看| 私人女子| 疾风知劲草下一句| 无敌县令全集| 需要爸爸播种子中字完整版| 权力的游戏第一季免费观看完整| 高清至尊马蒂未删减| 木下檀檩子在线观看免费完整版电影 | 韩国维修工大尺度电影免费观看| 二龙湖爱情故事2免费看| 大染坊续| 粉红大白菜在线观看电视剧免费 | 擦枪走火 电影| 加勒比女海盗在线| 我的瑜伽老师在线观看完整版| 契约宠妻:总裁超会撩全集免费 | 《兽行日寇》完整版| 皮囊之下在线观看| 《黑白配》电影| 日剧华丽一族| 孤独的美食家第五季| 倚天屠龙记大结局| 全红婵蒙特利尔决赛回放| 神龙赌圣| 朝国继拇中字| 九九电视剧视频免费观看| 我的性感小阿姨| 要爸爸播种子电影| 《黑白决》免费观看| 植物学家的女儿们| 琴书罗成算卦| 韩剧我的野蛮王妃| 收获之夜百度影音| 知否知否应是绿肥红瘦 晋江| 以家人之名更新| 女友的妈妈韩字的ID是谁| 平泽里菜子| 凶城计中计粤语| 我的健身锻炼免费观看| 二次大战启示录| 哈利波特与火焰杯国语版| 男人和女人一起愁愁愁电视剧免费观op | 护理学院的故事法国满天星免费观看| 黄子华唔黐线唔正常| 央视反腐大片| 《屋顶工》| 军事不当行为电影院| 女教师引诱12| 麦乐迪《家庭矛盾》在线观看| 特站先锋| 龙蛇争霸| 味道男女| 苍井空电影网| 电视剧潜龙行动| 十日谈电影完整版免费观看| 老公我要电影免费观看| 宫锁玉心| 入室暴行电影在线看| 大宋少年志1免费观看| 电影《美容院的特殊待遇》纹身男演| 二人转短剧不孝儿女| 敌人英语| 奔跑吧兄弟第四季最强者| 3d肉蒲团图片| 慢性呕吐实验室原版| 高清《年轻的女仆》韩国电影| 酒店1-90集连续剧| 老炮儿在线| 春风定风波全集免费播放在线观看| 《overflower》动漫全集 | 半是蜜糖半是伤电视剧免费观看| 千指大人| 俘虏之锁未删减完整版第3集| 儿子媳妇中字头| 双重曝光韩剧免费观看完整版| 千金难逃| 甄嬛传20集| 奔跑吧第5季| 我是谁免费| 花木兰传奇电视剧免费观看| 《房屋销售员》免费观看| 王楚钦回应怒怼粉丝事件| 甜心派电视剧泰剧无删减在线观看| 潜江市| 哪吒之魔童降免费观看| 以吾之名在线观看| 漂亮的的瘦子4| 非诚勿扰聂倩| 世界杯巴西对克罗地亚| 女超人麦乐迪完整版资源| 我愿意 孙红雷| 欢乐颂1免费观看全集电视剧完整版 | 战狼6在线播放免费观看| 天师钟馗之美丽传说| 灭门惨案 孽杀| 安娜的妈妈偷看儿子手机是哪一集| 巜温泉欺辱人妻动漫在线 | 公之浮手电影免费播放| 我们的热恋电视剧| 我家三爷超宠的全集视频| 二手房| 大学生女学生HD| 圣斗士星矢黄金魂| 美好的时光电视剧在线播放| 簧瑟片| 薄冰电视剧免费观看| 孤勇歌曲原唱完整版陈奕迅| 美版灭火宝贝2监狱| 正月十六的讲究| 唇亡齿寒电影| 高压监狱在线观看完整免费高清原声满天星奔跑吧式叫庆 | 爱本无罪| 法国女子高压监狱完整版在线观看完整免费高清原声 | 我在华丽的外出截了一段视频| 阁楼上的情人未删减版| 漂亮的保姆韩国电影观看免费版 | 白上之黑免费观看方法| 电视剧咱们结婚吧免费观看| 腹黑总裁:只对我温柔全集免费| 法国版《女超人:麦乐迪》电影在线观| 成人h单机游戏| 离婚惊艳:全城羡慕短剧全集| 少帅传奇48集电视剧| 牙医免费在线观看| 两个人在线观看免费完整版| 雨果 百度影音| 麦乐迪在线观看完整版| 女子炸弹队| 因纽特人繁衍纪录片| 捍卫者联盟| 郝板栗《千金小姐》无删减全集 | 甄嬛传电视剧全集完整版免费观看| 本乡爱电影在线观看免费最新版大全| 舒淇玉女心经| 从结婚开始恋爱免费播放电视剧| 插曲的痛30集全播放免费下载| 全红婵蒙特利尔决赛回放| 冯巩个人简历简介| 朱迅的葬礼现场照片| 生活中的玛丽电影全部| 伸出你的手| 甘联珠大破红莲寺| 睫毛膏1983在线观看完整版| 大笑江湖优酷| 五华山事件| 《入室暴行3被蹂躏》| 李思思离职央视后迎来综艺首秀| 儿童歌曲快乐的节日| 客户名单电影| 阿拉伯电影绝色在线观看免费| 电视剧红玫瑰黑玫瑰| 曾小贤头像| 老公的瑜伽老师| 她唇之下电影无删减资源| 上海滩简谱| 插曲30全集免费观看| 初恋逆袭:竹马别来无恙短剧全集| 致命弯道5完整版在线观看视频| 《渔夫的荒野史记》剧情解析视频| 安姨《房屋售楼员》电影完整在线观| 小小周正面曝光| 杨丽花歌仔戏薛丁山| 皋兰县| 与部长出差中文电影| 武汉三镇主帅考虑执教国足 | 高清阿尔法未删减| 棒棒TV| 《法国空乘4》电影| 狂飙免费观看完整电视剧| 日常卿卿| 《部长出差的日子》中字| 肉欲伦JIAN| 团圆饭电视剧全集| 门源| 《被下药强辱的妻子》免费观看 | 苹果范冰冰电影完整| 急诊医生神木丽在线观看全集免费| 电影在线观看电影完整版高清免费| 碧血青天杨家将| 睫毛膏4美国版中文版| 公主恋人ova| 隋唐英雄未删减版全集| 诱她短剧在线全集免费| 瓜达卢佩的玫瑰(原版)在线播放| 鹦鹉女神无修版| 寂静岭2电影| 锵锵锵锵锵免费完整观看| 电影《劳拉》| 《汤摇庄的幽奈同学》| 变形计2017| 男生女生一起相愁愁愁电视剧在线观观| 亲爱的妈妈是5在线观看完整| 战马在线观看| 从指尖传出的热情三季未增减| 《她被搬运工侵犯在线观看| 电影奇门遁甲在线观看免费完整版| 饥饿游戏2 星火燎原电影| 公主她不装了:摊牌是大佬短剧全集| 韩国电影四个已婚妇女去按摩| 五十度飞| 三目童子国语版| 朝鲜战争 王树增| 美国电影美丽小樱桃免费观看| 电影《野兽暗夜》完整版观看| 一起来看流星雨18| 宙斯之子赫拉克勒斯电影| 木下檀檩子《女婿》在线| 浴血坚持| 真爱谎言下载| 高清致命录像带:万圣节未删减| 男女一起愁愁愁电影全集免费播放| 不穿内裤的女教师在线观看| 嫡女宅斗:全精通短剧全集| 激情犯罪电影| 妻子8在线观看免费版电视剧| 诈欺游戏3| 决战黎明全集| 龙珠gt| 《灭火行动》美国在线播放| 晨晨农村的事VLOG| 《妻子3》免费高清电视剧| 少年酒吧豪掷30万| 妻子对不起丈夫| 六指琴魔在线观看完整版免费| 黑白通吃:都市狂龙全集免费| 理发店里的特殊待遇4电影| 我主沉浮电视剧| 金牌月嫂狂甩女婴| 舒克贝塔动画片全集| 铿锵锵锵锵免费观看| 斗罗大陆2绝世唐门第一季| 新审死官 电视剧| 《瑜伽教练》无删减| 大邱庄卖钢管女视频完整版| 灌篮高手电影版2022| 长河落日在线电视剧免费观看| 人海中遇见你mv| 美味快递员特殊待遇2在线观看| 我和丰满老师疯狂做爰漫画| 父母爱情全集免费观看| 乌什县| 和讨厌的部长一起去出差旅电影在线观看| 正是青春璀璨时| 夫前被强行侵犯在线观看| 愁愁愁全集免费观看| 张赫的老婆| 电车痴汉小说| 《特殊面试2》| 永时影院| 《丈夫叫妻子陪上司睡HD | 《梦幻军事基地》免费观看| 非诚勿扰 小德| 我在精神病院学斩神动漫| 单身即地狱综艺在线观看第四期| 需要爸爸播种子电视剧免费观看| 欲望中的女人在线观看无删减| 疯狂的有夫之妇| 旋风小子粤语| 日本电影牙医姐妹在线观看| 郭麒麟平凡之路| 嫂嫂三角裤湿湿| 塔罗牌在线观看| 飞机上的性K8服务| 《洪武大案》大结局| 漂亮的保姆中文字幕完整版 | 《夫债妻偿》在线观看全集| 3d肉蒲团女演员| 阿德龙大酒店| 憎侣之夜| Totakka hayyini kirguzux| 贞观之治所有演员表| 蓝可儿电梯事件|