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;
}
痛點:每個失敗點都要重寫釋放邏輯,極易漏釋、序錯,造成資源洩漏。
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...)隱含釋放層級,閱讀即懂。malloc 失敗),直接 return 更直觀;強行 goto 反而破壞一致性。break/continue 只能跳一層)。switch、try/catch 底層實際上也是透過跳轉指令實現。結論:goto 不是洪水猛獸,而是「集中式資源釋放」的利器。只要遵循「單向、逆序、語義統一」,大方使用,讓程式碼更健壯、更易讀。