In danger of immediately sounding “pompous”, I will bravely postulate, that most developers just simply do not understand how distant is so-called “Modern C”, from what they usually perceive as C programming language.
Allow me to push you into the deep end of the pool, straight away. Here is one sample I prepared earlier:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
int main(void) { // each array is a struct of two members: size and data // array size is given at runtime // _auto_cleanup_ is exactly what it says it is float_array* _auto_cleanup_ f255 = new_float_array(0xFF); // put a value into the array at index 0 f255->data[0] = 3.14; // for each array type, new and copy functions are provided // copy will be also auto freed float_array* _auto_cleanup_ f255copy = copy_float_array(f255); // check if values as index 0 are equal assert(f255->data[0] == f255copy->data[0]); //string is an char array, of a known size char_array* _auto_cleanup_ c255 = new_char_array(0xFF); // c255->size is 255 here c255->data[0] = '!'; // quick string copy char_array* _auto_cleanup_ c255copy = copy_char_array(c255); assert(c255->data[0] == c255copy->data[0]); //arrays of user defined types are also possible my_type_array _auto_cleanup_ * mta = new_my_type_array(0xF); // no need to manualy free anything before exiting return 42; } |
Here is my formula:
Modern C = Standard C + ( GNUC | clang )
- Standard C is C17 as of today, managed by the WG14 committee.
- GNUC is well-known (and still) the reference compiler for the C programming language.
- clang is a “C language” compiler of the LLVM suite
So why this “GNUC or clang” part of the formula? Simply because these two compilers have developed a powerful set of C extensions still staying true to the standard as much as possible. The reasons are purely pragmatic. Dramatically improved code resilience, safety, and scalability. And developed by teams understanding it is a waste of time trying to replace C.
We shall use SystemD open-source project as an optimal use case. It is a well-known, complex, mature, and brilliant C program. Studying its source will make you understand (sooner or later) what is Modern C. What are the reasons for the existence of (in this case) clang attributes, and how that philosophy makes SystemD first possible and second, the software part that is crucial for modern Linux. (hint: yes clang and GNUC attributes are almost the same.)
There are many seemingly equally fit for the task, programming languages in existence; yet somehow modern C is the most feasible one for the task at hand: mission-critical system software. The keyword is: feasible.
I might think in that little sample of mine I have managed to show how are attributes, macros, and the rest of the C paraphernalia used today. Where the key assumption is: you want the fastest and safest possible core code, but still not in assembler, and actually maintainable by someone years or decades after you. As a part of some living systems. Hint: no I do not claim that little code is something never seen before. I simply claim it shows the power of modern C.
For now and for the curious, I will present (hand cleaned) the output of the precompiler from the code behind that link:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
/* this is used as mandatory argument for the: TYPE __attribute__((__cleanup__(CLEANER_FUNCTION))) VAR_NAME = HEAP_ALLOCATED_SIZE where __attribute__ and __cleanup__ are clang and GNUC keywords. the trick: signature of the CLEANER_FUNCTION, should be `TYPE **` but it is not. compiler sees `void *` and obediently passes any pointer as cleanup_free argument inside we cast back to `void **` and then dereference that to `void *` the proper argument for the free(void *) standard heap deallocation function. */ static inline void cleanup_free(void* p) { free(*(void**)p); } /* Here we use: Flexible Array Member (FAM), feature of standard C https://www.geeksforgeeks.org/flexible-array-members-structure-c/ This give us structure that holds array and its size, mandatory to be defined at the moment of creating the instance of this struct. Thus we shall have full array description, of any size, of the type float */ typedef struct floatS_ { size_t size; float data[]; } float_array; /* size is here decided at runtime, not compile time and then dynamicaly allocated note the result can be NULL if calloc fails. */ static inline float_array * new_float_array(unsigned size_) { float_array* retval = calloc(1, sizeof(float_array) + sizeof(float[size_])); if (retval) retval->size = size_; return retval; } /* FAM is not copied if we copy the struct instances ditto we do the copying `by hand` */ static inline float_array * copy_float_array(float_array * src_) { // this is assert macro expanded ((src_) ? (void) (0) : __assert_fail ("src_", "/app/example.c", 51, __extension__ __PRETTY_FUNCTION__)); // declare, define and allocate the pointer to be returned // notice how we calculate the size of the instance to be copied float_array* retval = calloc(1, sizeof(float_array) + sizeof(float[src_->size])); // copy the lot, including the FAM, to the result, if calloc has not failed if (retval) { memcpy(retval, src_, sizeof(float_array) + src_->size); } return retval; }; /* we will not explain the same as above but for the array of chars */ int main(void) { float_array* __attribute__((__cleanup__(cleanup_free))) f255 = new_float_array(0xFF); f255->data[0] = 3.14; float_array* __attribute__((__cleanup__(cleanup_free))) f255copy = copy_float_array(f255); ((f255->data[0] == f255copy->data[0]) ? (void) (0) : __assert_fail ("f255->data[0] == f255copy->data[0]", "/app/example.c", 63, __extension__ __PRETTY_FUNCTION__)); return 42; } |
Quite interesting isn’t it? (hint: here is that “auto-free” discussion; also the char array is the same as float obviously but for a type char
)
As “we go along”, I will be quite happy to explain and answer all your questions.
PS: Please see here the discussion on the size of the VLA struct to be allocated.
Why would anybody code like this?
Safe, resilient, and fast modern C code. Resilience is economical. Debugging takes time and time is money.