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

MySQL 動態字符串 DYNAMIC_STRING 內存管理機制源碼解析

     編輯:PHP 相關知識

MySQL 動態字符串 DYNAMIC_STRING 內存管理機制源碼解析

MySQL 內部大量使用動態字符串構建 SQL、協議包、日誌等。其核心結構 DYNAMIC_STRING(定義於 sql/dynstr.h)通過「緩衝區 + 增長策略」實現高效內存管理,避免頻繁 malloc/free。本文深度解析其結構定義、初始化、追加擴容、截斷、轉義等關鍵操作源碼邏輯(基於 MySQL 5.6/5.7 代碼庫)。

1. 結構體定義

typedef struct st_dynamic_string {
    char *str;              // 字符串數據指針
    size_t length;          // 當前實際長度(不含結尾 \0)
    size_t max_length;      // 緩衝區總容量
    size_t alloc_increment; // 每次擴容增量
} DYNAMIC_STRING;

設計意圖:length 追蹤有效數據,max_length 追蹤已分配內存,alloc_increment 控制擴容步長,平衡內存碎片與擴容次數。

2. 初始化:init_dynamic_string()

my_bool init_dynamic_string(DYNAMIC_STRING *ds,
                            const char *init_str,
                            size_t init_alloc,
                            size_t alloc_increment)
{
    if (!alloc_increment) alloc_increment = 128; // 默認增量 128 字節

    size_t length = 1; // 至少留 \0
    if (init_str && (length = strlen(init_str) + 1) < init_alloc)
        init_alloc = ((length + alloc_increment - 1) / alloc_increment) * alloc_increment;
    if (!init_alloc) init_alloc = alloc_increment;

    if (!(ds->str = (char*) my_malloc(init_alloc, MYF(MY_WME))))
        return TRUE; // 分配失敗

    ds->length = length - 1;
    if (init_str) memcpy(ds->str, init_str, length);
    ds->max_length = init_alloc;
    ds->alloc_increment = alloc_increment;
    return FALSE; // 成功
}

關鍵邏輯:

  • 若提供 init_str,按其長度向上取整到 alloc_increment 倍數分配。
  • 若未提供初始串,直接分配一個 alloc_increment。
  • 使用 MySQL 內存分配器 my_malloc(帶錯誤追蹤、統計)。

3. 追加內存:dynstr_append_mem()

my_bool dynstr_append_mem(DYNAMIC_STRING *ds,
                          const char *append,
                          size_t length)
{
    // 需要擴容?
    if (ds->length + length >= ds->max_length) {
        size_t new_length = (ds->length + length + ds->alloc_increment) / ds->alloc_increment;
        new_length *= ds->alloc_increment; // 向上取整到增量倍數

        char *new_ptr = (char*) my_realloc(ds->str, new_length, MYF(MY_WME));
        if (!new_ptr) return TRUE;
        ds->str = new_ptr;
        ds->max_length = new_length;
    }
    // 拷貝數據
    memcpy(ds->str + ds->length, append, length);
    ds->length += length;
    ds->str[ds->length] = '\0'; // C 字符串安全
    return FALSE;
}

擴容策略:按 alloc_increment 向上取整,而非翻倍。優點:內存增長平滑,適合已知大致最終長度場景;缺點:大字符串頻繁擴容時 realloc 次數多於指數增長。

4. 截斷:dynstr_trunc()

my_bool dynstr_trunc(DYNAMIC_STRING *ds, size_t n) {
    ds->length -= n;
    ds->str[ds->length] = '\0';
    return FALSE;
}

僅移動邏輯長度指針,不歸還內存,極快。適用於解析協議時丟棄已處理前綴。

5. 字符查找:strcend()

char *strcend(const char *s, char c) {
    for (;;) {
        if (*s == c) return (char*)s;
        if (!*s++) return (char*)s - 1; // 到達 \0 返回結尾指針
    }
}

返回首次出現字符 c 的地址,或字符串結尾 \0 地址。用於轉義處理中定位引號位置。

6. 顯式擴容:dynstr_realloc()

my_bool dynstr_realloc(DYNAMIC_STRING *ds, size_t additional_size) {
    if (!additional_size) return FALSE;
    if (ds->length + additional_size > ds->max_length) {
        ds->max_length = ((ds->length + additional_size + ds->alloc_increment - 1) /
                          ds->alloc_increment) * ds->alloc_increment;
        if (!(ds->str = (char*) my_realloc(ds->str, ds->max_length, MYF(MY_WME))))
            return TRUE;
    }
    return FALSE;
}

預留空間供後續直接寫入,避免多次 append_mem 觸發多次擴容。

7. 系統命令轉義:dynstr_append_os_quoted()

my_bool dynstr_append_os_quoted(DYNAMIC_STRING *ds, const char *append, ...) {
    const char *quote_str = "'";
    va_list args;
    va_start(args, append);

    dynstr_append_mem(ds, quote_str, 1); // 前引號

    while (append != NullS) {
        const char *cur = append;
        const char *next;
        while ((next = strcend(cur, quote_str[0])) != cur + strlen(cur)) {
            // 發現引號,轉義:原內容 + \ + 引號
            dynstr_append_mem(ds, cur, next - cur);
            dynstr_append_mem(ds, "\\", 1);
            dynstr_append_mem(ds, quote_str, 1);
            cur = next + 1;
        }
        dynstr_append_mem(ds, cur, strlen(cur)); // 剩餘無引號部分
        append = va_arg(args, char*);
    }
    va_end(args);
    dynstr_append_mem(ds, quote_str, 1); // 後引號
    return FALSE;
}

將任意多字符串用單引號包裹,內部單引號轉義為 \',最終生成如 'ls -a\'l',可安全傳給 system()。

8. 設計總結與啟發

特性MySQL 實現現代 C++ 對比
增長策略固定增量線性增長std::string 指數增長 (1.5~2倍)
內存分配器自定義 my_malloc/my_realloc分配器模板 std::allocator
線程安全無(單線程調用或外部加鎖)std::string 非線程安全
小字符串優化無SSO (Small String Optimization)
轉義/引用專用函數 dynstr_append_os_quoted手動處理或 fmt::format

MySQL 選擇線性增量主要因早期版本內存碎片敏感、且許多動態串最終長度可預估(如 SQL 緩衝)。現代高性能庫多採用指數增長 + SSO,綜合吞吐更優。

應用場景建議

  • 嵌入式/內存受限環境:參考固定增量策略,避免指數增長浪費。
  • 高頻構建大字符串(SQL、JSON、日誌):預先 dynstr_realloc 預估容量,減少 realloc 次數。
  • 需安全構建 Shell 命令:直接用 dynstr_append_os_quoted 邏輯或成熟庫 libshellquote。

閱讀 MySQL 動態字符串源碼,能深刻理解「緩衝區管理、增長策略、內存所有權」這三大 C 級字符串處理核心課題。

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