Last active
December 14, 2017 08:01
-
-
Save uucidl/abc25729ae775fcf45bf094b14e2cdb8 to your computer and use it in GitHub Desktop.
Opaque struct example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
/* @language: c11 */ | |
#include <stddef.h> | |
#include <stdint.h> | |
#if defined(__cplusplus) | |
#define ARRAY_ALIGNAS alignas(8) | |
#else | |
#include <stdalign.h> | |
#define ARRAY_ALIGNAS _Alignas(8) | |
#endif | |
/** | |
* Opaque representation which allows creation on the stack, allocation etc. | |
* | |
* NOTE: This isn't how I would write an array type. I wouldn't make the members | |
* of such type opaque, in general. This is an example which I would use for types | |
* that need opacity, such as fat platform layers (access to audio APIs, databases) | |
* holding resources, or for types that would be slow to compile. | |
* | |
* Pros: | |
* - collaborating types are not leaked, | |
* - no forward declarations necessary, | |
* - implementation details are hidden | |
* | |
* I.e. hopefully you get good compilation insulation. | |
* | |
* Cons: | |
* - size has to be adjusted as needed (which might be a pro as it makes | |
* you aware of the size of a given data-type) | |
*/ | |
struct Array | |
{ | |
ARRAY_ALIGNAS uint8_t data[sizeof (uint8_t*) + 2*8]; | |
}; | |
size_t ArraySize(struct Array* array, size_t element_size); | |
void* ArrayAllocate(struct Array* array, size_t element_size, size_t index); | |
void* ArrayGet(struct Array* array, size_t element_size, size_t index); | |
#undef ARRAY_ALIGNAS |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern "C" { | |
#include "00array.h" | |
} | |
// type-safe interface to array.h | |
// | |
// This shows you can combine a type-erased core implementation and a safe, templated interface. | |
template <typename T> | |
struct SafeArray : Array | |
{ | |
friend size_t ArraySize(SafeArray* self) { | |
return ArraySize(static_cast<Array*>(self), sizeof (T)); | |
} | |
friend T* ArrayAllocate(SafeArray* self, size_t index) { | |
return static_cast<T*>(ArrayAllocate(static_cast<Array*>(self), sizeof (T), index)); | |
} | |
friend T* ArrayGet(SafeArray* self, size_t index) { | |
return static_cast<T*>(ArrayGet(static_cast<Array*>(self), sizeof (T), index)); | |
} | |
}; | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "00array.hpp" | |
#include <string> | |
int main(int argc, char** argv) | |
{ | |
SafeArray<char*> args = {}; | |
for (int i = 0; i < argc - 1; ++i) | |
{ | |
*ArrayAllocate(&args, i) = argv[1 + i]; | |
} | |
for (int i = 0; i < ArraySize(&args); ++i) | |
{ | |
std::printf("%d - %s\n", i, *ArrayGet(&args, i)); | |
} | |
return 0; | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -x | |
clang++ -m32 -g -Wall -Werror -std=c++11 -fsanitize=address 00main.cpp array.cpp -o array_ex32 | |
clang++ -m64 -g -Wall -Werror -std=c++11 -fsanitize=address 00main.cpp array.cpp -o array_ex64 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @language: c++11 | |
// | |
// implementation of the `array.h` interface in c++ | |
extern "C" { | |
#include "00array.h" | |
} | |
#include <cstdlib> | |
#include <cstring> | |
struct ArrayImpl | |
{ | |
uint8_t* start; | |
uint64_t size; | |
uint64_t capacity; | |
}; | |
static_assert(sizeof (ArrayImpl) == sizeof (Array), "opaque struct needs to conform"); | |
// we use the memcpy idiom to do type punning between the opaque and the | |
// transparent representation. | |
// | |
// Compilers will remove the extra `memcpy` when optimizing this code, so this | |
// effectively is kind of reinterpret_cast that's impervious to the | |
// strict-aliasing rule. | |
size_t ArraySize(struct Array* array, size_t element_size) | |
{ | |
ArrayImpl self; | |
std::memcpy(&self, array, sizeof (ArrayImpl)); | |
return self.size / element_size; | |
} | |
void* ArrayAllocate(struct Array* array, size_t element_size, size_t index) | |
{ | |
ArrayImpl self; | |
std::memcpy(&self, array, sizeof (ArrayImpl)); | |
auto const needed_size = element_size * (1 + index); | |
if (needed_size > self.capacity) { | |
auto next_capacity = 2*needed_size; | |
self.start = static_cast<uint8_t*>( | |
std::realloc(self.start, next_capacity)); | |
self.capacity = next_capacity; | |
} | |
if (needed_size > self.size) { | |
self.size = needed_size; | |
} | |
std::memcpy(array, &self, sizeof (Array)); | |
return self.start + element_size * index; | |
} | |
void* ArrayGet(struct Array* array, size_t element_size, size_t index) | |
{ | |
ArrayImpl self; | |
std::memcpy(&self, array, sizeof (ArrayImpl)); | |
return self.start + element_size * index; | |
} |
Another example of application of this technique from Bx / Bgfx:
Also see this thread:
https://twitter.com/bkaradzic/status/940999711976144896
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Inspired by http://ourmachinery.com/post/physical-design/ "Physical Design of The Machinery"
And https://twitter.com/niklasfrykholm/status/848920522226315264