嵌入式C編程之錯誤處理(附代碼例子)

嵌入式大雜燴 2020-11-26 00:00

作者:clover-toeic

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

前言

本文主要總結嵌入式系統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代表失敗;標準C庫中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值)映射為一個出錯信息字符串,并返回指向該字符串的指針。可將出錯字符串和其它信息組合輸出到用戶界面,或保存到日志文件中,如通過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.c
Open file '/sdb1/wangxiaoyuan/linux_test/test1/test.c'(Success)!
Open file: Success
[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.h
Cannot open file 'NonexistentFile.h'(No such file or directory)!
Open file failed: No such file or directory
[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.h > test
Open file failed: No such file or directory
[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.h 2> test
Cannot 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]$ ./test
Enter the dividend: 10
Enter the divisor : 0
The divisor cannot be 0!
[wangxiaoyuan_@localhost test1]$ ./test
Enter the dividend: 10
Enter the divisor : 2
The 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 = 0
Enter Func1
Enter Func2
Enter Func3
dwJmpRet = 3
Jump 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: 10
Enter the divisor : 0
Exception 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: 10
Enter the divisor : 0
Exception 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 *, intconst 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;
}


免責聲明:本文來源網絡,免費傳達知識,版權歸原作者所有。如涉及作品版權問題,請聯系我進行刪除。

最后

以上就是本次的分享,如果覺得文章不錯,轉發、在看,也是我們繼續更新得動力。

猜你喜歡:

一個不該被遺忘的打印輸出函數

為什么Linux內核里大量使用goto,而很多書籍卻不提倡使用?

1024G 嵌入式資源大放送!包括但不限于C/C++、單片機、Linux等。在公眾號聊天界面回復1024,即可免費獲取!


嵌入式大雜燴 專注于嵌入式技術,包括但不限于C/C++、嵌入式、物聯網、Linux等編程學習筆記,同時,內包含大量的學習資源。歡迎關注,一同交流學習,共同進步!
評論
  • 做電池供電攝像頭、可視門鈴、戶外安防的同學應該都踩過這個坑:攝像頭要拍,ISP、sensor、無線模塊全得開著,整機電流動輒幾百 mA。一節 18650 撐不了幾天,用戶投訴“怎么又沒電了”。有人會說:加個 PIR 人體紅外不就行了?問題是 PIR 怕熱、怕寵物、怕隔著亞克力/玻璃殼,而且只能感知“有溫差的人體移動”,靈敏度飄忽。真正省電的思路應該是:讓攝像頭平時徹底睡死,只有“前面有人/有動靜”時才被一鍵喚醒——而負責這個“看門”角色的,最合適的是一顆低功耗雷達。一、為什么是雷達,而不是 PI
    唯創知音語音芯片 2026-08-07 14:44 262瀏覽
  • 產品使用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 528瀏覽
  • 樓宇自控里的“巴別塔”一棟智慧樓宇、一個產業園區里,暖通空調運行著廠商私有協議,照明系統遵循專屬通信標準,變配電、門禁安防各成體系——設備間數據壁壘森嚴,上位平臺對接全靠定制化 “翻譯” 開發,不僅成本高企,交付周期也難以管控。這就是樓宇自控行業多年的痛點:協議林立、封閉林立。而破局的鑰匙,早在30多年前就已經寫好了——BACnet。作為ASHRAE制定的樓宇自控國際標準協議,BACnet是如今全球樓宇自控系統(BAS)互聯互通的“通用語言”。新風機、冷源群控、變配電、照明、門禁,凡是需要被樓宇
    飛凌嵌入式 2026-08-10 09:26 306瀏覽
  •                                                                        
    廣州鐵金剛 2026-08-06 17:44 230837瀏覽
  • 做儲能項目,你是怎么選控制器的?隨著工商業儲能、電網側儲能的規模化落地,作為儲能EMS“大腦”的嵌入式控制器,直接決定了整套系統的運行穩定性、數據可靠性與項目落地效率。不少從業者都踩過類似的坑:接口數量不足要額外加裝擴展模塊、戶外高溫導致設備宕機、電磁干擾引發數據丟包、軟件生態薄弱拖慢開發周期……選對儲能控制器,核心要盯準四個關鍵維度。首先看接口能力:全鏈路覆蓋,才是降本的核心。一套完整的儲能系統,需要同時對接PCS、BMS、智能電表、溫濕度傳感器、除濕機、煙感消防主機,還要連接急停開關、斷路器
    飛凌嵌入式 2026-08-11 08:46 229瀏覽
  • 摘要東莞市恒創連接器有限公司(HCHGCONN)是位于廣東東莞的連接器源頭制造工廠,專注于連接器國產替代、小間距大電流連接器、線對板連接器、線對線連接器、板對板連接器、電源連接器及定制連接器開發。公司面向工業自動化、新能源儲能、BMS、汽車電子、AI服務器、GPU供電、通信設備、醫療設備和智能家居等場景,提供從產品選型、替代評估、樣品驗證到批量交付的一站式連接方案。恒創可支持 Molex、TE Connectivity、JST、Hirose、Samtec 等國際品牌部分系列的國產替代評估。常見替
    HC003 2026-08-07 10:14 269瀏覽
  • 在物聯網從“萬物互聯”邁向“萬物智聯”的進程中,LTE Cat.1憑借其在速率、成本與網絡覆蓋之間的完美平衡,正成為中低速物聯網市場的絕對主力。隨著2G/3G網絡的逐步退網以及新國標對終端設備智能化、定位功能的強制要求,Cat.1模組迎來了爆發式的增長窗口。在這一關鍵節點,基于華為海思Hi2131芯片設計的LTE Cat.1模組,憑借其在能效、射頻、架構及物理規格上的底層創新,成功打破了“高性能必高功耗”的傳統魔咒,為行業提供了極具競爭力的連接最優解。核心技術優勢:重塑Cat.1能效與連接標桿海
    用戶1778981989717 2026-08-05 21:04 214瀏覽
  • 讓微弱脈沖信號更清晰——鎖相放大器集成Boxcar平均器在脈沖激光、泵浦-探測、時間分辨光譜、飛行時間質譜等實驗中,真正需要測量的信號往往只出現在一個很短的時間窗口內,卻被淹沒在遠大于它的噪聲和背景之中。 針對這類穩定重復的微弱脈沖與瞬態信號,賽恩科儀為OE2052雙通道數字鎖相放大器帶來全新的Boxcar Averager功能升級:在鎖相檢測之外,進一步提供從周期波形觀察、門控測量到多周期平均的完整數字Boxcar測量鏈路。 Boxcar平均器,如何從噪聲中“截取”目標信號?Boxcar平均器
    SSI_賽恩科儀 2026-08-07 15:49 50292瀏覽
  • 做藍牙音箱的工程師大概都碰過這個局面。產品要連手機放歌,要本地播開機提示音和按鍵音,還要接 App 做配網和狀態上報。三件事拆開,早先的常規做法是音頻藍牙一顆料、數傳藍牙一顆料、本地播放再掛一顆小 MCU 加一片 Flash。板子越畫越大,BOM 越算越貴,待機電流和固件工作量也跟著漲。WT2605C 出來以后,這套需求可以集成到一顆芯片,就是 WT2605C-32N,該芯片采用QFN32 封裝,4×4 毫米,把音頻藍牙、BLE 數傳和 MP3 本地播放三樣全包了。對藍牙音箱來說,最實在的好處就
    唯創知音語音芯片 2026-08-06 15:14 427瀏覽
  • 一、方案簡介在Buildroot構建的嵌入式Linux系統中部署Flutter應用,主流有兩套成熟的開源方案,分別適配不同的顯示場景與開發需求:方案一:flutter-elinux(Sony開源方案)由索尼官方維護的嵌入式Linux Flutter工具鏈,既支持DRM/GBM直接渲染,也兼容Wayland、X11等多種顯示后端,場景適配性更廣。方案二:flutter-pi(輕量級嵌入器)一款輕量級Flutter引擎宿主程序,僅基于底層DRM/GBM接口實現渲染,無需依賴桌面環境,資源占用更低,更
    飛凌嵌入式 2026-08-07 09:34 251瀏覽
  •        很多人只見過封裝在SMD外殼里的小尺寸晶振,卻不知道一顆2016、1612甚至1210封裝的微型石英晶振,從一塊巴掌大的石英原礦,到最終能穩定起振的成品,要經過十幾道精密工序。它內部的石英晶片厚度最薄不到20微米,比普通A4紙的1/5還薄,整個生產過程里哪怕0.1微米的誤差,都會直接廢掉整批產品,這也是小尺寸晶振過去長期依賴進口,國內廠商花了十幾年才啃下量產硬骨頭的核心原因。1. 原石定向切割:0.01度的角度誤差都不能有小尺寸晶振的第一步,
    TKD泰晶科技 2026-08-07 08:43 576瀏覽
我要評論
0
點擊右上角,分享到朋友圈 我知道啦
請使用瀏覽器分享功能 我知道啦
主站蜘蛛池模板: 《唐朝诡事录2》| 虐恋情深全集免费| 高清JOJO的奇妙冒险 飙马野郎| 美丽小蜜桃3:美丽人生,优酷| 校花的贴身高手无删减版电视剧 | 冰河剧集两个妈妈| 无眠之境电视剧免费观看| 如懿传在线观看全集| 韩国R电影| 星际穿越洛丽塔| 首部母乳喂养法规| 《法国空乘11》播放了几集| 纳粹女魔残酷实验2免费观看| 黑狐电视剧在线观看| 《急诊护士在线观看| 汪汪队立大功全集免费中文版| 舞乐传奇| 《色戒》未删减在线观看| 桃恋者未删减| 替天行道之灭门惨案| 朋友的妈妈中语版韩国| 波兰发生多起涂黑石头冒充煤炭事件| 美丽的小蜜桃5| 边城小子| 欧美大兵的寝室免费观看。| 诱人的秘书BD在线观看| 需要爸爸播种籽2中文版免费观看 《和讨厌的社长出差旅》大结局短发 | 部长出差的日子电视剧| 禁欲电影完整版在线播放| 苍蝇陷阱快播| 《双女任务》在线观看| 美剧 监狱风云| 泡芙小姐| 贵妃还乡在线观看| 庆余年第二季免费看全集| 诡秘之主动漫免费完整版| 乡村爱情16部全集电视剧| 狮子王免费| 马龙王楚钦等15人获记大功奖励| 公公的浮生手中字电影完整版 | 高压监狱2免费完整版在线播放| 我的抗战之猎豹突击| 格莱美金曲| 速度与激情9电影免费观看 | 星际传奇2| 免费拗女网站1300部| 豫剧包青天| 《玉女心经之阴阳和合》电影免费观看 | 全明星solo| 半斤八两主题曲| 《情趣用品调教》| 女律师替夫还债50万电影叫什么 | 強暴處女1-12系列| 开关女孩第二季| 不扣扭女孩| 美女收藏家| 玫瑰的故事更新时间| 在姨母家的遗憾HD中字_经典片全集_免费在线观看-森森影院 | 高清《怪谈》免费观看| 新上任女员工| 冬至电视剧全集免费观看完整版高清| 驯服小姨子2中文字幕| 电视剧冰锋| 姐姐免费观看| 桃花劫电影免费观看高清大电影| 穿越小兽:兽夫粘人短剧全集| 369你懂的电影| 最牛女村主任| 隋唐英雄传第四部| 韩国电影售楼处的秘密 | 凌云志志(法国)满天星在线| 甜蜜的惩罚全集| 侦察记全集| 眼泪女王| 非你莫属泰剧在线观看| 高清《浮图缘》免费观看全集完整版| 青青娱乐| 二战德军发泄室正版| 电影《功夫》国语免费观看| 满天星《爱丽丝梦游辣境》| 会计账簿的基本内容| 理发店韩国宋智考| 非亲兄弟电影| 南京大学辟谣周恒致女生怀孕| 麦乐迪《家庭矛盾》在线观看| 劳荣枝案受害者家属发声| 疯狂欲望无删减 在线高清中字| 性 谎言和录像带| 李光耀观天下| 台前县| 好男人观看免费高清完整在线| 臣本布衣短剧全集| 法国满天星《律师事务所》| 久久偷看各类WC女厕嘘嘘偷窃| 蝙蝠侠与罗宾| 年经的继坶3| 自W到抖还要继续但没喷| 东北插班生免费观看全集| 娃娃脸4| 一年一度喜剧大赛在线观看| 人鱼小姐国语版在线观看免费全集 | HD版激战丛林完整版| 玉观音高云翔版| 图书馆女友第一季全集免费六集| 《大明王朝1566》电视剧| 警花张津瑜头撞玻璃| 坎贝奇无憾完整版观看| 将军家的小娘子电视剧免费观看| 双龙会高清完整版免费观看| 洪金宝电影国语版| 意大利八尺夫人满天星美版 | 屠宰森林1小时52分版| 电视剧虎胆雄心全集免费观看| 擦枪走火电影完整版| 荷尔蒙美国版9| 小妈妈电影| 1—40全集观看理想之城| 俘虏之锁1-2集在线观看| 亚当君同类型动画有哪些| 高压监狱2在线完整免费| 曾几何时的意思| 绝种好男人国语| 斗罗大陆电视剧第二部| 韩国电影失足美女色诱猎物| 安拉拉斋电影免费播放| 新婚夜全集免费观看电视剧| 1996年杨敏思版电视剧第三回合 | 白嫖者联盟免费播放熊出没电影| 食人鱼6| 巧奔妙逃高清完整版免费观看| 台球高手在线观看免费完整版视频| 好奇实验室| 闯关东百度影音| 《急诊护士》法国版电影在线播放完| 韩版欺诈游戏| 浴血黑帮全季在哪能看| 需要爸爸播的种子免费观看| 铁石心肠2| 阿弥陀佛的故事| 法国满天星《致命娇妻》的剧情简介 | v酒店第一季在线观看| 嫌疑人x的献身 日本| 娜塔莎一家| 冢本监督昭和系列越南女子别动队在线观看完整版 | 白峰美羽 播放| 大秧歌电视剧全集免费| 绝世龙尊短剧免费看| 人猿泰山电影| 妻子的姐姐2免费版电视剧大全| 心战粤语版| 《私人女性监狱》HD| 假面骑士极狐剧场版免费观看| 城市猎人电影| 一起嗟嗟嗟30分钟免费看 | 佳期如许| 我需要爸爸种子英文| 长相思第2季电视剧星辰影院| 李灿森结婚| 特殊理发店| 善良美丽女邻居4| 电视剧红妆| 吴健版《农民伯伯2》播放常青 | 泰剧《蛇姬恋》在线播放| 京都疑云主题曲| 海贼王722| 健身房的激情| 小敏回忆第一季免费观看高清| 《我的身体》1985年帕尼怀尔德| 加勒比海盗成人版| 武动乾坤第5季免费播放| 噼里啪啦国语版在线观看| 人妻初次按摩2| 《黑白禁区》电视剧全集| 年轻的母亲第一季| 走进非洲| 与敌同行国语| 刘亦菲为了气我真是什么戏都接 | 金瓶梅在线观完整版观看视频| 昊天罔极是什么意思| 国庆50大阅兵完整版| 女同地带| 女超人满天星版麦乐迪| 银河护卫队2电影天堂| 《高潮》未删减| 维修工的颜遇 在线| 印度歌曲mp3| 美女餐厅2修改器| 五号特工组百度影音| 痛30集全播放免费观看| 星际之门亚特兰蒂斯第五季| 美国XXXX69| 倚天屠龙记2阴阳圣火令最新| 艾曼纽6| 韩国电影朋友家的麦子| 男生女生向前冲2024| 延禧攻略全集免费观看| 1水手服饲养未减版剧情介绍| 出关电视剧全集在线| 都市剧集在线电视剧免费观看| 图书馆女友1-6集观看地址 | 卢旺达的玫瑰满天星| 开心鬼救开心鬼| 七天电影| 美容院的待遇服务在线观看| 铠甲勇士之雅塔莱斯大电影完整版| 湄公河大案全集在线观看| 俄罗斯版女儿国2| 方谬神探| 甜蜜惩罚全集在线观看| 金银瓶1-5hd普通话水蜜桃2 布隆迪| 巴山夜雨的意思| 落跑甜心30集| 多情成仁高清影视| 韩剧蓝色隐形帽子的电影| meinvsiwa| 午夜寂寞| 十九岁大学生免费观看完整版电视剧| 理判事判| 楼下的租客| 《法国空乘5》免费播放| 金瓶艳谈| 白莲花 电视剧| 聊城市| 新版《水浒传》| 丐侠传奇演员表| 电影《长津湖》免费版| 义姐是不是良妈妈喂养动漫| 吗吗朋友| 加勒比在线| 梅川小帅上司送伞完整版| 电视剧新结婚时代| 我的妈妈日语| 美国式家庭禁忌结局-3农厂家的女儿| 乘风2024免费观看完整| 南来北往电视剧剧情介绍| 人生马戏团粤语全集| 三年成全免费观看在线播放| 雪中悍刀行第三部50集免费观看| 万界独尊免费完整版高清动漫| 啄木鸟《壮志凌云》片段| 《黑白禁区》电视剧全集 | 老狼在线观看免费播放电视剧| 荒岛生存| tobu8XXⅩHD| 异世邪君520| 流星花园 泰版观看全集| 小巷人家40集全免费高清下载 | 赛德克巴莱完整版电影| 一骑红尘妃子笑中的妃子是谁| 美容院的待遇服务免费| 外出电影| 大风歌全集| 男女生一起愁愁愁电视剧在线观看 漫画 | 美国电影荣誉守则满天星| 女帝夺江山:拥美男入怀短剧全集| 我叫金三顺电视剧| 哇嘎美国| 我的世界籽岷虚无世界2| 落落历险记| 八戒7| 小唐横情史| 守法公民电影国语完整版免费观看| 百万新娘之爱无悔大结局| 特别酒店2_午夜电影全集_完整版免费在线观看 | 迎风青春电视剧免费观看| 荆轲刺秦王电影| 联姻五年后她重生了短剧免费全集| 战狼6欧式少女全部剧情介绍| 猎罪图鉴免费观看完整版| 南北少林电影| 爱我多深原版不剪辑| 19岁大学生真人电视| 沧元图第二季第4集| 长风渡电视剧40集免费观看高清| 逃跑新郎| 魔术快斗国语版全集| 黑街舞男高清版免费观看| 高清《致命的艳遇》电影| 混血儿的摇篮| 本能2在线观看| 韩剧想你国语版| 需要爸爸播种籽2| 黑暗面在线观看完整版免费高清| 澳门风云2西瓜影音| 周二谋杀定律电视剧| 侯玉洁牧师讲道大户人家4| 烬相思 电视剧| 半是蜜糖半是伤免费观看在线| 火线传奇3| 女版战狼免费观看| 超级大坏蛋国语版免费观看| 美食的俘虏125| 熊出没高清观看| 聊斋艳谭之艳魔大战| 古墓丽影法国版| 独立日 电影| 那年冬天风在吹| 剃须然后捡到高中生| 慈禧秘密生活 国语| 魔镜号在线观看完整版免费| 唐砖电视剧完整版| 能听到我的心吗| 护理学院的故事法国满天星免费观看| 小公主苏菲亚第四季免费观看| 普兰店市| 复仇打脸:重生弃少全集免费| 窥情在线观看| 两人互换麦子3| 密桃成熟时33d| 潜渊电视剧观看免费全集| 艳情电影邻居的阿姨| 法国空乘2018在线观看免费| 火蓝刀锋28| 《有翡》观看免费完整版| 十七岁版在线观看免费| 我爱几何电视剧免费观看| 梅西现身迈阿密超市| 女婿有劲枪枪到底岳峰扮演者| 游泳馆特殊待遇| 《妓院里的中国姑娘》BD高清高清在线 | 高清恩爱的盗贼大人未删减| 杰克影视606满天星免费观看| 《夕颜》动漫全集在线观看完整版中 | 今天的她们电视剧免费观看| 乌龙元首| JUL945老公不在的平日、被自外工作回家的絶伦公公天天侵犯到疯狂高潮。三舩美 | 画中迷电视剧全集免费| 电视剧 国家命运| 最后的幸存者第二季| 韩国蓝色隐形帽子免费| 新金瓶梅2| 三年高清免费观看国语| 越南满天星超大尺电眼影| 中医经筋捆扎疗法视频| 宝丰县| 请和我老公结婚 日剧| 骄阳伴我 电视剧| 寂寞的人妻水电工维修| 仁光受难记| 霹雳大喇叭国语| 《恋雪纪》完整版免费看| 十二生肖电视剧| 免费拗女稀缺资源观看| 十七岁电影| 电视剧武则天全集| 《丈夫上司部长与妻子的相处之道》| 《建国大业》| 妈妈的味道韩国中字| 美国伦理雪莉3| 新还珠格格的所有歌曲| 姐姐立正向前走| 97电视剧在线播放免费观看| 擎天撼地| 最美情侣视频完整版高清| 广东工厂搬迁到四川是真的吗| 韩国电影《美发店的秘密| 早乙女露依ed2k| 《乳房》特殊按摩电影| 塔尔萨之王免费观看| 少女免费版在线观看第6集| 鸳鸯村的故事| 凶宅胭脂国语免费观看完整版高清 | 梁祝艳谭电影全部播放| 需要爸爸播种在美国| 王子变青蛙全集高清| 花与蛇7免费完整版全集高清| 勇士vs独行侠 人情世故| 男女一起愁愁愁高清全集在线看| 爱情睡醒了28| 欧式少女第16集全集剧情介绍视频 | 车贤静三级| 爱我就别想太多1-40集免费完整| 点燃我温暖你全集在线观看| 忌讳5-8| 电影老虎出更| 惊天激战2024完整版在线观看| 雾山五行第二季樱花动漫| 大哥的宝库免费观看全集高清| 凯登版《交易》满天星| 迷人的新娘| 刘美君电影| 新走西口广场舞| 继拇做爰3| 铃芽之旅在线观看| 离婚律师结局| 麻栗坡县| 壮志凌云女版1994美国满天星| 蒸发太平洋| 生日快乐刘若英版电影| 男女在一起愁愁愁免费观看电视剧动| 老家门前唱大戏| 捷克街头搭讪系列完整版免费观看| 普京抵达人民大会堂| 哇嘎高清在线观看| 野花电影手机观看免费高清| 立功电视剧| 就想赖着你吻戏| 功夫小龙| 花儿与少年5综艺免费| 寻秦记免费完整版全集在线观看| 蜜桃成熟时1997完整版| 冢本在线视频| 复仇者联盟字幕| 婆婆女婿练瑜伽的说说| 高压监狱高压2法版免费观看高压监狱 | 食神电视剧| 电视剧来电奇缘全集| 新闻女王电视剧在线观看| 致我们甜甜的小美满小说| 翡翠王下载| 张您雨魅惑全集免费观看| 意大利语95版泰山在线播放| 老爸回家 电视剧| 邻居的老婆完整版电视剧免费观看| 369免费电影。你懂得| 高清未婚男女的效率恋爱| 侄女的开发日记免费动漫观看| 天堂之吻电影版在线看| 木下凛凛子邻居电影完整版在线观看| 继母17岁| 电影《恋爱的味道》的简介| 寒山令电视剧全集完整版免费观看| 黑衣人2高清完整版国语| 杰克森 胡| 杜拉拉升职记 电影下载| 变形金刚7高清下载| 维斯塔小镇家长会2免费观看| 不当军事行为| 可人儿成人社区| 朋友的新妈妈| 三年大片在线观看免费| 梦里水乡mtv| 金牌销售的秘密2中字更新时间 | 手撕绿茶:姐姐超飒全集免费| 神医娇妻:总裁别碰短剧全集| 绅士和小姐| 八戒8电影在线播放免费观看| 新扎师姐| 美国电影娃娃脸| 一个完美结局电影完整版在线观看 | 公元前2000年| 电影17·3免费观看完整版在线观看 | 看暴操老妈46集完整片| 电视剧你比星光美丽免费观看 | 间谍过家家樱花| 拜访者q| 黑名单 美剧| 女邻居的滋润4| 豪情1百度影音| 我和丰满瑜伽老师| 脸红心跳的妖怪爱之战役| 美姊妹赤板栗完整版在线观看| 大侦探福尔摩斯1在线观看| 银魂262| 盗墓笔记 迅雷| 战狼6免费在线观看| real在线播放| 九周半完整版百度影音| 机boy小子真假威龙| 美式忌保罗讳1-4是合法的吗| 莫妮卡裸奶无删减版| 童话村最新电影| 《登山的目的2》中文| 卖车的女销售2电影| 小李飞刀翻拍| 新版春去春又回| 高清《人生导师》电影在线观看免费 | 胭脂电视剧全集| 姐姐真漂亮免费观看| 遇见花香的那一刻| 《私密女性监狱》中文| 美景之屋免费观看全集电影完整版 | 齐天大圣孙悟空陈浩民全集| 暴躁46集全免费剧情| 封神英雄榜第二部免费观看电视剧| 切糕段子| 道家太阳手印| 与讨厌的部长一起去出差| 小燕子一家人| 瓜达卢佩的玫瑰2| 君九龄免费观看| 长相思第一季多少集| 快车电影资源| 花与蛇全集| 日本大学生私密按摩在线播放| 珍馐记电视剧免费版在线观看| 小辣椒3电影在线看免费完整版高清| 《美景之屋2》无删减裸露版| 《卿本佳人》李月仙| 爱的色放韩国电影| 爱丫爱丫电影在线观看免费版高清国语 | 二泉映月课文朗读| 卧底2电视剧免费观看全集| 《屠宰呕吐娃娃》免费观看完整版 | 你是我的眼| 隐形的亿万富翁电影免费| 战狼6在线观看免费播放下载| 《超女麦乐迪》在线播放地址| 《澳门风云3》电影| 包青天93版免费观看西瓜影院| 《需要爸爸种子》英文翻译| 垌头剧集网回到娘家的儿媳妇| 爱在离别时全集| 江照黎明| 给女儿开了花包| 电影《毒医肥尸》| 荒野求生贝尔 格里尔斯| 大鸿米店电影| 斯巴达第三季无删减在线看| 法国空姐3免费高清原声满天星美版百度 | 酒店1-75集全集免费播放| 台湾满天星在线观看台湾电影| 刀剑神域mad| 夏妻动漫| 舞蹈红红火火| 2008徐少强在线观看完整版| 没有玫瑰的花店下载| 谍影重重1电影| 葫芦丝演奏| 漂亮妈妈中字开头7个字| 电视剧特战荣耀完整版全集免费观看| 十大奇冤下载| 断背山百度影音| 别墅轮换1攻略完整版正版| 独孤天下电视连续剧免费观看| 《完美邻居2》电影在线观看中文版 | 黄金瞳电视剧免费观看全集| 《渔夫荒淫史在线播放| 我的老婆是大佬3国语| 电视剧勇敢的心| 冷山电影未删减完整版| 春欲未删减版dvd| 小凤新婚下在线观看免费版| 潜行狙击电影版国语| 荷尔蒙美国版6| 《以你的心诠释我的爱》| 火箭军司令员李玉超| 铜铿锵锵完整版免费看| 孩子王电影| 今夜无人入睡免费观看韩剧| 台湾黛比的浪漫女教师| 电视剧妈妈无罪| 金银瓶1—5hd 普通话一级| 金发梦露免费观看完整版| 镇魂 电视剧| 束缚游戏完整版| 似曾相识短剧免费观看| 已婚护士| 三年大片大全免费观看国语版电影完| 法国空姐满天星法版| 我的观影报告剧情介绍| 天使快播| 也平凡电视剧在线观看| 秘密搜查官の女在线观看| 太平洋战争纪录片| 禁忌之女2| 醉拳2电影| 《战狼4》免费999| 武藤绫香番号| 天地无伦百度影音| 五台山奇情| 高清《猎杀之后》电影| 瓜达卢佩玫瑰在线| 剑魔独孤求败全集免费观看国语| 新逃学威龙张浩在线观看| 王曼昱简介| 丈夫让妻子陪上司吃饭| 江苏公共频道| 我们结婚吧电视剧免费全集| 《消失的初恋》电视剧| 超级变变变| 唐砖免费观看全集| 朝国年经继拇6免费版| 风筝电视剧| 蘑菇影视妈妈的职业 经典| 印度教父| 《最高の爱人诏》原| 没了丈夫和上司免费观看所有电影| 小棉袄免费观看全集电影| 唐砖1-37集免费观看完整 | 《极乐宝鉴》在线观看| 王牌对王牌第二季| 地狱默示录| 命运之夜天之杯| 蜜桃成熟时1997百度影音| 《渔夫的荒野史记》在线观看免费 | 《牙医姊妹》无删减版观看在线观看| 色戒无删减版| 刘老根第三部| 好运理发师| 金银梅5一10普通话1| 年经母年| 《爱情有烟火》免费观看| 大兵日记全集| 海豹看看动画片在线观看| 《图书馆的女朋友》1-6结局| 非洲原始部落的女人| 农村歌舞团露骨表演| 年轻人在线播放| 猎犬 第二季| 劳拉的痴迷1996电影免费播放| 假面骑士欧兹| 兵临城下美版电影| 女儿的味道免费观看| 酒店服务生在线观看完整免费高清第一集 | xxxnxgx| 开始推理吧第3季免费观看全集高清| 《妻子偿还》| 白峰美羽教师| 公交车一路被C| 《爱在离别时黄奕》免费观看| 绅士的品格在线观看免费完整版| 致美丽的你| 我年轻的瘦子5| 三级片桃色凶器| 《烬相思》电视剧| 《妻子3》免费高清电视剧| 有鬼住隔壁国语| 美国保罗2免费| 秋山莉奈| 君九龄免费观看| 嫌疑人x的献身电影| 非诚勿扰20120527| 克莱尔的迷宫在线| 中国好声音之为你转身| 灵与欲舒淇高清全集| 双谍姐妹花| 需要爸爸的种子在线播放 | 上栗县| 日本波多野吉衣| 免费论理片| 女儿情歌曲原唱| 甜蜜惩罚之我是看守所专用宠物| 私人女性监狱| 善良的秘书法国空乘| 天龙八部钟汉良| 江苏卫视花样年华全集| 《消失的痕迹》电视剧| 性的暴行| 妻子被黑人在丈夫面前被欺负电影| 沉默的舰队电影| 藜麦的正确吃法| 湘北小成| 曹阳 MOKO| 战狼9欧式少女战狼12| 玛丽和马克思| 星落凝成糖电视剧免费| 白鹿《唐宫奇案》免费观看| 山岸逢花在线观看| 电影圣特罗佩斯的姑娘们在线观看 | 鬼吹灯之龙岭迷窟| 缘分 电影| 电视剧大侠霍元甲全集免费播放| 人间正道是沧桑在线看完整版| 人猿泰山1995版本| 活人配阴婚的后果| 电影《需要爸爸播种子 2| 辛亥革命电影影评| 海棠依旧电视剧全集免费观看完整版| 魔帝归来:三界唯我独尊全集免费 | 周渝民最新电影| 酒店服务生动漫免费看| 视频观看完整版高清| 婚礼大作战| 韩国伦理部长来我家| 第11集| 左手视频免费观看| 《折腰》全集免费观看| 拥抱未来的你| 小蜜桃6观看免费高清版电视剧97| 《奇迹》台剧| 蜜桃熟了| 没有工作的一年| 北越暴行电影2| 爵迹2冷血狂宴完整版| 伦敦奥运会男子篮球决赛| 西厂厂花| 中国推背图| 漂亮小瘦子 3| 高清《周年纪念日》电影| 小丈夫 电视剧| 王牌对王牌第六季免费观看| 韩国电影丰满的少妇| 站着再来一次全集| 人妻初次按摩2完整版| 雄狮少年电影| 男生打游戏听出网友病危| 上流赤流复仇| overflower免费观看动漫全集完整版,没有血缘关系 | 巜生殖按摩女3在线播放| 和部长一起去出差旅的日子日剧| 邱淑贞电影全集| 绝命凯特| 非缘勿扰 下载| 里面也好好爱第5集免费观看| HD版《激战丛林》完整版在线观看免费 | 关西无极刀高清| 兄弟的嫂子和小姨子| 当哒当:邪视电影| 添女人荫蒂视频A片| 美女热舞慢摇| 华英雄电影免费观看| 手工制作丝网花| 喜羊羊与灰太狼古古怪界大作战| 隐形的帽子在线观看完整版免费高清| 日剧卖房子的女销售| 洪湖水浪打浪简谱| 婆家娘家第三部| 高清《女学生3》在线观看| 新金银1—5| 渔夫的老婆赛仑免费观看 | 三位大学位按摩记的更新内容怎么写 | 怀念战友刀郎| 偶像练系生| 我们都要好好的全集免费完整版| 妻子8免费高清完整电视剧情 | 未婚少女高清免费观看电视剧| 嫂嫂19岁| 深田咏雪| 浮之手| JUL-945三船美铃在线播放| 在线观看密爱| 三级剧情片| 男女在线观看免费高清电视剧推荐| 黑白配国语版在线播放| 一卡二卡三卡在线观看| 爱回家之二人世界| 九妹mv| 远离人群的地方| 天降财神国语高清| 酒店1-75集免费观看国内| 甜蜜惩罚我是专门看守所宠物真人| 青春四十电视剧| 交谊舞曲快四| 怪异韩国电视剧在线观看| 黑白配4黑白配在线观看免费高清 《在姨母家的客厅》免费播放完整版 | 《10号舱房的女人》| 《浪漫女家教》HD-免费在线观看完整版[1线]-被窝电影网 | 最新港剧| 男生女生一愁愁愁电视剧在线观看| 正在播放: JUL-520 留下好色的老爹和软弱的妻子 我去了三天两夜的出差... 成 | 电视剧折腰免费看| 楚留香传奇电影版| 娃娃脸 5| 夏树电影免费观看完整版全集最新一 | 招魂电影在线观看完整版免费| xl司令真人版第一季全集免费cb| 女装正太01| 韩国电影姐姐在线完整版观看| 胜女的代价优酷| 医学调查电视剧免费观看| 激战丛林在线观看| 不良义姐真人版第一季在线看免费| 保卫孙子电视剧| 修罗战神:谁敢动我家人全集免费| 巨乳学院漫画| 国家宝藏1高清| 战狼9姐弟完整版免费看| 1979女超人| 夺子战争全集| 韩剧电影《肚脐》原版| 善良的小姨子无删减| 壮志凌云(法国版)2011| 大奉打更人电视剧完整版免费| 帅飘全集| 美景之屋6:我的完美人生中字| 终结者5:创世纪| 头脑特工队2电影| 八妻子视频观看| 冤家亲家电视剧| 飞刀又见飞刀百度云| 第一次4电视剧免费观看| 台剧《黛比浪漫女家教1》在线播放| 伦理片高压监游完整燃| 吉祥三宝舞蹈| 一人之下第四季免费观看全集中文| 证据原唱| 飞鸟集电视剧| 巜同学聚会》2| 神奇 电影| 正是青春璀璨时| 红高粱高清下载| 全红婵回母校现场示范压水花| 青梅竹马是消防员第二季完整版未打吗robl | 唐朝艳史| 金银梅韩国| 楼上的女邻居电视剧剧情介绍| 红墨坊电视剧| 离婚前规则33| 宜昌保卫战| 哈利波特6免费观看| 欧美506070| 商务旅行戴绿色帽子电影| 《猛鬼山坟》免费看| 邻家特工 下载| 美女猜拳脱脱| 铠甲勇士50| 女长官的体罚哪一集| 法国空姐八部曲系列| 妈咪超厉害:一胎二宝全集免费| 电影《现在不行》完整观看免费| 雪中悍刀行电视剧免费观看36集| 美国5~1普通话| 女版壮志凌云歌曲| 扫黑风暴28在线观看| 孤注一掷在线看| 关东棋王| 《老师再爱我一次》大结局| 战狼4免费999加拿大版| 《可疑诊疗室:特殊待遇》中文版 电影《香醇的绣感》男主吴绮珊 欧式少女第16集全集高清版观看网站 | 《特殊的游泳教练》| 非洲黑人噼里啪啦视频免费看…| 恩师情深电影| 武汉三镇主帅考虑执教国足 | 动漫《落魄贵族琉璃川》| 天海冀| 精装追女仔2004国语| 神秘感染电影| 闪亮的西瓜电视剧免费观看| 同桌的你电影插曲| 巜交换上司的秘书2中文电影| 性的爆行| 仙逆哪里可以看到76集| 汉末皇叔| 守护者联盟| 中国新说唱2019| 美国版武则天惊艳版演员表| 塞伦意大利1997地爆天星| 甜蜜专宠我是狱长第二季免费观看 | 非凡医生电视剧| 人猿泰山1995在线观看超清| 京香在线| 爱情公寓 第三季| 极速蜗牛插曲| 焦糖玛奇朵电视剧| 杭州纵火案灵异事件| 秋瓷炫《生死决断》| 三妻四妾免费观看完整版高清,皇家女| 日本顶级RAPPER| 明星大侦探5| 绝种好男人| 白蛇传说粤语| 木兰县| 女超人满天星蓝光版| 欧美裸体电影| 新闺蜜时代剧情介绍| 重生之锦绣嫡女短剧全集| 花名未闻剧场版| 丈夫不在部长来家做客| 高清探子阿坚:无头奇案| 《速度与激情10》电影|