明明确定数据类型,为何仍在C函数参数中使用void *ptr?
void*作为函数参数? 在C语言里,哪怕你觉得数据类型是确定的,很多库(比如libcurl)还是会用void*做参数,核心原因都是围绕API的通用性、解耦能力和语言特性来的,具体拆解成这几点:
让API通用化,适配所有业务场景
libcurl的回调接口不是为你写的MemoryStruct单独设计的。如果把参数固定成MemoryStruct*,那其他用户想把数据写入文件(用FILE*)、写入网络缓冲区(自定义另一个结构体),就没法用同一个回调接口了。用void*相当于给接口留了个“通用插槽”,任何用户自定义的数据结构都能塞进去,一套接口就能适配所有可能的写入场景,不用为每种需求单独做接口。彻底解耦库代码和业务代码
libcurl作为底层网络库,根本不需要知道你上层用什么结构体存数据。如果参数是具体的结构体指针,libcurl的头文件就得包含你的结构体定义,这就把库和你的业务代码绑死了。用void*的话,库只需要负责把指针原样传递,完全不用关心指针指向的是什么,你自己在回调里转成实际类型就行,库和业务代码之间没有任何依赖,各自改各自的都不影响。用C语言模拟泛型逻辑
C语言本身没有原生泛型,void*是实现泛型最常用的手段。libcurl的回调本质上是一个“通用写入钩子”——不管你要写的数据存在哪里,只要你自己能处理这个指针,接口就能工作。这就像其他语言里的泛型参数,让同一个函数逻辑能处理任意类型的数据。保障API的向后兼容性
如果一开始接口就用了具体类型的指针,后续要支持新的数据类型就得修改接口,旧代码直接就用不了了。而void*的接口一旦定下来,不管以后用户搞出什么新的数据结构,都能直接适配,不会破坏现有代码的兼容性,这对长期维护的开源库来说至关重要。
拿你说的libcurl例子来讲:你通过curl_easy_setopt给CURLOPT_WRITEDATA传了MemoryStruct*,libcurl根本不管这个指针是什么类型,只是在回调的时候原封不动把它传给userp。你在自己的WriteMemoryCallback里把它转成MemoryStruct*,这完全是你自己的业务逻辑,和libcurl的核心代码没关系。要是你想改成写入文件,只需要传FILE*给CURLOPT_WRITEDATA,然后在回调里把userp转成FILE*就行,接口完全不用变。
内容的提问来源于stack exchange,提问作者Voltere

