skeeto / w64devkit

Portable C and C++ Development Kit for x64 (and x86) Windows
The Unlicense
3k stars 211 forks source link

add libcsptr? #31

Closed icls1337 closed 1 year ago

icls1337 commented 2 years ago

https://github.com/Snaipe/libcsptr

skeeto commented 2 years ago

Thanks for the suggestion, but I am not interested in including this library. In general my focus is distributing tools, not libraries (beyond Mingw-w64 as a foundational C runtime). I considered including zlib, one of the most widely-used general purpose libraries, but ultimately decided against it. That establishes baseline for inclusion.

First, I don't want to be in involved in supplying dependencies. Which version should I include? How should it be built? Since they're linked in arbitrary contexts, I have to consider security ramifications. I'm likely to patch it just as I patch the tools in w64devkit, so it will diverge from expectation, which will be more noticeable with libraries than tools, and may require support.

Second, developers can mostly build these dependencies themselves. They have the very tools for it in their hands! Then they can answer all the above questions as they wish. For example, they can produce a debug build of the library — debug symbols, UBSan, assertions, etc. — which I wouldn't supply. (Very relevant in this case as I'll discuss in a moment.) For example, despite the overly-complicated CMake build in the libcsptr repository, it's trivial to build in w64devkit without CMake. These commands build both a shared and static library (release build):

$ touch include/csptr/config.h $ cc -c -g3 -DNDEBUG -Os -Iinclude/csptr src/.c $ cc -shared -s -o csptr.dll .o $ ar r csptr.a *.o

Many libraries like, zlib and SQLite, are even easier to build. A few are quite a bit harder, such as SDL, and you'll probably just need to download an official build.

If I included such libraries in w64devkit, they'd be on the include path and linkable with "-l". You could get the same by copying the headers and build artifacts under w64devkit, though that's fragile and will get wiped out by upgrades. Instead I suggest creating a "library root" and pointing GCC there with C_INCLUDE_PATH, CPLUS_INCLUDE_PATH, and LIBRARY_PATH (i.e. set them in your .profile.) On my primary system I have zlib "installed" this way. It would also work with libcsptr.

