MySQL 內部大量使用動態字符串構建 SQL、協議包、日誌等。其核心結構 DYNAMIC_STRING(定義於 sql/dynstr.h)通過「緩衝區 + 增長策略」實現高效內存管理,避免頻繁 malloc/free。本文深度解析其結構定義、初始化、追加擴容、截斷、轉義等關鍵操作源碼邏輯(基於 MySQL 5.6/5.7 代碼庫)。
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 控制擴容步長,平衡內存碎片與擴容次數。
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。my_malloc(帶錯誤追蹤、統計)。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 次數多於指數增長。
my_bool dynstr_trunc(DYNAMIC_STRING *ds, size_t n) {
ds->length -= n;
ds->str[ds->length] = '\0';
return FALSE;
}
僅移動邏輯長度指針,不歸還內存,極快。適用於解析協議時丟棄已處理前綴。
char *strcend(const char *s, char c) {
for (;;) {
if (*s == c) return (char*)s;
if (!*s++) return (char*)s - 1; // 到達 \0 返回結尾指針
}
}
返回首次出現字符 c 的地址,或字符串結尾 \0 地址。用於轉義處理中定位引號位置。
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 觸發多次擴容。
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()。
| 特性 | 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,綜合吞吐更優。
dynstr_realloc 預估容量,減少 realloc 次數。dynstr_append_os_quoted 邏輯或成熟庫 libshellquote。閱讀 MySQL 動態字符串源碼,能深刻理解「緩衝區管理、增長策略、內存所有權」這三大 C 級字符串處理核心課題。