为您的C++库提供C API并严格别名

Sam*_*ett 25 c c++ llvm strict-aliasing

提供C API时的一个常见模式是在公共头中转发声明一些不透明的类型,这些类型传递给您的API方法,然后reinterpret_cast在翻译单元内转换为定义的C++类型(因此返回C++版本).

以LLVM为例:

Types.h中,声明了这个typedef:

typedef struct LLVMOpaqueContext *LLVMContextRef;
Run Code Online (Sandbox Code Playgroud)

LLVMOpaqueContext 未在项目中的任何其他位置引用.

Core.h中,声明了以下方法:

LLVMContextRef LLVMContextCreate(void);
Run Code Online (Sandbox Code Playgroud)

这是在Core.cpp中定义的:

LLVMContextRef LLVMContextCreate() {
  return wrap(new LLVMContext());
}
Run Code Online (Sandbox Code Playgroud)

wrap(和unwrap)由CBindingWrapping.h中的宏定义:

#define DEFINE_SIMPLE_CONVERSION_FUNCTIONS(ty, ref)     \
  inline ty *unwrap(ref P) {                            \
    return reinterpret_cast<ty*>(P);                    \
  }                                                     \
                                                        \
  inline ref wrap(const ty *P) {                        \
    return reinterpret_cast<ref>(const_cast<ty*>(P));   \
}
Run Code Online (Sandbox Code Playgroud)

并在LLVMContext.h中使用:

DEFINE_SIMPLE_CONVERSION_FUNCTIONS(LLVMContext, LLVMContextRef)
Run Code Online (Sandbox Code Playgroud)

所以我们看到C API基本上接受一个指针LLVMOpaqueContext并将其转换为一个llvm::LLVMContext对象,以执行在其上调用的任何方法.

我的问题是:这不违反严格的别名规则吗?如果没有,为什么不呢?如果是这样,那么在公共接口边界上的这种抽象如何能够合法地实现呢?

Sto*_*ica 22

这不是严格的别名违规.首先,严格别名是通过错误类型的glvalue访问对象.

在您的问题中,您创建一个LLVMContext,然后使用LLVMContext左值来访问它.那里没有非法走样.

可能出现的唯一问题是指针转换是否不会返回相同的指针.但这也不是问题,因为reinterpret_cast保证在往返转换中返回相同的指针.只要我们转换为和返回的指针类型是适当对齐的数据(即不比原始类型更严格).

无论是好事还是坏事都是值得商榷的.我个人不会打扰LLVMOpaqueContext并返回struct LLVMContext*.它仍然是一个不透明的指针,并且struct在类型定义为的时候C头声明它并不重要class.这两者可以互换,直到类型定义.

  • @melpomene - 无效的指针值也是C++中的一个东西.但我认为语言律师明智地说,在严格别名违规和使用无效地址之间UB的原因有所不同(就像你在你的片段中使用它们一样). (3认同)
  • @ArneVogel - 我想Clang因为与MSVC的互操作性而选择了它.据我所知,微软的名字受损了.但我不认为这种约束返回指针的情况可能会导致问题,因此可以在定义类的TU中关闭警告.谢谢你提出来.我实际上并不知道Clang到现在为止这样做了. (2认同)
  • @HolyBlackCat:3.9.2 p3 in [n4296](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf)"指向cv-qualified的指针(3.9. 3)或cv-unqualified void可用于指向未知类型的对象.**这样的指针应能保存任何对象指针.**类型为`cv void*`的对象应具有相同的表示和对齐方式要求为`cv char*`." (我的重点).请注意,此分配特定于char*和void*. (2认同)