Takeaway #1: "C and C++ are different: don’t mix them, and don’t mix them up"
This is how I managed to sneak C++ into an embedded C codebase. We even created some templates for data structures that supported static allocation at compile time.
Sooo... yeah... I should definitely change company!
https://stackoverflow.com/questions/58815959/include-binary-...
https://herbsutter.com/2012/05/03/reader-qa-what-about-vc-an...
I guess what you're really asking is what are the best or most common ways to do OO in C?
Another big one that I always forget C still doesn't support is function overloading.
https://stackoverflow.com/questions/562303/the-definitive-c-...
Where can I find something about objects being "think of your code as representing the state and interactions of objects" honesty totally new to me.
So no, certainly I'm not asking ways to do OO in C. But it seems to be more definitions of object orientation as I thought...
Yes you can do it less cleanly with macros or inline functions. But you can't do it performantly with struct and function pointers.
C++ imo doesn't offer anything compelling for the embedded usecase. Especially not considering all the footguns and politics it brings.
You can of course be strict and diligent about it but if you are you are pretty much just writing C anyway. Better to do it explicitly.
Allowing the use of the C++ standard library has been one of my biggest regrets (not that it was my decision to make, I fought it).
But why would you do that if you have an instrument that lets you work at the same level as C, but with methods provided as a proper abstraction that maps exactly to what you'd have written yourself anyway?
If you do that, you'll notice that, for example, encapsulation is not a part of that de facto definition, because languages like Python and (until recently) JavaScript lack it, despite being considered OO.
Indeed, the only two things that appear to be consistently present in all OO languages are: 1) some notion of object identity as distinct from object state, and 2) runtime polymorphic dispatch.
But I write ; i++){ and seeing it the other way round throws me off for a minute, because I think, as you put it, why would you use those very particular semantics?
But I guess this is only a semantic argument.
This is not one of those beginner -> journeyman -> expert cycles where coincidentally the way you wrote it as a beginner is identical to how an expert writes it but for a very different reason. I'd expect experts are very comfortable writing either { x = k; k += 1; } or { k += 1; x = k; } depending on which they meant and don't feel an itch to re-write these as { x = k++; } and { x = ++k; } respectively.
I'm slightly surprised none of the joke languages add equally frivolous operators. a%% to set a to the remainder after dividing a by 10, or b** to set b as two to the power b or some other silliness.
If you're asking why people use pre-increment by default instead of post-increment, it's mostly historical. The early C compilers on resource-constrained platforms such as early DOS were not good at optimization; on those, pre-increment would be reliably translated to a simple ADD or INC, whereas code for post-increment might generate an extra copy even if it wasn't actually used.
For C++ this was even worse with iterators, because now it depended on the compiler's ability to inline its implementation of postfix ++, and then prove that all the copies produced by that implementation have no side effects to optimize it to the same degree as prefix ++ could. Depending on the type of the underlying value, this may not even be possible in general.
The other reason is that all other unary operators in C are prefix rather than postfix, and mixing unary prefix with unary postfix in a single expression produces code that is easy to misunderstand. E.g. *p++ is *(p++), not (*p)++, even though the latter feels more natural, reading it left-to-right as usual. OTOH *++p vs ++*p is unambiguous.
> it is hard to say no to you, and I’m sorry to say it. But we have to choose a focus, and our focus is to implement (the standard) and innovate (with extensions like everyone but which we also contribute for potential standardization) in C++.
I mean, yeah if it came from a two member team at a startup, sure focus on C++, understandably. But Microsoft, what happened to "Developers! Developers! Developers!"?
I’m scratching my head how you think this is materially different than what you described in your first para. s/state/data and s/interactions/methods.
If anything though I would say the GP is more aligned with the classic definition as it highlights the focus is more on the messages (interactions) themselves rather than the implementation.
Where "mixing C/C++" is helpful:
- I "mix C in with my C++" projects because "sqlite3.c" and ffmpeg source code is written C. C++ was designed to interoperate with C code. C++ code can seamlessly add #include "sqlite3.h" unchanged.
- For my own code, I take advantage of "C++ being _mostly_ a superset of C" such as using old-style C printf in C++ instead of newer C++ cout.
Where the "C is a totally different language from C++" perspective is helpful:
- knowing that compilers can compile code in "C" or "C++" mode which has ramifications for name mangling which leads to "LINK unresolved symbol" errors.
- knowing that C99 C23 has many exceptions to "C++ is a superset of C" : https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B...
The difference is that i++ has to keep a copy to the original around as the return value is the pre-increment value, while with ++i that isn't needed as the resulting value is being returned.
In the for loop that shouldn't matter as a) for an integer it is essentially for free (it is just reordering when the relevant register is set) and b) that value is hopefully optimized out anyways by the compiler, however as there are cases where it matters some people prefer the ++i style, some just think it looks better.
The C library headers for libraries I write often include C11/C99 stuff that is invalid in C++.
Even when they are in C89, they are often incorrect to include without the include being in an `extern "C"`.
Of course, it's still C.
> Is the usage of single linked lists still prevalent as the main container type?
As far as I can remember, the C standard library has never had any functions that used linked lists. Nor are there any container types, linked lists or otherwise, provided by C. So I'd say this is a question about how people teach and use C, not related to the language -- or language spec version -- itself.
In release fast mode, unsigned overflow/underflow is undefined in Zig whereas in C it wraps.
:-)
Of course C has many UBs that Zig doesn't have, so C is far less safe than Zig, especially since you can use ReleaseSafe in Zig..
D does not decay arrays, so D has array bounds checking.
Note that array overflow bugs are consistently the #1 problem with shipped C code, by a wide margin.
The modern fmt-inspired std::print and std::println etc. are much nicer, preserving all the type checking but losing terrible ideas like stored format state, and localisation by default. The biggest problem is that today C++ doesn't have a way to implement this for your own types easily, Barry illustrates a comfortable way this could work in C++ 26 via reflection which on that issue closes the gap with Rust's #[derive(Debug)].
There's an effort to extract the good parts and make it work for embedded use cases or even bring them into C. Khalil Estelle on WG21 has been working on an experimental, deterministic runtime for exception handling, to give one example. Constexpr is an example of the latter that's now showing up in C23.
It takes a little bit of an effort to make a header work on C and C++. A lot less effort than making a single Python file work with Python 2 and 3.
This isn’t strictly true, a C implementation is allowed to associate memory-range (or more generally, pointer provenance) metadata with a pointer.
The DeathStation 9000 features a conforming C implementation which is known to catch all array bounds violations. ;)
One of the most compelling things C++ offers to embedded use case is moving runtime initialization to compile-time initialization by liberally using constexpr functions. You literally ask the compiler to do work that would otherwise be done at runtime.
Almost seamlessly. You have to do
extern “C” {
#include "sqlite3.h"
}
(https://isocpp.org/wiki/faq/mixing-c-and-cpp#include-c-hdrs-...)for(auto it = begin(v); it != end(v); ++it)
But without exceptions it is mostly syntactic sugar anyway.
If compile time initialization is the most compelling usecase I'll rest my case. Good feature, yes! Hardly worth switching language for.
cin >> a;
Then the program goes berserk as soon as the first non-number is read out of standard input. All the other "cin >> integer" lines are immediately skipped.
Yes, I know about error checking, clearing error condition, discarding characters. But it's a whole lot of stuff you need to do after every single "cin>>" line. It makes the simplicity of cin not worth it.
It's telling that every compiler toolchain that compiles C++ also compiles C (for some definition of "C"). With compiler flags, GCC extensions, and libraries that are kinda-sorta compatible with both languages, there's no being strict about it.
_My_ code might be strict about it, but what about tinyusb? Eventually you'll have to work with a library that chokes on `--pedantic`, because much (most?) code is not written to a strict C or C++ standard, but is "C/C++" and various extensions.
I’m not sure about the stdlib version, but with fmtlib you can easily implement formatters for your own types. https://fmt.dev/11.0/api/#formatting-user-defined-types
A lot of stuff in the C++11 standard library was based on widespread use of Boost. Since then, I don't know. Also, were things like templates and lambdas implemented as compiler extensions before standardization? I don't know, but I doubt it. Maybe "we're a committee of people who will decide on a thing and we hope you like it" was always the norm in many ways.
3.1.2.5 Types
[...] A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting unsigned integer type.
Source: C89 (draft) at https://port70.net/~nsz/c/c89/c89-draft.txtYou check error for the whole batch.
* Performance
* Support for localization (as the format string and positions of values to format differ between languages).
* Code reuse & dogfooding - the data structures used in iostreams are not used elsewhere, and vice-versa
* C and OS interoperability - as you can't wrap a stream around a FILE* / file descritor
* bunch of other stuff...
iostreams work, but are rather crappy.
So, C23? ... that's nice and all, but, let's talk about it in 20 years or so T_T
The less C the merrier.
If you care about correct use of localisation, standard C and C++ libraries aren't really what you're looking for, or even C and C++ to start with.
This interacts in the obvious way with refusing to correct mistakes after the fact for fear of breaking user code.
I don't believe anyone has written a paper along the lines of "let's not bother with the existing practice part anymore", it's more an emergent feature of people following local incentive structures.
I like "Effective C" over "Modern C" because it's more engaging ... "Modern C" is super rigorous and feels a bit like reading an annotated spec of the language, which is what an expert may need, but makes for a dull read for a casual C user like me.
--
int main() {
int a[3];
return foo(a);
}
> gcc test.c
> ./a.out
Oops.D: int foo(int[] a) { return a[5]; }
int main() {
int[3] a;
return foo(a);
}
> ./cc array.d
> ./array
core.exception.ArrayIndexError@array.d(1): index [5] is out of bounds for array of length 3
Ah, Nirvana!How to fix it for C:
Hindsight is 20/20, remember that. Streams are not that bad of an idea and have been working fine for decades. You haven't named a problem with it other than the fact the operators are used for other stuff in other contexts. But operator overloading is a feature of C++ so most operators, even the comma operator, can be something other than what you expect.
>The biggest problem is that today C++ doesn't have a way to implement this for your own types easily, Barry illustrates a comfortable way this could work in C++ 26 via reflection which on that issue closes the gap with Rust's #[derive(Debug)].
You can trivially implement input and output for your own types with streams.
You appear to be a Rust guy whose motive is to throw shade on C++ for things that are utterly banal and subjective issues.
The only reason why iostreams are slow is because of its incompatible buffering scheme, and the fact that C and C++ need to stay in sync when linked together. And that brand of slow is still faster than other languages, except sometimes those that delegate i/o to pure C implementations.
But having the "class" keyword is nice. Having built in support for member functions is nice.
Sometimes a person just wants the simplicity of C++ 2003.
(In reality I was working on a project where our compiler only supported C++ 2003 and we had a UI library written in C++ 2003 and honestly pure C UI libraries kind of suck compared to just sprinkling in a bit of C++ sugar.)
I agree. It's lunacy. just be explicit and use functions or equivalent like literally every other language.