4 ms·
If your code is plain C, then anyone can extend it with, or embed it inside, code of literally any other language; and in so doing, they will have full access/e
by derefr 1y ago
If your code is plain C, then anyone can extend it with, or embed it inside, code of literally any other language; and in so doing, they will have full access/exposure to everything in your codebase — all the same stuff that they would if they were writing their host/extension code in C.
This is not true of C++ (or most other languages):
• C++ has a runtime (however minimal); and so, by including any C++ code in a codebase, you're making it much more difficult to link/embed the resulting code — you now have to also dynamically link the C++ runtime, and ensure that your host code spins it up "early", before any of the linked C++ code gets to run. (This may even be impossible in some host languages!)
• Also, even if there was no associated runtime to deal with, C++ isn't wholly C-FFI-clean. All the stuff that people like about C++ — all the reasons you'd want to use C++ — result in codebases that aren't cleanly C-FFI exposable, due to name mangling, functions taking parameters with non-C-exportable types, methods + closures not being C-FFI thunkable [and functions returning those], etc.
• And even if you bite that bullet, and write your library in C++ but carefully wrap its API to give it C-FFI-clean linkage (usually via a hybrid C / C++ project), this still introduces a layer of FFI runtime overhead. When another non-C language consumes your code, it's then getting double FFI overhead — a call from its code to yours has to convert from its abstractions, to C's abstractions, to C++'s abstractions, and back. (This is why you don't tend to see e.g. non-C++ projects embedding LLVM, or LLVM being extended with non-C++ passes, despite LLVM being designed in this "C wrapper around a C++ core" style.)
C is one of the only languages with a zero-impedance-mismatch, zero-overhead default or forced binding of external symbols to the C FFI (i.e. the C set of platform ABIs + C symbol naming standard.)
The others that do this are: C3 (https://c3-lang.org/ https://c3-lang.org/); Zig, unless you do weird things on purpose, and... that's really it. Everything else has the same two problems as C++ outlined above.
Even Rust, even Odin, etc. only provide C-FFI linkage as an opt-in feature; and they do nothing to incentivize use of it; and so, of course, due to their useful non-C-FFI-clean features, developers are disincentivized from ever enabling it before they "need" it. So in practice, most libraries in those languages are not consumable from C [or other C-FFI-compatible languages] — and most software in those languages are not extendable in C [or another C-FFI-compatible language] — without extra effort on the upstream's part to add explicit support for doing that. And most upstreams don't bother.
Writing software in C itself, is essentially a way for a project to "tie itself to the mast" and commit to its ABI always being C-FFI clean; such that it can be consumed not only from C, but also from any other language a project might use that supports importing C-FFI libraries. (Which is most languages.)
- CyberDildonics 1y agoC++ has a runtime (however minimal); No it doesn't. Also, even if there was no associated runtime to deal with, C++ isn't wholly C-FFI-clean Yes it is, you just extern "C" whatever you want. All the stuff that people like about C++ — all the reasons you'd want to use C++ — result in codebases that aren't cleanly C-FFI exposable Not true at all, the biggest two things, destructors and move semantics you still have everywhere except for the boundaries with C. And even if you bite that bullet, and write your library in C++ but carefully wrap its API to give it C-FFI-clean linkage (usually via a hybrid C / C++ project), this still introduces a layer of FFI runtime overhead There is no overhead here, it is not different from C. I don't know where all this comes from, but I doubt it comes from heavy experience with modern C++.
- cyber1 1y agoNo, it does. "The only two features in the language that do not follow the zero-overhead principle are runtime type identification and exceptions, and are why most compilers include a switch to turn them off." - https://en.cppreference.com/w/cpp/language/Zero-overhead_principle.html https://en.cppreference.com/w/cpp/language/Zero-overhead_pri...
- CyberDildonics 1y agoSo saying 'it has a runtime' doesn't really make sense, it has a runtime if you want for two features that aren't necessary.
- derefr 1y agoWhen most people refer to Java, they're referring to the "Java SE Platform" (the one that ships with all the stdlib stuff you'd expect, requiring a ton of process-boot-time logic and runtime support threads); not the "Java SE Embedded Platform" (the one you'd use to write e.g. Java Card software for smartcards, that doesn't have all of that stuff.) "Java for the Java SE Platform" and "Java for the Java SE Embedded Platform" are essentially two different languages, with the latter being a strict subset of the former. When most people refer to C++, they're referring to the platform you get by linking the C++ STL. There's also what you might informally call "RTOS kernel C++" — C++ with all its syntax features (exceptions and destructors and RTTI) but no STL — that you get by linking only to a core C++ runtime support libraries (libsupc++/libc++abi/etc); and there's also "ultra-low-power embedded C++", where you don't even link to these, and don't get to use those features, so you instead have to use placement `new` and manual destructor calls, do your own error-handling with sentinel values or tagged unions or what-have-you, etc. These platforms are, again, essentially their own languages, each a strict subset of the one before. Just like you can't hire a random Java programmer and expect them to be immediately productive on a Java Card codebase, C++ programmers are not "ultra-low-power embedded C++" programmers, any more than C# or Objective-C programmers are C programmers, or any more than C programmers are assembly-language programmers. --- You might rebut with "C++ has a lot of subsets; it's a language with a lot of features that people can take or leave; people still call all of these subsets C++." Yes, people do get used to thinking of C++ as "a language that has a bunch of different features that you can choose to use or not", and that mental-schema inertia carries over into this case — but it doesn't / shouldn't actually be applied here, and people are wrong to do so. In the case of other C++ features, if you're not using a feature, it's because you just don't need what it does. When you choose to constrain your use of most C++ features, this results in the codebase being easier to understand. For most C++ features, avoiding use of the feature reduces the experience barrier required to begin contributing to the codebase. Which is why the subsets of C++ that don't use these features, are still just "C++": anyone who is a "C++ programmer" can instantly and intuitively maintain a codebase that is written in one of these subsets of C++. But in the case of the syntax features requiring C++ runtime support, if you're strictly adhering to not using those... then you're having to do far more onerous and esoteric stuff (placement new + manual destructing; C-like error handling; BYO type metadata with intermediate void-ptr casting) instead. And that's stuff they don't even teach you how to do in a C++ course, or even a very thick C++ textbook. As a "C++ programmer" cannot be expected to code for this "ultra-low-power embedded C++", you may as well consider it a separate language. --- And if you agree with that, then you should agree that it makes sense to claim that C++ has a runtime. It is only the separate language, "ultra-low-power embedded C++", that doesn't.