void* 是 C 語言實現泛型編程、內存操作、回調接口的基石。它代表「無類型指針」,既是靈活性的來源,也是類型安全的隱患。本文系統梳理語法規則、算術限制、標準庫應用模式與工程級最佳實踐。
| 操作 | 合法性 | 說明 |
|---|---|---|
任意指針 → void* | ✅ 隱式轉換 | 無需強轉,void* p = int_ptr; |
void* → 任意指針 | ⚠️ 需顯式強轉 | int* p = (int*)void_ptr; (C++ 強制,C 允許隱式但不推薦) |
void* 算術 (+/-) | ❌ ANSI C 非法 | 編譯器不知類型大小,無法計算步長 |
void* 解引用 (*) | ❌ 非法 | 同上,不知解引用大小 |
| 比較 (==/!=) | ✅ 合法 | 比較地址值 |
void *p = malloc(100); p++; // ANSI: 錯誤 | GNU: 合法 (視為 char*, 步長 1) p += 5; // ANSI: 錯誤 | GNU: 合法 (char*)p++; // ANSI: 合法 | GNU: 合法 (推薦寫法,可移植)
工程建議:嚴格遵循 ANSI,所有指針算術先強轉為 char* 或目標類型。
內存操作函數純粹操作字節,不關心語義,故參數為 void*:
void *memcpy(void *dest, const void *src, size_t n); void *memset(void *ptr, int value, size_t num); void *memmove(void *dest, const void *src, size_t n); int memcmp(const void *s1, const void *s2, size_t n);
分配器返回 void*,調用者賦予語義:
int *arr = (int*)malloc(N * sizeof(int)); // 強轉聲明:這塊內存存 int // 或 C99 複合字面量 (不推薦濫用) int *arr = malloc(N * sizeof *arr); // sizeof *arr 自動推導類型大小
// 通用比較函數類型
typedef int (*CmpFunc)(const void *a, const void *b);
// qsort 簽名
void qsort(void *base, size_t nmemb, size_t size, CmpFunc cmp);
// 使用示例:整數數組排序
int cmp_int(const void *a, const void *b) {
int ia = *(const int*)a;
int ib = *(const int*)b;
return (ia > ib) - (ia < ib); // 避免溢出
}
int main() {
int a[] = {5, 2, 9, 1};
qsort(a, 4, sizeof(int), cmp_int);
// a 變為 {1, 2, 5, 9}
}
#define PRINT_VAL(x) _Generic((x), \
int: printf("%d\n", x), \
double: printf("%f\n", x), \
char*: printf("%s\n", x), \
default: printf("unknown\n") \
)
// 用法
PRINT_VAL(42); // 調用 int 分支
PRINT_VAL(3.14); // 調用 double 分支
PRINT_VAL("hello"); // 調用 char* 分支
void* 陣列時,需並行存儲類型標籤(Tagged Union)。void* user_data 傳遞上下文,確保生命週期覆蓋回調執行期。void a; 定義變量(編譯錯誤:illegal use of type 'void')。void* 做 sizeof(結果為 1,GNU 擴展)。void* 類似「抽象基類指針」:
void 變量 ↔ 不能實例化抽象類。static_cast 回具體類型 ↔ dynamic_cast / 虛函數調用。掌握 void* 的本質與邊界,是從「會用庫函數」進化到「能設計通用庫接口」的關鍵一步。記住:無類型不代表無責任,強轉瞬間即承擔類型正確性契約。