7 ms·
To achieve a reasonable level of encapsulation in C, a header file must be seen as a public-only interface. It should declare only the structs that are relevant
by bluetomcat 3y ago
To achieve a reasonable level of encapsulation in C, a header file must be seen as a public-only interface. It should declare only the structs that are relevant for the user of the module. If that's "struct my_module_handle { ... }", declare it and document the corresponding accessor and modifier functions. Everything else must reside in the C source file with internal linkage (static storage class). The whole source file is your implementation.
There is an anti-pattern where header files are used for all the declarations needed internally by the source file. Including (pasting verbatim with the preprocessor) that file from another module would bring in all the unnecessary declarations.
- simias 3y agoI think what you say makes complete sense at module-level (as in, for a standalone lib for instance) but I never bother segregating things internally within a lib/module/exe and rely on good documentation and coding practices to avoid having member mutations all over the place. If I code in Rust or C++ I can use namespacing and public/private to give every single object in the codebase a clean interface, but in C doing that is just frustrating, not to mention potentially inefficient.
- hbossy 3y agoThis is how it's supposed to be done but you always end-up moving them to header just to make writing unit tests less painful.
- 10000truths 3y agoThis is a smell. Your unit tests should not have to rely on internal implementation details.
- gpderetta 3y agoAnd in the worst case you can have module-private headers. No need to pollute your interface.
- coldtea 3y agoThey don't have to and shouldn't, but it's convenient. That's the parent's point ("to make writing unit tests less painful").
- icedchai 3y agoAre unit tests common in C? In the mid-2000's, I worked on an "enterprise" system, written in C and C++. There were about 300,000 lines of code, maybe 10 tests. This thing was the core of a billion dollar business
- not2b 3y agoThat's very broken; more usual practice at the time was to have large numbers of tests, aiming for good code coverage. I've sometimes seen too many full-tool tests and few true unit tests (tests that just test functions for correctness), but "maybe 10 tests" is frightening.
- icedchai 3y agoThis particular company was very heavy on manual testing, unfortunately. (This was roughly 2003 - 2006 ish.)
- menaerus 3y agoOpaque pointers usually impose the restriction on the API such that in order to use the handle one has to dynamically allocate the object on heap. That's a quite unfortunate tradeoff IMO.
- cozzyd 3y agothere are ways around this, if VLAs are allowed. // in <opaque_foo.h> typedef struct opaque_foo opaque_foo_t; size_t opaque_foo_sz(void); void opaque_foo_init(opaque_foo_t* foo) // in your code, which you could write a helper macro for if you were so inclined char opaque_foo_mem[opaque_foo_sz()]; opaque_foo_t * my_foo = (opaque_foo_t*) opaque_foo_mem; opaque_foo_init(my_foo);
- kopecs 3y agoDoesn't this violate strict aliasing?
- cozzyd 3y agoyes, though you can fix that with compiler flags (or, #pragma if you want strict aliasing elsewhere in your code) alternatively, gcc supports VLAs in unions, but I don't think clang does, but that makes it extra annoying to do. edit: apparently you can probably apply the may_alias attribute to the type? Or you could try using transparent_union. No idea if clang supports either...
- mananaysiempre 3y agoAt that point, it is IMO better to obtain that block from alloca()[1]—it’s not standard, but where it’s available the compiler will treat the result as untyped for the purposes of aliasing. (If you’re on GCC/LLVM, __builtin_alloca_with_align is also an option, although note that the memory it returns may not outlive the current block—similar to a standard automatic variable, but unlike memory from traditional alloca.) ISO C has a gigantic hole when it comes to obtaining and recycling untyped memory, pretending the hole is not there isn’t going to help. [1] https://nullprogram.com/blog/2019/10/28/ https://nullprogram.com/blog/2019/10/28/, discussed at the time at https://news.ycombinator.com/item?id=21374863 https://news.ycombinator.com/item?id=21374863