首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

【編程好習慣】goto 語句的正當防衛:錯誤處理與資源釋放的最佳拍檔

     編輯:關於C語言

goto 在 C/C++ 界「臭名昭著」,許多編碼規範甚至明令禁止。但 Linux 核心(2.6.21)使用 20,333 次、VxWorks 6.2 用 9,142 次、glibc 2.9 用 1,750 次——事實證明,恰當使用 goto 能大幅簡化錯誤處理、提升可讀性與可維護性。關鍵在於「怎麼用」,而非「能不能用」。

反面教材:分散式錯誤處理的陷阱

以下函數初始化佇列,需依序分配 5 項資源。若中途失敗,必須釋放已成功分配的資源。圖 1 採用「就地處理」:

int queue_init(queue_t **pp_queue, int size)
{
    pthread_mutexattr_t attr;
    queue_t *queue;

    queue = (queue_t *)malloc(sizeof(queue_t));
    if (!queue) return -1;
    *pp_queue = queue;

    memset(queue, 0, sizeof(*queue));
    queue->size_ = size;

    pthread_mutexattr_init(&attr);
    if (pthread_mutex_init(&queue->mutex_, &attr)) {      // 失敗點 1
        pthread_mutexattr_destroy(&attr);
        free(queue);
        return -1;
    }

    queue->messages_ = (void **)malloc(queue->size_ * sizeof(void *));
    if (!queue->messages_) {                              // 失敗點 2
        pthread_mutexattr_destroy(&attr);
        free(queue);
        return -1;
    }

    if (sem_init(&queue->sem_put_, 0, queue->size_)) {   // 失敗點 3
        free(queue->messages_);
        pthread_mutexattr_destroy(&attr);
        free(queue);
        return -1;
    }

    pthread_mutexattr_destroy(&attr);
    return 0;
}

痛點:每個失敗點都要重寫釋放邏輯,極易漏釋、序錯,造成資源洩漏。

正面範例:goto 集中式錯誤處理

int queue_init(queue_t **pp_queue, int size)
{
    pthread_mutexattr_t attr;
    queue_t *queue;

    queue = (queue_t *)malloc(sizeof(queue_t));
    if (!queue) return -1;               // 尚未分配資源,直接返回
    *pp_queue = queue;

    memset(queue, 0, sizeof(*queue));
    queue->size_ = size;

    pthread_mutexattr_init(&attr);
    if (pthread_mutex_init(&queue->mutex_, &attr))
        goto error;                      // 跳轉至統一釋放區

    queue->messages_ = (void **)malloc(queue->size_ * sizeof(void *));
    if (!queue->messages_)
        goto error;

    if (sem_init(&queue->sem_put_, 0, queue->size_))
        goto error1;                     // 已分配 messages_,需先釋放它

    if (sem_init(&queue->sem_get_, 0, 0))
        goto error2;                     // 已初始化 sem_put_,需先銷毀它

    pthread_mutexattr_destroy(&attr);
    return 0;

error2:
    sem_destroy(&queue->sem_put_);
error1:
    free(queue->messages_);
error:
    pthread_mutexattr_destroy(&attr);
    free(queue);
    return -1;
}

優勢:

  • 主流程清晰,無錯誤處理雜訊。
  • 釋放邏輯集中、按分配逆序,消除漏釋風險。
  • 標籤命名(error、error1...)隱含釋放層級,閱讀即懂。

使用 goto 的三條鐵律

  1. 不可濫用:若尚未分配資源(如第一個 malloc 失敗),直接 return 更直觀;強行 goto 反而破壞一致性。
  2. 禁止形成迴路:goto 應形成單向流(從上往下跳),避免「義大利麵式」控制流。極少數不破壞可讀性的情況可例外。
  3. 語義統一:函數內所有 goto 應代表同一語義(如「出錯需釋放資源」),切勿混雜業務邏輯跳轉。

適用場景不只錯誤處理

  • 多層巢狀迴圈需一次跳出(break/continue 只能跳一層)。
  • 狀態機實作中,根據事件直接轉移到目標狀態標籤。
  • 編譯器生成的程式碼中,switch、try/catch 底層實際上也是透過跳轉指令實現。

結論:goto 不是洪水猛獸,而是「集中式資源釋放」的利器。只要遵循「單向、逆序、語義統一」,大方使用,讓程式碼更健壯、更易讀。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved