调用旧版 C API 时,如何在现代 C++ 中正确地将指向结构的指针转换为指向其他结构的指针?

Posted

技术标签:

【中文标题】调用旧版 C API 时,如何在现代 C++ 中正确地将指向结构的指针转换为指向其他结构的指针?【英文标题】:How to correctly cast a pointer-to-struct to pointer-to-other-struct in modern C++ when calling legacy C API? 【发布时间】:2017-09-01 22:13:19 【问题描述】:

在调用诸如getnameinfo 之类的传统 C API 时,现代 C++ 中正确将指向结构的参数转换为指向另一个结构的指针的正确方法是什么,它具有类似的布局,但不是'与原始结构的类层次结构无关?

例如,getnameinfo 的第一个参数接受 const struct sockaddr *,但在将 const struct sockaddr_storage * 传递给它时它也应该可以工作。但是,AFAIK 只是 reinterpret_cast-ing 指针被 C++ 标准视为 UB。

在上面的示例中,给定一个sockaddr_storage 对象,将指向它的指针传递给getnameinfo 的正确方法是什么?

【问题讨论】:

可能是UB,但也无关紧要。做演员就行了套接字 API 需要一个指针,仅此而已。不用你担心。作为一个 C 结构,你可以期望它只是同一个地址(没有虚拟成员或继承或任何类似的东西)。 单独投射指针绝不是 UB。只有 访问 指针 - 库调用边界是不透明的,因此不会引起问题。 同样,这里也适用“通用初始子序列”规则。 @o11c 引用您引用的规则?我所能找到的只是关于两个结构在联合中声明的情况,这里不是这种情况。 【参考方案1】:

我可能误读了标准,但由于这些类型是标准布局类型,如果您访问它们的公共初始子序列,则不是 UB。我相信[class.mem/23] 是为什么:

在具有结构类型 T1 的活动成员的标准布局联合中,允许读取结构类型 T2 的另一个联合成员的非静态数据成员 m,前提是 m 是 T1 的公共初始序列的一部分,并且T2;就好像T1的相应成员被提名了一样。

这是对联合的明确许可,理论上可以简单地通过编译器魔法来完成。但实际上我认为这意味着对于标准布局类,公共初始序列在内存中具有相同的布局。

现在让我们考虑[basic.lval/8.6]:

如果程序尝试通过以下方式访问对象的存储值 行为是以下类型之一以外的左值 未定义:

... 在其元素或非静态数据成员中包含上述类型之一的聚合或联合类型(包括, 递归地,子聚合的元素或非静态数据成员或 包含联合), ...

当您取消引用重新解释的指针时,您将获得一个聚合的左值,其中包括您访问的成员的类型。由于该成员属于公共初始序列,所以一切都应该很好。

我怀疑是否存在一个理智的实现,这将无法跨越库边界。

【讨论】:

以上是关于调用旧版 C API 时,如何在现代 C++ 中正确地将指向结构的指针转换为指向其他结构的指针?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 c# 应用程序中正确保护 api 凭据

将 libfmt 与旧版 API 一起使用

我是不是在 C++ 中正确实现了欧拉方法?

C++ Visual Studio 运行时错误

从C ++代码成功进行C#回调后,“运行时检查失败#0 - ESP的值未在函数调用中正确保存”

按下回车按钮时如何使用 C++ Win32 API 调用按钮?