dlsym()手册页中的规避方案是否仍存在出错风险?
1. 规避方案在特殊架构下的风险
你关注的那个*(void **) (&cosine) = dlsym(handle, "cos");规避方案,在函数指针与数据指针大小不同、或是函数指针需要多值表示的架构上,确实和直接转换函数指针为void*一样有风险。
道理很直白:dlsym()返回的void*是数据指针,如果函数指针的内存布局和数据指针不匹配——比如函数指针占更大空间,或者需要存储代码段标识这类额外信息——把void*直接写到函数指针的内存里,必然会导致信息缺失或截断,最终得到的函数指针是无效的,调用时照样触发未定义行为。
2. 为什么标准没将该规避方案定为未定义行为?
C标准没把这个操作归为未定义行为,是因为它只约束类型转换的合法性,不保证赋值后的结果能正常使用:
&cosine是指向函数指针的指针,把它转成void**是合法的——C标准允许任何对象指针转换为void*相关的指针类型。- 解引用
void**并赋值dlsym()的返回值,只是把一个void*的值写入cosine的内存地址,这个操作本身符合C语法规范。
但标准不保证赋值后的函数指针能正常调用,这属于“实现定义行为”而非严格的未定义行为。POSIX当初推荐这个方案,是因为主流架构(x86、ARM等)里函数指针和数据指针的大小、表示方式完全一致,这个方案既能绕过编译器警告,实际使用也不会出问题。
3. 能否实现无出错风险的dlsym()?
理论上,要彻底规避所有架构的风险,dlsym()应该返回专用的通用函数指针类型,而非void*。但POSIX设计dlsym()时,C标准还没有统一的泛型函数指针规范,只能用void*作为“万能指针”来兼容各种场景。
不过从POSIX.1-2008的2013年技术修正(TC1)开始,已经要求符合标准的实现必须支持void*与函数指针之间的转换——也就是说,在遵循该标准的系统上,两种转换方式(直接强制转换或规避方案)都是安全的。
至于那些函数指针与数据指针不兼容的特殊架构,要么系统本身不遵循POSIX的这个修正要求,要么会提供专门的扩展接口处理动态加载的函数指针,这时候就需要针对架构做特殊适配了。
内容的提问来源于stack exchange,提问作者Danila Berezin

