一、longjmp ()/setjmp () 函數詳解

setjmp() 和 longjmp() 是 C 標準庫(<setjmp.h>)中的函數,用於實現非局部跳轉(跨函數 / 代碼塊的跳轉),突破常規的函數調用棧流程。常用於異常處理、錯誤恢復或簡化複雜分支邏輯。

1. 核心概念

  • 非局部跳轉:不同於 goto(僅能在當前函數內跳轉),longjmp() 可跳轉到之前通過 setjmp() 標記的位置,即使跨多個函數調用層級。
  • jmp_buf 緩衝區:存儲程序執行上下文(寄存器、棧指針等),作為跳轉的 “錨點”。

2. 函數原型

c

運行

#include <setjmp.h>

// 標記跳轉點,返回值:
// - 首次調用:返回 0;
// - 被 longjmp() 觸發跳轉時:返回 longjmp() 的第二個參數(非 0)
int setjmp(jmp_buf env);

// 跳轉到 setjmp() 標記的位置,恢復 env 存儲的上下文
// val:傳遞給 setjmp() 的返回值(必須非 0,若傳 0,setjmp() 實際返回 1)
void longjmp(jmp_buf env, int val);

3. 工作原理

  1. setjmp(env):將當前程序執行上下文(PC 指針、棧指針、寄存器等)保存到 env 緩衝區,返回 0;
  2. longjmp(env, val):從 env 恢復上下文,使程序跳回 setjmp() 調用處,此時 setjmp() 不再返回 0,而是返回 val(非 0);
  3. 跳轉後,棧會回退到 setjmp() 所在的函數層級,後續函數的棧幀被丟棄。

4. 示例代碼(錯誤處理場景)

c

運行

#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>

jmp_buf jump_env; // 全局/靜態緩衝區(需保證跳轉時有效)

void risky_operation(int code) {
    if (code < 0) {
        // 發生錯誤,跳回 setjmp 標記的位置,返回值 1
        longjmp(jump_env, 1);
    } else if (code > 100) {
        // 另一種錯誤,返回值 2
        longjmp(jump_env, 2);
    }
    printf("Operation success! Code: %d\n", code);
}

int main() {
    // 標記跳轉點,首次返回 0
    int ret = setjmp(jump_env);
    switch (ret) {
        case 0: // 首次執行,正常邏輯
            printf("Start operation...\n");
            risky_operation(-5);  // 觸發錯誤1
            // risky_operation(101); // 觸發錯誤2
            // risky_operation(50);  // 正常執行
            break;
        case 1: // 錯誤1處理
            printf("Error: code is negative\n");
            exit(1);
        case 2: // 錯誤2處理
            printf("Error: code exceeds 100\n");
            exit(1);
        default:
            printf("Unknown error\n");
            exit(1);
    }
    return 0;
}

輸出

plaintext

Start operation...
Error: code is negative

5. 關鍵注意事項

  • 緩衝區有效性jmp_buf 需保證在 longjmp() 調用時未被銷燬(如全局 / 靜態變量,或棧上變量但跳轉後棧未被覆蓋),否則行為未定義;
  • 禁止跳轉的場景
  • 不能從信號處理函數中跳轉到非信號處理的代碼(可能導致資源泄漏);
  • 不能跳轉到已返回的函數(棧幀已釋放,訪問無效內存);
  • 資源泄漏風險:跳轉時會跳過局部變量的析構(C++)、free()/close() 等操作,需手動清理資源;
  • C++ 兼容性:C++ 中不推薦使用(建議用異常 try/catch),因為 longjmp() 不會調用對象的析構函數,可能導致內存泄漏或對象狀態異常。

6. 適用場景

  • 嵌入式系統 / 內核態代碼(無異常機制時的錯誤恢復);
  • 簡化多層嵌套函數的錯誤返回(避免逐層返回錯誤碼);
  • 實現協程 / 上下文切換(基礎原理)。

二、qsort () 函數詳解

qsort() 是 C 標準庫(<stdlib.h>)中的通用快速排序函數,支持對任意類型的數組進行排序,核心是通過回調函數定義比較規則。

1. 核心特點

  • 通用性:支持 int、char、結構體等任意類型數組;
  • 原地排序:無需額外內存(快速排序的特性);
  • 不穩定排序:相等元素的相對位置可能改變;
  • 回調驅動:比較邏輯由用户自定義的函數決定。

2. 函數原型

c

運行

#include <stdlib.h>

// 對數組進行快速排序
// base:指向待排序數組的首地址
// nitems:數組元素個數
// size:單個元素的字節大小(如 int 是 4,char 是 1)
// compar:比較函數指針,定義元素的排序規則
void qsort(void *base, size_t nitems, size_t size, 
           int (*compar)(const void *, const void *));

