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

时间:2018-03-12 11:25:41

标签: c++ c llvm strict-aliasing

提供C API时的一个常见模式是在您的公共标头中转发声明一些opaque类型,这些类型传递给您的API方法,然后在翻译单元内reinterpret_cast将它们转换为您定义的C ++类型(因此回到C ++领域。)

以LLVM为例:

Types.h中声明了这个typedef:

typedef struct LLVMOpaqueContext *LLVMContextRef;

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

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

LLVMContextRef LLVMContextCreate(void);

Core.cpp中定义:

LLVMContextRef LLVMContextCreate() {
  return wrap(new LLVMContext());
}

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));   \
}

并在LLVMContext.h中使用:

DEFINE_SIMPLE_CONVERSION_FUNCTIONS(LLVMContext, LLVMContextRef)

因此我们看到C API基本上采用指向LLVMOpaqueContext的指针并将其转换为llvm::LLVMContext对象,以执行在其上调用的任何方法。

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

1 个答案:

答案 0 :(得分:22)

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

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

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

无论是好事还是坏事都是有争议的。我个人不会打扰LLVMOpaqueContext并返回struct LLVMContext*。它仍然是一个不透明的指针,当类型定义为struct时,C标头用class声明它并不重要。这两者可以互换,直到类型定义。