I dove into the code, literally looking at the first true part of the implementation in array/sc_array.h.
Two observations, from probably less than tree minutes of reading:
1. The nomenclature with "sc_array_term()" as the destructor (the opposite of sc_array_init()) was new to me; I'm not saying that's a real problem but it's at least adding friction in a way. I would have expected sc_array_free(), but that seems to be internally used as an alias for the standard function free(). Very confusing, since that name (sc_array_free()) then leaks into the source but it has the feeling of being meant for internal use.
2. I wonder if this has a bug:
/**
* Deletes items from the array without deallocating underlying memory
* @param a array
*/
#define sc_array_clear(a) \
do { \
(a)->cap = 0; \
(a)->size = 0; \
(a)->oom = false; \
} while (0)
In my experience, when you want to clear a dynamic array but keep the allocated memory, the 'capacity' ('cap', here) field should not be cleared. I think this will leak memory if an array is grown, cleared, and then re-filled since the sc_array_add() function will see a zero capacity, and allocate new storage.Just my inflation-devalued SEK 0.02, and I did not run the code, I just read it very quickly. Corrections welcome.
I think there shouldn't be any need to innovate in the naming of these kinds of functions... GLib got it perfectly right [0] with the pair init / finalize. One might like the alternative init / deinit if the goal is to have some symmetry in the naming... but there are not many better choices.
free is to be used (always IMHO) typically as the opposite of new, make, or create.
"Naming things is difficult" and all that...
This does look like a bug, if not functionally, then by the described intent. The comment states that the intent is to delete items without deallocating; yet zeroing the capacity cuts any future way to deallocate properly, unless it's just an array of simple types (free(baseptr)). If there are any pointer types, then there will remain no safe way to deallocate the elements' contents.
Sure, there could be other ways of keeping inventory of the allocated memory, but this detaches the array from knowing its allocated bounds.
There is one use of sc_array_clear() in the test code [1] which really makes it look as if it is being used in a way that I think (again, I haven't single-stepped this code, only read it) leaks memory.
I agree on the pain of everything being macros, it's more pain than it's worth I think and will likely lead to code duplication (and more pain in debugging, probably).
I would even go so far as to think that this kind of single-file design, where each file is independent of the others, makes it harder and more annoying to implement more complicated data structures.
[1]: https://github.com/tezc/sc/blob/master/array/array_test.c#L3...
It might not be an implementation bug, but that is just asking for bugs. Possibly very hard to find bugs.
The way I see it, because sc_array_clear resets cap and size to zero, the next call to sc_array_add will issue a realloc call to the default minimum size (8) and it won't leak because sc_array_clear doesn't overwrite elems.
So yeah, not cool but doesn't look like a bug either.
As for someone claiming so much appreciation for C it seem like your love is a bit toxic ;-) (no offense here).
Eventually I rolled my own [1] more focused library. It's basic and portable.
0: https://begriffs.com/posts/2020-08-31-portable-stable-softwa... 1: https://github.com/begriffs/libderp
I went pretty deep into composable C templates to build mine so it's more powerful than most. The containers can handle non-bitwise-movable types with full C++-style lifecycle functions and such, and the sort algorithms can handle dynamic and non-contiguous arrays (they are powerful enough to implement qsort() [1], which is more than I can say for any other C sort templates I've seen.) My reasoning for the complexity at the time was that any powerful container library is going to be reasonably complex in implementation (as anyone who's looked at STL source code knows), so it just needs to be encapsulated behind a good interface.
I'm not so sure that's true anymore. These sorts of simpler libraries like the one linked here definitely seem to be more popular among C programmers. I think if people are using C, it's not just the C++ language complexity they want to get away from, but also the implementation complexity of libraries and such. There's a balance to be had for sure, and I think the balance varies from person to person, which is why no library has emerged as the de facto standard for containers in C.
[0]: https://github.com/ludocode/pottery
[1]: https://github.com/ludocode/pottery/tree/develop/util/potter...
I decided that I wanted to be able to simply drop a single .h file and a single .c file into any project without have to build a `libBlah.so` and link it to every project that needed (for example) a hashmap.
The practical result is that using the hashmap only requires me to copy the header and source files into the calling project.
It does build as a standalone library too, so you can link it if you want.
My primary reason for starting this is that I was pretty unsatisfied with all of the string libraries for C. When all I want to do is concatenate multiple strings together, I don't want to have to convert between `char *` and `struct stringtype *` everywhere.
The string functions are very useful as they all operate on the standard `char *` (nul-terminated) type.
There are a growing number of stand alone support modules in my projects that I need to publish as a collection some day. Here's a couple links to some of them:
- https://github.com/WickedSmoke/faun/tree/master/support
- https://github.com/xu4-engine/u4/tree/master/src/support
These include more exotic stuff like a grid based field-of-view calculation and a version of the sfxr synthesizer.
C is very simple, while C++ is a monster with 100 heads, each speaking in another language.
If all members of a project use the same subset of C++, then yes, it would be preferable over C. But if anyone or any team in a large project uses whatever he wants, things can get pretty complicated, pretty fast.
More often than not, it is a matter of not wanting to use C++ than it not being available.
Yes, it can work for simple Arduino sketches, and I won't say that there are not complex embedded projects in C++.
But... why should I want to use C++?
For one, I use a small subset, like it's C with classes. I like function overloading and default argument values, initializing default values for some structs, not having to type typedef struct, the fact that for a time I could declare variables mid-function and using literal bool, true, false, etc.
But full-blown C++ on embedded (MCU)? I think I'll pass.
That's perfectly valid. I reach for C when I need to write fairly small programs. When C does not fill my needs anymore, I don't reach for C++. There are better high-level languages.
C++ is not a replacement for C. When one of the worlds foremost experts on C++ complains that the language is too complex to understand[1], passing on C++ is a reasonable position.
[1] https://steven.brokaw.org/posts/scott-meyers-cant-remember-c...
I’ve seen some janky envs in my life but this is pretty high up. Do you never enable LTO when linking deps? These devices are literally 1000x slower than a typical 8 core desktop.
As often said, apt install foo is also a bit of a package manager for C.
Maybe we should establish a sort of expert-led central archive of rock-solid, battle-tested C libs/functions/snippets that one can trust ?
Maybe we should establish a sort of expert-led central archive of rock-solid, battle-tested C libs/functions/snippets that one can trust ?
GNUlib [1], albeit marketed as a "portability library", in fact shares a lot of that goal and includes data structure implementation, OS interfaces, etc. A couple of excerpts from the docs:* "Gnulib is intended to be the canonical source for most of the important “portability” and/or common files for GNU projects. These are files intended to be shared at the source level" [3]
* "We develop and maintain a testsuite for Gnulib. The goal is to have a 100% firm interface so that maintainers can feel free to update to the code in git at any time and know that their application will not break." [3]
[1]: https://www.gnu.org/software/gnulib/manual/html_node/index.h...
[2]: https://www.gnu.org/software/gnulib/manual/html_node/Gnulib-...
[3]: https://www.gnu.org/software/gnulib/manual/html_node/High-Qu...
[1] https://github.com/rizinorg/rizin/tree/dev/librz/util
[2] https://github.com/rizinorg/rizin/tree/dev/librz/include/rz_...
Rust's traits are generics done right. I haven't looked at Concepts too much, I hope that they'll be better than templates.
C is light and simple, but don't use it much, because it can get too verbose.
C++ allows for succinct code, but it's neither light or simple. Nor does it have any concern for the elegance of its design.
Hence the common practice of using a small subset C++ and pretending it's just C with Extras.
It has a lot of inelegant facilities, which, when used under the hood, allow you to express your elegant abstractions.
> Hence the common practice of using a small subset C++ and pretending it's just C with Extras.
That's mostly failure to use C++. Since C++11, and especially with later updates to the standard, idiomatic C++ is very different from C - even if you're not using a lot of the standard library. I'll link to a talk already linked-to in another comment in this discussion:
Rich code for tiny computers / Jason Turner, CppCon 2016 https://www.youtube.com/watch?v=zBkNBP00wJE
What do you feel this content adds in context of the article? I can't tell.
Any sufficiently complicated C program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of C++.
Every C thread on every forum always has some C++ proponent jumping in to bemoan that people prefer one of the simplest languages over, literally, the most complex (from a programmer PoV) language in existence.
Is it really such a stretch to believe that people prefer readability and maintainability over expressive power?
The "lengths" in this case is small - a once of cost of a few days to implement vector and string functions gets you most of what most developers need and want.
C++ is basically almost a perfect super-set of C. Use the parts of C++ that are useful to you, don't use the parts that aren't. Clearly std::vector, std::string, std::unordered_map would be ultra useful to anyone. Especially because they are easier to use, more optimized, not as prone to errors as their C equivalents.
I blame Jonathan Blow and Muratori for this. They somehow got the entire gamedev community to worship the idea of writing things in C. People grow out of it really fast though, as soon as they actually learn C++.
Go and Rust are similar in that aspect.
I wish I could love that feature, but I hate it. I don't like the way it obscures what's going on under the hood (I find myself taking snapshots and trying to figure out "what's changed" or reverse engineer the installer metadata), and many projects use it as a crutch instead of providing a simple drop-in file (or a few). So many projects don't even include a simple "download" button anymore.
I think this poses a subtle security risk about namespacing. Who authorizes these packages? Who audits these repositories?
When you use a C/C++ library, there is obvious accountability. You know who maintains the repository (usually your distribution) or you explicitly copy someone elses code as a subrepository.
I think using the cleanup attribute, and a defer macro can make it happen.
I quickly scanned for things that stood out for me. As with anything, there are some things I disagree with.
To wit: the condition variable code includes a mutex inside of it, to deal with the case where you perform a signal on something before there is a waiter. Does this solve some problems? Sure. But it introduces extra overhead when I know what I'm doing and just want a condition variable.
These types of small things - the author thinks this is the "right thing", whereas others won't - are why no C standard C library like this exists. In C, the beauty is that you make every single decision; someone else's decisions won't be the same as yours.
https://www.kylheku.com/~kaz/kazlib.html
Used in e2fsck, Ethereal and others.
There is even C++ support hidden in there: the dict.h contains a C++ template wrapper (which keeps the container intrusive).
From my perspective, it seems like a massive time drain and non-productive use of my time. Just a few points:
- Tooling seems all over the place (build system, package management)
- Having to roll your own trivial functions / types (tooling may play into this)
- Versioning is confusing (C99, C11, ???)
The only advantage I see would be in embedded software because C is supposedly supported on a lot of platforms and is performant. But, I'm not actually sure this is true in practice.
Can you write one file of C code and compile it easily for multiple platforms or is there a lot of caveats?
Again, what do you mean by 'time drain'? Do you know how many human years were saved because 'grep' doesn't have to wait for the Java Hotspot VM to start up? If you spend an extra week to write a program that runs 10 seconds faster, you only need 14400 users to break even. If you've got a million users, you've singlehandedly saved humanity 115 _days_. That's a massive amount of time saved.
You must mean that you've got to code up some idea in whatever way possible so that it sees the light of day asap, regardless of how slow it is, how much energy it uses and how much disk space it consumes, because you need to sell something to someone. That's not what C is for.
C exists to write fast programs, not to write programs fast.
Adding phat pointers, arrays, tagged unions, and closures to C would fix a lot of the current pain points without turning it into the disaster that is C++.
What if you want to write fast programs fast?
Also, while not useful for command line tools or for small run once programs, if the software is running continuously or its size is past a certain threshold, it might pay off to use .NET or Java since the speed is not that far of from C.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Now, let's talk about how much time C++ wastes ;)
Target POSIX, and there's very few caveats. You're looking at portability as "how many processors can this run on". Think of it more as "how many places can use my library if I wrote it in C".
You're missing the major upside of writing in C: if you write your library in C, it is callable (with no changes) from Python, Ruby, Java, C++, C#, Lisp ... just about any language, really.
The language is so simple that an automated tool can be used to automatically generate bindings from your language of choice to C. I use Swig. Look into Swig.
You see C as an ancient dinosaur with no use, the reality is that it is usually the only real way to write reusable software[1].
[1] Sure, you can write in C++, but you'd still have to wrap it in C.
Don't worry, the ISO working groups are making sure that it won't last and soon writting a C compiler will become a nightmare like what they did for c++ (C23 is seriously scary).
Instead they should fix it: remove _Generic, typeof, etc which have nothing to do there, and make sure writting a C compiler does keep requiring only a reasonable amount of developement efforts (not an army of devs for 20 years like c++).
Actually, removing and hardening stuff would be more appropriate, namely not really adding stuff: remove typedef? harden and improve the type casting rules, let's be bold and eat the bullet, remove the implicit cast except to/from void* pointers and add static/dynamic casts (please not with the c++ syntax)? Make function pointers the same than the other pointers, finally (POSIX/GNU requires it though). Etc.
Have you ever written a C compiler? These things are not difficult to support. You sound more interested in just removing things that are "new" that you don't really like than genuinely concerned about things that are complex to implement, like passing structs around while keeping the platform ABI in mind, and handling the intricacies of bitfields.
Bye bye ehhh. Linux, Windows, OS X, Postgres, Android, Python, Sqlite, Redis...
I know now that the association only really goes as far as who maintains it, but if it wasn't for how it was branded, I probably would have been using it in a lot more of my projects.
Sounds kind of stupid, but never underestimate how branding can affect how your project gets used. Especially by people who are unfamiliar with it.