3. 比較函數規則

  • 入參:兩個待比較元素的指針(const void* 需強制轉換為具體類型);
  • 返回值:
  • < 0:第一個元素 < 第二個元素(升序時放前面);
  • = 0:兩個元素相等;
  • > 0:第一個元素 > 第二個元素(升序時放後面)。

4. 示例代碼

示例 1:排序 int 數組(升序 / 降序)

c

運行

#include <stdio.h>
#include <stdlib.h>

// 升序比較函數:int 類型
int compare_int_asc(const void *a, const void *b) {
    // 轉換為 int* 並取值
    int val1 = *(const int*)a;
    int val2 = *(const int*)b;
    // 簡化寫法:return val1 - val2;(注意溢出風險)
    if (val1 < val2) return -1;
    if (val1 > val2) return 1;
    return 0;
}

// 降序比較函數:int 類型
int compare_int_desc(const void *a, const void *b) {
    return compare_int_asc(b, a); // 交換參數即可
}

int main() {
    int arr[] = {5, 2, 9, 1, 5, 6};
    size_t n = sizeof(arr) / sizeof(arr[0]);

    // 升序排序
    qsort(arr, n, sizeof(int), compare_int_asc);
    printf("Ascending: ");
    for (size_t i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");

    // 降序排序
    qsort(arr, n, sizeof(int), compare_int_desc);
    printf("Descending: ");
    for (size_t i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");

    return 0;
}

輸出

plaintext

Ascending: 1 2 5 5 6 9 
Descending: 9 6 5 5 2 1
示例 2:排序結構體數組(按字符串 / 數值)

c

運行

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定義結構體
typedef struct {
    char name[20];
    int score;
} Student;

// 按分數升序比較
int compare_student_score(const void *a, const void *b) {
    const Student *s1 = (const Student*)a;
    const Student *s2 = (const Student*)b;
    return s1->score - s2->score;
}

// 按名字字典序比較
int compare_student_name(const void *a, const void *b) {
    const Student *s1 = (const Student*)a;
    const Student *s2 = (const Student*)b;
    // strcmp 返回值符合 qsort 比較規則
    return strcmp(s1->name, s2->name);
}

int main() {
    Student students[] = {
        {"Alice", 85},
        {"Bob", 92},
        {"Charlie", 78},
        {"David", 92}
    };
    size_t n = sizeof(students) / sizeof(students[0]);

    // 按分數排序
    qsort(students, n, sizeof(Student), compare_student_score);
    printf("Sort by score:\n");
    for (size_t i = 0; i < n; i++) {
        printf("%s: %d\n", students[i].name, students[i].score);
    }

    // 按名字排序
    qsort(students, n, sizeof(Student), compare_student_name);
    printf("\nSort by name:\n");
    for (size_t i = 0; i < n; i++) {
        printf("%s: %d\n", students[i].name, students[i].score);
    }

    return 0;
}

輸出

plaintext

Sort by score:
Charlie: 78
Alice: 85
Bob: 92
David: 92

Sort by name:
Alice: 85
Bob: 92
Charlie: 78
David: 92

5. 關鍵注意事項

  • 類型轉換void* 必須強制轉換為對應類型的指針,否則會導致內存訪問錯誤;
  • 溢出風險:比較 int 時,return a - b 在 a 很大、b 很小(如 a=INT_MAX, b=-1)時會溢出,建議用 if-else 或 (a > b) - (a < b)
  • 穩定性qsort() 是快速排序實現,不穩定(相等元素可能交換位置),若需穩定排序需手動實現(如歸併排序);
  • 性能:平均時間複雜度 O (n log n),最壞 O (n²)(但標準庫通常做了優化,如結合插入排序);
  • 空指針檢查:若 base 為 NULL 或 nitems=0qsort() 無操作(行為安全)。

6. 適用場景

  • 快速實現任意類型數組的排序(無需手動編寫排序算法);
  • 批量數據處理(如日誌排序、統計數據排序);
  • 配合 bsearch()(二分查找函數)使用,先排序後查找,效率更高。

三、總結

函數 / 特性

核心用途

關鍵注意事項

setjmp/longjmp

非局部跳轉、錯誤恢復

避免資源泄漏,C++ 中優先用異常

qsort

通用數組排序

正確編寫比較函數,注意類型轉換和溢出

兩者均為 C 標準庫的核心函數:setjmp/longjmp 解決 “流程跳轉” 問題,qsort 解決 “通用排序” 問題,掌握其用法可大幅簡化 C 語言編程中的複雜場景。