用字节数组替代void指针以隐藏指针是否属于不良编程实践?
用字节数组/联合体隐藏内部指针的实现是否属于不良编程实践?
你提到的这种写法借鉴自Godot 3,核心是通过将内部指针包装为固定大小的字节数组(或联合体),降低用户误修改内部数据的概率。直接给出结论:这不算典型的不良编程实践,但属于一种取巧的封装手段,存在局限性,且有更规范的替代方案。
三种写法的核心差异
你给出的三种实现,目标都是隐藏一个仅允许标准库/内部函数修改的指针:
- 字节数组版本:
#ifndef FILE_DATA_SIZE #define FILE_DATA_SIZE sizeof(void *) typedef struct { u8 dont_touch_data[FILE_DATA_SIZE]; } File; #endif
- 联合体版本(兼顾内部操作便利性):
#ifndef FILE_DATA_SIZE #define FILE_DATA_SIZE sizeof(void *) typedef union { u8 dont_touch_data_array[FILE_DATA_SIZE]; void *dont_touch_data_pointer; } File; #endif
- 直接暴露指针的版本:
typedef struct { void *dont_touch_data; } File;
这种写法的合理性
它确实能达成你想要的效果:
- 直接暴露
void*时,用户可以轻松执行file->dont_touch_data = NULL;这类误操作;而字节数组版本需要用户刻意做强制类型转换(比如*(void**)file = NULL;)才能修改,大幅降低了无意误改的概率。 - 联合体版本兼顾内部实现:库函数可以直接用指针成员操作,对外仅暴露数组,避免用户直接接触指针。
这种写法的局限性
- 无法彻底阻止刻意修改:只要用户愿意,依然可以通过类型转换绕过限制,只是提高了操作门槛,做不到真正的封装。
- 可读性与维护性差:其他开发者看到这个结构体时,会困惑为什么不用直接用指针,需要额外注释说明意图,增加了团队协作成本。
- 平台兼容性隐患:依赖
sizeof(void*)与字节数组大小完全匹配,虽然绝大多数平台都符合,但在一些特殊嵌入式系统中,可能存在内存对齐或大小不一致的问题,引发潜在内存错误。 - 不符合C语言封装惯例:C语言中实现“隐藏内部状态”的标准做法是不透明指针(Opaque Pointer),而非这种取巧的包装方式。
更规范的替代方案:不透明指针
这是C语言实现封装的标准方式,能从根本上阻止用户访问内部数据:
- 在头文件中仅声明结构体类型,不暴露任何成员:
// 头文件中 typedef struct File File;
- 所有操作
File的函数(如打开、关闭、读写)都在库的实现文件中定义,内部结构体的具体成员完全对外隐藏:
// 实现文件中 struct File { void *internal_data; // 其他内部成员 }; File* fopen(const char* path, const char* mode) { File* fp = malloc(sizeof(File)); fp->internal_data = // 初始化内部指针 return fp; } int fclose(File* fp) { // 清理内部资源 free(fp); return 0; }
这种方式不仅彻底阻止了用户访问内部数据,还符合C语言的设计惯例,可读性和可维护性都更好。
总结
你提到的写法有一定实用价值,能过滤大部分无意的误修改,但并非最优解。如果追求严格的封装和规范的代码,更推荐使用不透明指针方案。
内容的提问来源于stack exchange,提问作者Henrique Moisés
相关产品推荐
相关产品推荐

