A common pattern when providing a C API is to forward declare some opaque types in your public header which are passed to your API methods and then reinterpret_cast
them into your defined C++ types once inside the translation unit (and therefore back in C++ land).
In Types.h this typedef is declared:
typedef struct LLVMOpaqueContext *LLVMContextRef;
LLVMOpaqueContext
is not referenced anywhere else in the project.
In Core.h the following method is declared:
LLVMContextRef LLVMContextCreate(void);
Which is defined in Core.cpp:
LLVMContextRef LLVMContextCreate() { return wrap(new LLVMContext()); }
wrap
(and unwrap
) is defined by a macro in 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)); \ }
And used in LLVMContext.h:
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(LLVMContext, LLVMContextRef)
So we see that the C API basically takes a pointer to an LLVMOpaqueContext
and casts it into an llvm::LLVMContext
object to perform whatever method is called on it.
My question is: isn't this in violation of the strict aliasing rules? If not, why not? And if so, how can this type of abstraction at the public interface boundary be acheived legally?
In C, C++, and some other programming languages, the term aliasing refers to a situation where two different expressions or symbols refer to the same object.
Pointer aliasing is a hidden kind of data dependency that can occur in C, C++, or any other language that uses pointers for array addresses in arithmetic operations. Array data identified by pointers in C can overlap, because the C language puts very few restrictions on pointers.
It's not a strict aliasing violation. To start with, strict aliasing is about accessing an object via a glvalue of the wrong type.
In your question, you create a LLVMContext
, and then use a LLVMContext
lvalue to access it. No illegal aliasing there.
The only issue which may arise is if the the pointer conversion doesn't yield back the same pointer. But that too is not a problem, since reinterpret_cast
is guaranteed to give back the same pointer in a round-trip conversion. So long as the pointer type we convert to and back from is to suitably aligned data (i.e. not stricter than the original type).
Whether or not it's a good or bad way to go about things is debatable. I personally would not bother with LLVMOpaqueContext
and return a struct LLVMContext*
. It's still an opaque pointer, and it doesn't matter that the C header declares it with struct
while the type definition is with class
. The two are interchangeable up to the point of the type definition.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With