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)
Indeed, and yet here we are with C23
> The change of heart was the new management, and the whole Microsoft <3 FOSS.
Yeah, agree. To me the turning point was when they created WSL.
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.
1) https://gcc.gnu.org/projects/cxx-status.html#:~:text=C%2B%2B...
>Possible undefined behavior ranges from ignoring the situation completely with unpredictable results ... or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message)
So a compiler is absolutely welcome to make undefined behavior safe. In fact every compiler I know of, such as GCC, clang, MSVC has flags to make various undefined behavior safe, such as signed integer overflow, type punning, casting function pointers to void pointers.
The Linux kernel is notorious for leveraging undefined behavior in C for which GCC guarantees specific and well defined behavior.
It looks like there is also the notion of unspecified behavior, which gives compilers a choice about the behavior and does not require compilers to document that choice or even choose consistently.
And finally there is what you bring up, which is implementation defined behavior which is defined as a subset of unspecified behavior in which compilers must document the choice.
void remove_char(char *s, char c) {
size_t i, j;
for (i = j = 0; s[i] != '\0'; i++)
if (s[i] != c)
s[j++] = c;
s[j] = '\0';
}
This might be better expressed with a higher order filter function, but C is too low level for things like that.There are also idioms for stack manipulation using them: "stack[sp++] = pushed" and "popped = stack[--sp]".
C code does a lot of incrementing and decrementing by one, and so having dedicated syntax for it is convenient.
That actually really does exist already with CHERI CPUs, whose pointers are tagged with "capabilities," which catch buffer overruns at runtime.
https://tratt.net/laurie/blog/2023/two_stories_for_what_is_c...
https://msrc.microsoft.com/blog/2022/01/an_armful_of_cheris/
"_BitInt(N)" is also ugly, reminds me of "_Bool" which is thankfully "bool" now.
[1] guard, defer, auto, constexpr, nullptr (what is wrong with NULL?), etc. On top of that "constexpr" and "nullptr" just reeks of C++.
That said, Modern C is an incredible book, I have been using it for C99 (which I intend to continue sticking to).
I personally find that googling stuff provides not much connection to the subject of study, very impersonal and try to avoid it.
For example I did google the concept, and found this https://github.com/cousteaulecommandant/ds9k.
Which is not trivial to parse, bing posited the answer as authoritative, and if you look at the code it is really nothing, it seems to be a folklore concept, and as such, it is much more aptly transmitted by speaking to a human and getting a live version than by googling an authoratitative static answer.
For starters, you have to #include a header to use it.
I suspect that decades of C being effectively frozen have caused the userbase to self-select to people who like C exactly the way it is (was), and don't mind supporting ancient junk compilers.
Everyone who lost patience, or wanted a 21st century language, has left for C++/Rust/Zig or something else.
> 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.
What do you recommend instead? > You can do anything in C that you want to.
How about destructors?If I want to live on cutting edge I would rather use C++2x or Rust rather than C.
Am I missing something? What benefit this supposedly modern C offers?
also, unless you're targeting embedded or a very wide set of architectures, there's no reason why you couldn't start using C23 today
fscanf (STDIN, "%d", &a);
the program goes beserk as soon as the first non-number is read out of standard input.in both cases, you need error checking (which you "know about").
struct Foo {
int a;
float b;
std::string c;
};
Foo foo;
std::cout << foo;
with no extra code. It's called reflection, where the compiler can generate good-enough code to generate a character-stream serialization of an object without any human intervention.One of the few advantages of ISO standardization is you can just read the associated papers to answer questions like this: https://wg21.link/p2312
The quick bullet points:
* Surprises when invoking a type-generic macro with a NULL argument.
* Conditional expressions such as (1 ? 0 : NULL) and (1 ? 1 : NULL) have different status depending how NULL is defined
* A NULL argument that is passed to a va_arg function that expects a pointer can have severe consequences. On many architectures nowadays int and void* have different size, and so if NULL is just 0, a wrongly sized argument is passed to the function.
The microcontroller toolchains are generally built on top of GCC, so they get the features for free. There are some proprietary C compilers that are chronically lagging behind, but they are not nearly as important as they used to be two decades ago.
I believe Rust has adopted similar idioms. I’ve heard the overall idea referred to as Railway-oriented programming.
In C++ you could implement it with exceptions, though they bring in a bunch of their own baggage that you don’t have to deal with when using monads.
while(*d++ = *s++)
;
On the Motorola 68000 (based somewhat on the PDP-11) the code would look like: loop: move.b (a0)+,d0
move.b d0,(a1)+
bne loop
while on the x86 line, it would be: loop: mov al,[rsi]
mov [rdi],al
inc rsi ; extra instruction!
inc rdi ; extra instruction!
cmp al,0
jne loop
Yes, there are better ways to write that code for both the 68K and x86, but I hope this gets the point across.By the way, the US government did the profession no favors by including C++ as a memory-unsafe language. It is possible to write memory-safe C++, safe array dereferencing C++. But it’s not obvious how to do it. Herb Sutter is working on it with CppFront. The point stands that C++ can be memory-safe code. If you make a mistake, you might write some unsafe code in C++. But you can fix that mistake and learn to avoid it.
When you write C, you are in the bad luck shitter. You have no choice. You will write memory—unsafe code and hope you don’t fuck it up. You will hope that a refactor of your code doesn’t fuck it up.
Ah, C, so simple! You, only you, are responsible for handling memory safely. Don’t fuck it up, cadet. (Don’t leave it all to computers like a C++ developer would.)
Put C in the bin, where it belongs.
This disn't stop with <iostream>, they keep doing it - the latest example I can think of is std::ranges operations being "piped" with |.
Why would I rather step into the world of C++ just to deal with that?
1. prefix incr/decr precedence: "stack[--sp]"
2. postfix incr/decr precedence: "s[j++]"
3. i have no particular preference for the precedence and am just using a shorthand I inherited from my ancestors whose use cases are no longer relevant to me: "i++" in your for loop
My rank speculation is that C programmers get in a habit of #3 and then forget to consider precedence in an expression where it matters.
In any case, it would be interesting to do a scan of github to see how often prefix and suffix incr/decr had to get switched up in a bugfix patch.
I cannot remember the last time I saw C99 used. C codebases generally use C11 or C17, and C++ code bases use C++20
Absolutely true. I generally insist on folks learning C and C++ interoperability before diving in to all the "Modern C or C++" goodness. It helps them in understanding what actually is going on "under the hood" and makes them a better programmer/debugger.
See also the book Advanced C and C++ Compiling by Milan Stevanovic.
Many C++ coders are oblivious to those differences (myself included before I switched from 'mainly C++' to 'mainly C') because they think that the C subset of C++ is compatible with 'proper' C, but any C code that compiles both in a C++ and C compiler is actually also a (heavily outdated) subset of the C language (so for a C coder it takes extra effort to write C++ compatible C code, and it's not great because it's a throwback to the mid-90s, C++ compatible C is potentially less safe and harder to maintain).
For instance in C++ it's illegal to take the address of an 'adhoc-constructed' function argument, like:
sum(&(bla_t){ .a = 1, .b = 2, .c = 3, .d = 4 });
(godbolt: https://www.godbolt.org/z/r7r5rPc6K)Interestingly, Objective-C leaves its C subset alone, so it is always automatically compatible with the latest C features without requiring a new 'ObjC standard'.
If the pre- or post-increment behaviour isn't actually needed, I prefer `x += 1` though.
_BitInt(N) isn't typically used directly but typedef'ed to the width you need, e.g.
typedef _BitInt(2) u2;
The 'ugly' _B syntax is needed because the combination of underscore followed by a capital letter is reserved in the C standard to avoid collisions with existing code for every little thing added to the language (same reason why it was called _Bool).AFAIK defer didn't actually make it into C23?
I'm also more on the conservative side when it comes to adding features to the C standard, but IMHO each of the C23 additions makes sense.
For the cutting edge I would recommend Zig btw, much less language complexity than both modern C++ and Rust.
One good but less visible side effect of C23 is that it harmonizes more syntax with C++ (like ... = {} vs {0}) which makes it a bit less annoying for us C library maintainers to support the people how want to compile their C code with a C++ compiler.
The pre-processor original compiler, before the GCC fork, would leave everything else alone, blindly copying into the generated C file.
int foo(int (*a)[6]) { return a[5]; }
int main() {
int a[3];
return foo(&a);
}
Or for run-time length: int foo(int n, int (*a)[n]) { return (\*a)[5]; }
int main() {
int a[3];
return foo(ARRAY_SIZE(a), &a);
}
/app/example.c:4:38: runtime error: index 5 out of bounds for
type 'int[n]'
https://godbolt.org/z/dxx7TsKbK\*I see comments like yours everywhere all the time and I seriously think you have a very unhealthy emotional relationship with this topic. You should not have that much hate in your heart for a programming language that has served us very well for many decades and still continues to do so. Even if C was literally all bad (which imho isn't even possible), you shouldn't be that angry at it.
It's more code, sure, but it buys you a lot of good things. I/O is hard.
Though I've actually seen macro systems that do things akin to destructors, although less automatically.
In my experience, Windows devs don't like being told to use a different toolchain. They may have projects tied to Visual Studio, dependencies that are MSVC-only or code written for quirks of MSVC's libc/CRT, or want unique MSVC build features.
I found it hard to convince people that C isn't just C (probably because C89 has been around forever, and many serious projects still target it). I look like an asshole when I demand them to switch to whole another toolchain, instead of me adding a few #ifdefs and macro hacks for some rare nice thing in C.
Honestly, paradoxically it's been easier to tell people to build Rust code instead (it has MSVC-compatible output with almost zero setup needed).
For example, the primary reason for the sentence seems to be from the text: "Many code examples in this book won't even compile on a c++ compiler, So we should not mix sources of both languages".
It's not at all about the ability to use c libraries in c++ projects or vice versa :S.... c'mon guys!
Both have compatibly implemented the standard C++ auto. Since 2011 or so.
You have to know what you're biting into, before you use that.
This is no longer true. On UEFI systems the only thing you have to do normally is fix the boot order. In fact installing Linux first and Windows second tends to be the better dual-boot strategy nowadays.
Fixing the boot order can be done from UEFI setup, and even from Windows command line
bcdedit /enum firmware
bcdedit /set {fwbootmgr} displayorder {yourlinuxuuid} /addfirst
(Put single quotes around {} if you use PowerShell instead of CMD.exe)For instance, only sized types:u8...s64, f32, f64... no implicit casts except for void* and literals, no integer promotion, no switch, no enum, only one loop keyword (loop{}!), no anonymous code block, and no toxic attribute like "packed structure" which makes us lose sight of data alignment... no _generic, typeof, restrict, syntax based tls, etc...
But we would need explicit atomics, explicit memory barriers, explicit unaligned memory access.
Instead of adding and complexifying C to make writing a naive compiler more and more complex, long and a mouse and cat catchup "to the standard" tedious task, what should be done is exactly the other way around.
In end, I don't trust C officials anymore, I tend to stick to C99, or even assembly (I am currently writing rv64 assembly I run an x86_64).
Here's an example where Clang and GCC don't agree about the behaviour of auto in C23:
https://www.godbolt.org/z/WchMK18vx
IIRC Clang implements 'C++ semantics' for C23 auto, while GCC doesn't.
Last time I brought that up it turned out that both behaviours are 'standard compliant', because the C23 standard explicitly allows such differing behaviour (it basically standardized the status quo even if different compilers disagreed about auto semantics in C).
PS: at least Clang has a warning now in pedantic mode: https://www.godbolt.org/z/ovj5r4axn
Writing safe code is better than depending on safety features. Writing safe code is possible in any programming language, the only things required are good design principles and discipline (i.e. solid engineering).
> […] But we would need explicit atomics, explicit memory barriers, […]
You should read a change summary before complaining about bits missing from C99 that have in fact been added to C11.
> […] no toxic attribute like "packed structure" which makes us lose sight of data alignment […]
And you should also familiarize yourself with what's in actual ISO C vs. compiler extensions before complaining about bits that are in fact compiler extensions.
That's the nice thing with C: it's much easier for small teams to fully support than the latest C++ standards.
* is it (*s)++ or *(s++)?
* it is not *++s nor ++*s
And I have seen *(*s)++
in some places!It is concise syntax but very confusing.
I think this is a very lazy and somewhat conspiratorial take.
C++'s IO stream library, along with C++'s adoption of std::string, is a response to and improvement over C's standard library support for IO. That alone makes it an invaluable improvement. It's easy and very lazy to look back 30 years ago and badmouth things done back then.
It's also easy to complain about no one proposing changes when literally anyone, including you, can propose changes. The only need to do the legwork and put their money where their mouth is. The funny part is that we see frameworks putting together their own IO infrastructure and it ends up being not good, such as Qt's take on IO.
But talk is cheap and badmouthing doesn't require a pull request.
Manual checking of memory management correctness takes extra time and effort to review, debug, instrument, fuzz, etc. things that the compiler could be checking automatically and reliably. This misplaced effort wastes resources and takes focus away from dealing with all the other problems.
There's also a common line of thinking that that because working in C is hard, C programmers must be smarter and more diligent, so they wouldn't make dumb mistakes like the easy-language programmers do. I don't like such elitist view, but even if true, the better programmers can allocate their smarts to something more productive than expertise in programs corrupting themselves.
Calling big endian "commonly used by modern processor types" when s390x is really the only one left is a bit of a stretch ;D
(Comments about everyone's favorite niche/dead BE architecture in 3… 2… 1…)
You could, in theory, just use C++ and be done with it. But like any C++ project you'd need a pretty strict style guide or even a linter, but this time it would have to be extra restrictive lest you slide into full C++ territory. And maybe that's a major stumbling block for some people?
template<typename R>
constexpr ssize_t
io::ReadFull(R reader, char *buf, size_t len)
{
ssize_t recvd = 0, ret;
do {
ret = read(reader, &buf[recvd], len - recvd);
if (ret > 0)
recvd += ret;
else
break;
} while (recvd < len);
return recvd;
}
---1: https://open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3980.h...
If you can't live without knowing, sure, my stake in dismissing big endian architectures is that I can't in fact dismiss BE architectures because I have users on it. And it's incredibly painful to test because while my users have such hardware, actually buying a good test platform or CI system is close to impossible. (It ended up being Freescale T4240-QDS devkits off eBay. Not a good sign when the best system you can get is from a company that doesn't exist anymore.)
And at some point it's a question about network protocols/encodings being designed to a "network byte order" determined in the 80s to be big endian. When almost everything is LE, maybe new protocols should just stick with LE as well.
One exception to that in my experience: dependencies, although I think that's a bit deceiving as yes, it's easier to get dependencies in Rust but in some areas they're way less mature and can sometimes be a pain to work with (usually when dealing with big C or C++ libraries that have been quickly glued to a Rust interface to be available in Rust).
cin >> a;
and assume that it works.But also ...
sscanf (the_buffer, "%d", &a);
doesn't help the problem in any substantive way.The pb is in ISO c11+ we got some of the missing stuff for modern hardware architecture, but also tons of tantrums (_generic, typeof, restrict....)
The old definition did not even specify wether it was a pointer or an integer. So for platforms that did not follow the Posix ((void*)0) requirement it was a foot gun that had neither the type nor the size of a pointer.
> On top of that "constexpr" and "nullptr" just reeks of C++.
Probably because they where back ported from C++. You can still use NULL, since that was apparently redefined to be nullptr.
qemu CPU emulation exists too, but that's painfully slow for an actual CI run, and I'm not sure I trust it enough with e.g. AF_NETLINK translation to use the "-user" variant on top of an LE host rather than booting a full Linux (or even BSD).
And in the very best case, proper testing would pit BE and LE systems "against" each other; if I run tests on BE against itself there's a good risk of mis-encodings on send being mis-decoded back on receive and thus not showing as breakage…
… really, it's just a pain to deal with. Even the beauty (in my eyes) of these T4240 ppc64 systems doesn't bridge that :(
And not being competent in C++ is not great. You are going to be much more productive in C. The feedback loop is much faster, that is, feedback from your designs.
Contrast with Rust which is harder to get going but doesn't require nearly as much to be decent.
Just include the stddef.h header if you want to use NULL, similarly to how you include a header file if you want to use anything else, e.g. bool from stdbool.h.
[1] I am not entirely sure in retrospect, actually, as I might be misremembering, but my point stands with or without stdio.h!
I think 21st Century C by Ben Klemens and C Programming a Modern Approach by King are both more approachable alternatives as a modern C companions to K&R.
And back around 2010 MSVC still mattered a lot (which sounds weird from today's pov where most developers appear to have moved to Linux).
But OTH, few projects actually need C11 features (and C11 actually took one thing away from C99: VLAs - nothing of value was lost though).
C23 might be the first version since C99 that's actually worth upgrading to for many C code bases.
In my opinion, complexity doesn't scale linearly like this. Sometimes, in fact often times, having more complex tools means a simpler process and end result.
It's like building a house. A hammer and screwdriver are very simple. A crane is extremely complex. But which simplifies building a house? A crane. If I wanted to build a house with only a hammer and screwdriver, I would have to devise incredibly complex processes to get it done.
You see the same type of thing in programming languages. Making a generic container in C++ is trivial. It's very, very hard in C. You can make it kind of generic. You can use void * and do a bunch of manual casting. But it's cumbersome, error prone, and the code is more complex. It's counter-intuitive - how can C, a simpler language, produce code that is more complex than C++?
Or look at std::sort vs qsort. The power of templates and functors makes the implementation much simpler - and faster! We don't have to pass around void * and dereference them at runtime, instead we can build in comparison into the definition of the function itself. No redirection, no passing on the stack, and we can even go so far as to inline the comparison function.
There's really lots of examples of this kind of stuff. Point being, language complexity does not imply implementation complexity.
Fair—my bad, I can fail at reading tone sometimes.
Would you propose the C abstract machine abstracting away endianness entirely as an alternative? My understanding is that deprecating support for existing architectures is discouraged to every practical extent.
The issue is that these great new tools don't just fix the old vulnerabilities, they also provide a lot of new, powerful footguns for people to play with. They're shipping 2000 feet of rope with every language when all we need is 6 feet to hang ourselves.
For your particular use case, have you considered C#? VS works much more nicely with it.
Did you mean gcc? Your link shows a gcc error:
<source>:3:5: error: 'auto' requires a plain identifier, possibly with attributes, as declarator
3 | auto* p = &i;
| ^~~~
Although this may seem cryptic at first sight, the notational convenience is considerable, and the idiom should be mastered, because you will see it frequently in C programs.
Any recommended hardware I should use for bare metal development messing around? Hopefully priced like a SBC like the raspberry pi.
Want to move from making basic programs like adding, messing with functions, etc and bring my MIPS assembly up to a real hardware environment.
But it might be a minor problem for STB-style header libraries.
It's not uncommon for C++ projects to include the implementation of an STB-style header into a C++ source file instead of 'isolating' them in a C source file. That's about the only reason why I still support the common C/C++ subset in my C libraries.
https://www.godbolt.org/z/bWfhGx7xh
...without -march=native it's a mov and bswap (so not too bad either).
To be honest, I don't think this is a solvable problem. (Changing the C machine concept doesn't do much if you need to process network traffic that uses both (e.g. IP packet [big endian] carrying protobuf [little endian]). It's already mostly a question of data ingress/egress.)
What is solvable though is making sure people are sufficiently aware. And the people who read a book like "Modern C" are probably a very good target audience, building low-level bindings and abstractions. They should know that LE and BE are technically a free-floating design choice, but practically the vast majority of systems is LE now. But at the same time, yes, BE isn't extinct, and won't be any time soon… and it's left to them to make their best possible design given their environments.
1. That both byte orders are equally prevalent in the wild, particularly in systems that are expected to run modern C code.
2. That both byte orders are equally likely to be found in "modern" (new or updated) processor design.
It's not entirely incorrect, but a better phrasing could be used to clarify that little-endian is the more modern and common storage order, but you still cannot ignore big-endian.
However, this pessimistic tradeoff is just not true in case of Rust — it has been focused from the start on preventing footguns, and actually does a great job of it. You don't trade one kind of failure for another, you replace them with compilation errors, and they've even invested a lot of effort into making these errors clear and useful.
My concern isn't that the phrasing in the book is wrong, and I have expressly not argued that. It's that it presents the issue as having no further depth, and these two choices as equivalent. They aren't. The "Some processors are even able to switch between the two orders on the fly." that follows makes it even worse, at least to me it really sounds like you needn't give any care.
And the people reading this book are probably the people who should be aware of more real-world background on endianness, for the good of the next million of people dealing with what they produced.
https://github.com/fmtlib/fmt is what it's based on, for C++11 and up support.
int foo(int n, int (*a)[n]) { return (\*a)[5]; }
int main() {
int a[3];
return foo(ARRAY_SIZE(a), &a);
}
That syntax is why array overflows remain the #1 problem with C bugs in shipped code. It isn't any better than: int foo(size_t n, int* a) { assert(5 < n); return a[5]; }
int main() {
int a[3];
return foo(ARRAY_SIZE(a), a);
}
as the array dimension has to be handled separately from the pointer.Contrast with how simple it is in D:
int foo(int[] a) { return a[5]; }
int main() {
int[3] a;
return foo(a);
}
and the proof is shown by array overflow bugs in the wild are stopped cold. It can be that simple and effective in C.char* thing; // good
char *thing; // bad
This ... is awesome. As a C++ "native" I've always found the "star on the right" thing to be really horribly confusing.
// mutable pointer to mutable data:
char * str;
// Immutable pointer to immutable data:
char const*const str;
// Mutable pointer to an immutable pointer to a mutable pointer to immutable data:
char const**const* strs;
The constraints of the tool are inherited in the program; if the constraints encourage better design, then the program will have a better design. You benefit from the language providing a path of least resistance that forces intentionality. That intentionality makes the code easier to reason about, and less likely to contain bugs.
You do pay for this by writing more boilerplate, and by occasionally having to do some dirty things with void pointers; but these will be the exception to the rule, and you'll focus on them more since they are so odd.
POWER is bi-endian. In recent versions, Linux on POWER is little-endian (big-endian Linux on POWER used to be popular, until all the distros switched some years back), while AIX and IBM i are big-endian.
AIX and IBM i are probably not quite as alive as IBM mainframes are, but AIX is still arguably more alive than Solaris or HP/UX are, to say nothing of the dozens of other commercial Unix systems that once existed. Likewise, IBM i is just hanging on, yet still much more alive than most competing legacy midrange platforms (e.g. HP MPE which has been officially desupported by the vendor, although you can still get third party support for it.)
Also, I wouldn't be saying this if people didn't constantly try to recreate C++-isms in C. Which sometimes you need to do. So, then you have this strange amalgamation that kind of works but is super error prone and manual.
I also don't necessarily agree that C's constraints encourage better design. The design pushes far too much to runtime, which is poor design from a reasoning point of view. It's very difficult to reason about code when even simple data models require too much indirection. Also, the severely gimped type system means that you can do things you shouldn't be able to do. You can't properly encode type constraints into your types, so you then have to do more validation at runtime. This is also slightly improving, starting with _Bool years ago.
C++ definitely is a very flawed language with so, so many holes in its design. But the systems it has in place allows the programmer to more focus on the logic and design of their programs, and less on just trying to represent what they want to represent. And templates, as annoying as the errors are, prevent A LOT of runtime errors. Remember, every time you see a template that translates into pointers and runtime checks in C.
Also most companies making those platforms are not good at updating their toolchains. Expecting developers to compile their own toolchain, that is unsupported by platform vendor, is too much to ask.
Also GCC dropped support for certain architectures along the way, and even if you are willing to compile your own toolchain, it may not work for you.
I mean, one can reasonably argue that C & C++ declarator syntax is itself horribly confusing because it doesn't read left-to-right. But it is what it is, so why pretend that it's something else?
And no, you don't have to put everything in the header with C++, and especially not if you're using it in "C with classes" mode. C++ only needs it for templates - and even then only if you want to use implicit instantiation, with `extern template` available for fine-grained control allowing you to have those specializations separately compiled and implementations kept out of the header file.
I wonder sometimes why we keep insisting on the "OP ARG, ARG" format in general for assembly. Why not something like `MOV X -> Y` that would make it absolutely clear and unambiguous? For that matter, why not `COPY X -> Y`, since that's what it actually does?
That's A LOT of devices out there. A lot of which still get maintenance and even get feature updates (I'm working on one right now, C99).
So the claim that "C codebases generally use C11 or C17, and C++ code bases use C++20" intuitively sounds like totally untrue to someone working in embedded C/C++. I've been doing this for 15+ years and I've never touched anything higher than C99 or C++17.
If you're talking about gaming, sure. But that's not "C code bases generally".
Unless you think that code-bases created in the past year are a significant part of code bases that have been created since the inception of humanity.
I also think that it wouldn't be bad for code to be more generic. It is somewhat unnecessary for a procedure to allow an argument of type A but not of type B if the types A and B share all the commonalities necessitated by the procedure. Of course procedures with equivalent source code generate different machine code for different types A or B, but not in a way that matters much.
I believe it is beneficial for the language to see code as the description of a procedure, and to permit this description to be reused as much as possible, for the widest variety of types possible. The lack of this ability I think might be the biggest criticism I have for C from a modern standpoint.
Basically you can write a function that takes a tagged union and the compiler will passed the correct union based on named arguments.
int ret = foo(.slice = name);
int ret = foo(.src = str, .sz = strlen(str));
This code has a bug, and may even crash on some architectures:
execlp("echo", "echo", "Hello, world!", NULL);
This code doesn't have the bug: execlp("echo", "echo", "Hello, world!", nullptr);
Neither does this: execlp("echo", "echo", "Hello, world!", (char *)NULL);
When I say put it in the bin, I don’t mean that good software hasn’t been written already with it, or can’t be written with it. But you should stop using it given the earliest opportunity. When given the ability to write object-oriented software, clever engineers with too much time add insane complexity justified by unproven hypotheticals. Believe me, I know very well why people shy away from C++ like a trauma response. Overly-engineered/overly-abstracted complexity, incomprehensible template syntax, inadequate standard library, indecipherable error messages, C++ has its warts. But it is possible to write memory-safe software in C++, and it is not in C (unless we are talking about little code toys!). My answer is that you don’t have to write complicated garbage in C++. Keep it simple like you are writing C. Add C++ features only to get safety. Add polymorphism only when it solves a problem. Never write an abstract class ahead of time. Never write a class ahead of time.
Downvote me all day long. Call me angry. When billions of dollars are lost because someone, in our modern age, decided to write new software in C, or continue to develop software in C instead of switching to a mixed C++/C codebase with an intent to phase out new development in C.
It’s hard not to get angry when modern software is written with avoidable CVEs in 2020’s. Use after free, buffer overflows, are you kidding me? These problems should have been relics in 2010+, but here we are.
Amen. This is called progress.