Third, w64devkit is in large part capturing my personal preferences and workflows. I use it a lot and I use nearly every tool in one way or another. (If there's something I don't recognize, I learn how to use it, and then either it's useful or I delete it!) And I hardly use libraries beyond system-provided DLLs. I mentioned zlib, but I don't even use that enough to justify distributing it in w64devkit.

With that out of the way I have some specific libcsptr criticisms. Even if I was distributing libraries I still wouldn't include libcsptr. Foremost, smart pointers are a mistake, and they steer developers towards individual element thinking and managing individual lifetimes. My favorite links on this topic:

https://www.youtube.com/watch?v=f4ioc8-lDc0&t=4407s https://www.rfleury.com/p/untangling-lifetimes-the-arena-allocator

Additionally, the library doesn't meet my quality standards. The tests and the "smart array" example in the README trigger UBSan due to passing null to memcpy. If you make a UBSan debug build of libcsptr (see the w64devkit README for details) — which is what I would use during development — this will show up the moment you create a smart array. Easy to fix, but nobody noticing it all these years despite being easy to catch is a red flag. Perfect example of something I would patch. Also, it uses a VLA/alloca temporary — another red flag — and so you can't safely allocate a smart pointer for any object larger than a MB or so without it blowing the stack. That's something else I would patch. I found those in the first few minutes, and if I spent more time on it I expect I'd find more.

icls1337 commented 1 year ago

Thanks for the suggestion, but I am not interested in including this library. In general my focus is distributing tools, not libraries (beyond Mingw-w64 as a foundational C runtime). I considered including zlib, one of the most widely-used general purpose libraries, but ultimately decided against it. That establishes baseline for inclusion. First, I don't want to be in involved in supplying dependencies. Which version should I include? How should it be built? Since they're linked in arbitrary contexts, I have to consider security ramifications. I'm likely to patch it just as I patch the tools in w64devkit, so it will diverge from expectation, which will be more noticeable with libraries than tools, and may require support. Second, developers can mostly build these dependencies themselves. They have the very tools for it in their hands! Then they can answer all the above questions as they wish. For example, they can produce a debug build of the library — debug symbols, UBSan, assertions, etc. — which I wouldn't supply. (Very relevant in this case as I'll discuss in a moment.) For example, despite the overly-complicated CMake build in the libcsptr repository, it's trivial to build in w64devkit without CMake. These commands build both a shared and static library (release build): $ touch include/csptr/config.h $ cc -c -g3 -DNDEBUG -Os -Iinclude/csptr src/.c $ cc -shared -s -o csptr.dll .o $ ar r csptr.a .o Many libraries like, zlib and SQLite, are even easier to build. A few are quite a bit harder, such as SDL, and you'll probably just need to download an official build. If I included such libraries in w64devkit, they'd be on the include path and linkable with "-l". You could get the same by copying the headers and build artifacts under w64devkit, though that's fragile and will get wiped out by upgrades. Instead I suggest creating a "library root" and pointing GCC there with C_INCLUDE_PATH, CPLUS_INCLUDE_PATH, and LIBRARY_PATH (i.e. set them in your .profile.) On my primary system I have zlib "installed" this way. It would also work with libcsptr. Third, w64devkit is in large part capturing my personal preferences and workflows. I use it a lot* and I use nearly every tool in one way or another. (If there's something I don't recognize, I learn how to use it, and then either it's useful or I delete it!) And I hardly use libraries beyond system-provided DLLs. I mentioned zlib, but I don't even use that enough to justify distributing it in w64devkit. With that out of the way I have some specific libcsptr criticisms. Even if I was distributing libraries I still wouldn't include libcsptr. Foremost, smart pointers are a mistake, and they steer developers towards individual element thinking and managing individual lifetimes. My favorite links on this topic: youtube.com/watch?v=f4ioc8-lDc0&t=4407s rfleury.com/p/untangling-lifetimes-the-arena-allocator Additionally, the library doesn't meet my quality standards. The tests and the "smart array" example in the README trigger UBSan due to passing null to memcpy. If you make a UBSan debug build of libcsptr (see the w64devkit README for details) — which is what I would use during development — this will show up the moment you create a smart array. Easy to fix, but nobody noticing it all these years despite being easy to catch is a red flag. Perfect example of something I would patch. Also, it uses a VLA/alloca temporary — another red flag — and so you can't safely allocate a smart pointer for any object larger than a MB or so without it blowing the stack. That's something else I would patch. I found those in the first few minutes, and if I spent more time on it I expect I'd find more.

I'm just a little curious as to why you seem to prefer C over CPP?

skeeto commented 1 year ago

I'm just a little curious as to why you seem to prefer C over CPP?

There's little in C++ worth using that isn't already in C. Most features are either junk or not worth the trade-offs. When I say "Modern C++" below I mean typical template-heavy, smart pointers C++11 (or later). Going through C features I particularly value:

I like quick iterations and fast turnaround, not wasting my time waiting on tools. GCC 12, at -O0, compiles C at ~20,000 lines per second, and MSVC is about double this. I can build entire programs from scratch without any incremental or parallel configuration (e.g. unity builds) in the blink of an eye. Modern C++ compiles at ~200 lines per second. Literally two orders of magnitude difference. Even linking C++ is slower due to the presence of many more symbols from templating.

Debug binaries of C programs are nearly as fast as release. In most cases you'd need to look closely to notice the difference. In fact, a C debug binary is typically faster than the equivalent Modern C++ release build. (I demonstrated this in my MSI hash table article.)

In contrast, C++ templates bloat up into mountains of code and rely on optimization to clean it up. Debug builds are painfully slow, and the difference between debug and release is vast.

Step through a Modern C++ program in a debugger and you will find yourself jumping in and out of the C++ standard library and other runtime and template gunk that glues everything together. There are overloads all over the place and it's difficult to reason about exactly what's happening. This is especially true for debug builds which hasn't optimized some of this away.

Debuggers, GDB especially, struggle with displaying and expressing complex C++ types built from layers of templates.

I might like function overloading, but the cost is name mangling even when unused. Most of the time tools know how to demangle C++ symbols, but not always. When they don't, it's a lot of friction. Name mangling is just too high a cost, and a persistent thorn even when I'm writing C-styled C++.

I wish C had even simpler syntax. I'd happily give up const in exchange for more simplicity. It's not doing much anyway. C++ goes exactly the opposite direction.


So what does C++ have? declspec is nice. Not needing to typedef structs is nice, especially for recursive fields. I like that bitwise operators don't overflow; it's a mistake for C to treat them like arithmetic. I like empty initializer lists for default-initializing anything, and I like the option to choose default initialization for fields. (Honestly, C and C++ should have been defined to default-initialize all variables by default, with some special keyword to disable initialization when the performance matters. That was an early design mistake.) Though beyond this, C++ initialization is insane.

I'm not totally against type deduction, but outside of templates it's not important. Lambdas could have been neat, but weren't defined tightly enough (might allocate, too opaque). Namespaces could be useful, but aren't worth the cost of name mangling. Destructuring is neat in theory but disappointing in practice.

That's about it for useful C++ features. Exceptions are bad. Templates are a disaster. I still do not understand even the syntax of Concepts after hours of study. Smart pointers conflate allocation and lifetime, and resolve details at run-time that ought to be known at compile time (previously discussed). I dislike OOP (inheritance, virtual functions, access specifiers, etc.). Just like C, the C++ standard library is poor and best avoided. (Fortunately, also like C, it's easy to do so.)

My review of the C standard library in practice https://nullprogram.com/blog/2023/02/11/

C++ containers aren't salvageable because allocation is broken. In Modern C++ you now get to supply a custom allocator — a process that is hideously complicated — but the allocator is part of the container type, making it practically useless.

I think that's about it. Looks like I can count the things I like about C++ (versus C) on one hand, and these aren't worth the entry cost. That being said, the tooling, especially debugging, for C and C++ is still best-of-class by a huge margin, and if it was C++ versus anything else except C, I'd choose C++ then write it like C. :-)