NHacker Next
  • new
  • past
  • show
  • ask
  • show
  • jobs
  • submit
The C23 edition of Modern C (gustedt.wordpress.com)
belter 7 hours ago [-]
Important reminder just in the Preface :-)

Takeaway #1: "C and C++ are different: don’t mix them, and don’t mix them up"

jasode 6 hours ago [-]
>Takeaway #1: "C and C++ are different: don’t mix them, and don’t mix them up"

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...

tialaramex 5 hours ago [-]
The entire I/O streams (where std::cout comes from) feature is garbage, if this was an independent development there is no way that WG21 would have taken it, the reason it's in C++ 98 and thus still here today is that it's Bjarne's baby. The reason not to take it is that it's contradictory to the "Don't use operator overloading for unrelated operations" core idea. Bjarne will insist that "actually" these operators somehow always meant streaming I/O but his evidence is basically the same library feature he's trying to justify. No other language does this, and it's not because they can't it's because it was a bad idea when it was created, it was still a bad idea in 1998, the only difference today is that C++ has a replacement.

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)].

spacechild1 2 minutes ago [-]
Remember that C++ originally didn't have variadic templates, so something like std::format would have been impossible back in the day. Back in the day, std::iostream was a very neat solution for type safe string formatting. As you conceded, it also makes it very easy to integrate your own types. It was a big improvement over printf(). Historic perspective is everything.
dieortin 3 hours ago [-]
> The biggest problem is that today C++ doesn't have a way to implement this for your own types easily

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

tialaramex 2 hours ago [-]
I think the problem is that your idea of "easy" is "Here's a whole bunch of C++ you could write by hand for each type" while the comparison was very literally #[derive(Debug)]. I wasn't abbreviating or referring to something else, that's literally what Rust programmers type to indicate that their type should have the obvious boilerplate implementation for this feature, in most types you're deriving other traits already, so the extra work is literally typing out the word Debug.
pjmlp 4 hours ago [-]
Perfectly iostreams happy user since 1993.
Dwedit 3 hours ago [-]
int a;

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.

eMSF 3 hours ago [-]
How could you ever continue after the second statement without checking if you actually read an integer or not? How would you know what you can do with a?
jvanderbot 18 minutes ago [-]
You couldn't or wouldn't. but why have a read statement like cin>> which looks so nice and clean when you then have to go and check everything with flags and boolean casts on stateful objects.

I agree. It's lunacy. just be explicit and use functions or equivalent like literally every other language.

tightbookkeeper 3 hours ago [-]
You’re holding it wrong. Like nan, the point is you don’t have to error check every operation.

You check error for the whole batch.

codr7 4 hours ago [-]
Same, as long as I stay the hell away from locales/facets.

Type safe input/output stream types and memory backed streams served on a silver plate is a pretty decent improvement over C.

einpoklum 3 hours ago [-]
Then I suppose you don't care about:

* 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.

pjmlp 2 hours ago [-]
I care about performance, when it actually matters to acceptance testing.

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.

wakawaka28 29 minutes ago [-]
C and C++ are the bedrock of operating systems with the best performance and extensive support for all languages.

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.

tightbookkeeper 3 hours ago [-]
Yep, it’s very clean once you get the hang of it.
wakawaka28 33 minutes ago [-]
>No other language does this, and it's not because they can't it's because it was a bad idea when it was created, it was still a bad idea in 1998, the only difference today is that C++ has a replacement.

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.

tightbookkeeper 3 hours ago [-]
What’s wrong with it?
lugu 4 hours ago [-]
Thank you.
accelbred 5 hours ago [-]
C++ can seamlessly include C89 headers.

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"`.

nuancebydefault 5 hours ago [-]
Extern "C" around the prototypes is mandatory, otherwise your linker will search for C++ symbols, which cannot be found in the C libraries you pass it.
Conscat 5 hours ago [-]
Clang supports C11 - 23 in C++, as well as some future C features like fixed-point integers. The main pain points with Clang are just the fundamental differences like void* and char, which don't typically matter much at an interoperability layer.
kccqzy 5 hours ago [-]
Yeah plenty of headers first have `#ifdef __cplusplus` and then they add `extern "C"`. And of course even then they have to avoid doing things unacceptable in C++ such as using "new" as the name of a variable.

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.

Someone 4 hours ago [-]
> C++ code can seamlessly add #include "sqlite3.h" unchanged.

Almost seamlessly. You have to do

  extern “C” {
    #include "sqlite3.h"
  }
(https://isocpp.org/wiki/faq/mixing-c-and-cpp#include-c-hdrs-...)
cornstalks 3 hours ago [-]
If we're nitpicking then sqlite3.h already has `#ifdef __cplusplus` and `extern "C" {`. So yes, from the user's perspective it is seamless. They do not need to play the `extern "C" {` game.
4 hours ago [-]
MathMonkeyMan 3 hours ago [-]
My brief foray into microcontroller land has taught me that C and C++ are very much mixed.

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.

pjmlp 7 hours ago [-]
Specially relevant to all those folks that insist on "Coding C with a C++ compiler", instead of safer language constructs, and standard library alternatives provided by C++ during the last decades.
flohofwoe 6 hours ago [-]
Funny because for a long time the Microsoft MSVC team explicitly recommended compiling C code with a C++ compiler because they couldn't be arsed to update their C frontend for over two decades (which thankfully has changed now) ;)

https://herbsutter.com/2012/05/03/reader-qa-what-about-vc-an...

rdtsc 6 hours ago [-]
That thing always baffled me, this huge company building a professional IDE couldn't figure out how to ship updates to the C compiler.

> 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!"?

Jtsummers 5 hours ago [-]
It's not baffling, it's remarkably consistent. They implemented Java as J++ and made their version incompatible in various ways with the standard so it was harder to port your code away from J++ (and later J#). They implemented things in the CSS spec almost exactly opposite the specification to lock people into IE (the dominant browser, if you have to make your site work with 2+ incompatible systems which will you focus on?). Not supporting C effectively with their tools pushed developers towards their C++ implementation, creating more lock-in opportunities.
pjmlp 4 hours ago [-]
It was on purpose, Microsoft was done with C, the official message was to move on to C++.

The change of heart was the new management, and the whole Microsoft <3 FOSS.

rdtsc 4 hours ago [-]
> It was on purpose, Microsoft was done with C

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.

AlotOfReading 5 hours ago [-]
Funnily enough, the intellisense parser does support C syntax because it's using a commercial frontend by edison under the hood. MSVC's frontend doesn't.
pjmlp 4 hours ago [-]
Yeah, 12 years ago, when governments couldn't care less about nation state cyberattacks, and Microsoft was yet to be called by the Congress to testify on their failures.
com2kid 6 hours ago [-]
Perfectly valid to do if you need to interface with a large C code base and you just want to do some simple OO here and there. Especially if you cannot have runtime exceptions and the like.

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.

f1shy 6 hours ago [-]
What would be an example of "simple OO here and there" that cannot be done cleanly in plain C?
com2kid 18 minutes ago [-]
You can do anything in C that you want to. Of course one can make v-tables and all of that, and even do inheritance.

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.)

bobmcnamara 6 hours ago [-]
Templating on pixel classes so that a blitter builds all supported pixel paths separately and inlines them.

Yes you can do it less cleanly with macros or inline functions. But you can't do it performantly with struct and function pointers.

5 hours ago [-]
raluk 6 hours ago [-]
RAII
f1shy 6 hours ago [-]
Is RAII Object orientation? I thought it was an idiom of C++ by Stroustrup.
runevault 5 hours ago [-]
It doesn't necessarily have to be OO no. Rust uses RAII and it uses traits instead of traditional OO style inheritance etc. You do need something like destructors/drop trait for it to work as far as I know though.
tjoff 6 hours ago [-]
The killer feature of RAII is when combined with exceptions. But sneaking in exceptions in an embedded C project isn't something I'd encourage or recommend.

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).

kccqzy 5 hours ago [-]
There are a lot of large C++ shops that purposefully disable exceptions and yet still use RAII usefully. It's so useful that in many C codebases you see people using RAII. For example Gtk has g_autoptr and g_autofree.

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.

tjoff 3 hours ago [-]
RAII is useful without exceptions yes. I guess it is the other way around. Exceptions are not useful without RAII (sorry not sorry most garbage collected languages ;)).

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.

AlotOfReading 5 hours ago [-]
C++ offers lots of compelling things for embedded use cases, like enum classes (finally fixed in C23), constexpr, std::optional, namespaces, and atomics/generics that are much smaller dumpster fires.

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.

tjoff 4 hours ago [-]
I don't disagree, but these are in the ~2% convenience at most. With the huge baggage of including C++ in a project. The cost of learning C++ easily outweighs all those benefits. If you happen to have a proficient C++ team (that actually know embedded), go for it!
AlotOfReading 4 hours ago [-]
Speaking more broadly than just the std implementation, but result types like optional shouldn't be a 2% convenience, they should be used in most function calls that return errors. Rust is the obvious example here.
tjoff 3 hours ago [-]
If you argue for Rust I'm all for it, arguably much less of a learning curve than C++ too.
adamrezich 6 hours ago [-]
Namespaces, methods.
f1shy 6 hours ago [-]
Namespaces is not object orientation, is it? Am I missing something? You can place functions (methods) inside of structs in C23, can't you?
int_19h 6 hours ago [-]
You can handcode vtables in C, just as you can handcode loops in assembly (i.e. it works but it's verbose, not particularly readable, and brings more footguns).

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?

uecker 5 hours ago [-]
I don't know, I never found the "proper abstraction" be more than irrelevant syntactic sugar. And the cost of C++ is that you end up putting everything in the header (IMHO the biggest design flaw of the language) and then compile time start to get long....
staunton 6 hours ago [-]
On a high level, "object orientation" means you think of your code as representing the state and interactions of objects. You can equally well do this in assembly. If you think of some namespace as a "singleton object" then that's what it is.

I guess what you're really asking is what are the best or most common ways to do OO in C?

f1shy 6 hours ago [-]
Oh. I learned that object orientation is primarily a way to structure data and code, such that the data is encapsulated with the code that works on it, in so called objects. So an Object is the Data, plus the functions that work on the data, an ensure that some invariants are kept. In OO parlance, that code gets executed by sending messages (calling methods).

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...

int_19h 6 hours ago [-]
There's no clear definition of what OO is, so the best you can do pragmatically is look at mainstream languages that are broadly recognized as OO and try to deduce the commonalities.

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.

epcoa 6 hours ago [-]
> 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.

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.

adamrezich 6 hours ago [-]
Correct, and you did ask specifically for OO things, but I thought I'd list namespaces too as far as “C++ things you might use when writing C-like C++ code”.

Another big one that I always forget C still doesn't support is function overloading.

uecker 5 hours ago [-]
Function overloading is a feature that makes code less self-documenting without providing any meaningful value. Operator overloading is more interesting, because you can build you domain language with nice syntax. But I also tend to think that this is not really worth it.
adamrezich 3 hours ago [-]
In C++ where you have methods? Sure. It would be nice to have in C, though. But, alas, ABI compatibility.
cozzyd 5 hours ago [-]
CRTP?
pjmlp 4 hours ago [-]
Yeah, but one should provide C++ type safe abstractions on top.

Just like one doesn't use Typescript to keep writing plain old JavaScript, then why bother.

Spivak 7 hours ago [-]
I mean as long as your goal is specifically to do that I think it's fine. Using a C++ compiler to compile a C program isn't that rare.
f1shy 6 hours ago [-]
A couple of months ago, in the company I work, there was a talk from HR, where they explained how to make a good CV (the company is firing lots of people). She say: "if you have experience in programming C, you can writing just that, or, if you have lots of experience in C, is customary to write ``C++ Experience'' "

Sooo... yeah... I should definitely change company!

kstrauser 6 hours ago [-]
That literally made me do a spit take, and it was fizzy water and it burned.

My god. That's amazing.

thenipper 3 hours ago [-]
How many pluses until you should just say you have D experience?
4 hours ago [-]
jpcfl 7 hours ago [-]
Bjarne should have called it ++C.
card_zero 6 hours ago [-]
Because people choose to use pre-increment by default instead of post-increment?

Why is that?

int_19h 6 hours ago [-]
It should be ++C because with C++ the value you get from the expression is the old one.

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.

card_zero 6 hours ago [-]
K&R seems to use pre-increment early on, then post-increment consistently (or a lot, anyway, I haven't done a thorough check) after chapter 3, in situations where either would do. In fact, after introducing post-increment at 2.8.
wpollock 2 hours ago [-]
The PDP-11 that C originally targeted had address modes to support the stack. Pre-increment and post-decrement therefore did not require a separate instruction; they were free. After the PDP-11 went the way of the dodo, both forms took a machine cycle so it (mostly) became a stylistic issue. (The two operators have different semantics, but the trend to avoid side-effects in expressions means that both are most often used in a single expression statement like "++x;" or "x++;", so it comes down to your preferred style.)
jejdjdbd 6 hours ago [-]
Why would you use post increment by default? The semantics are very particular.

Only on very rare occasions I need post increment semantics.

And in those cases I prefer to use a temporary to make the intent more clear

codr7 3 hours ago [-]
If you're used to the idiom, the intent couldn't be clearer.

I miss it when switching between C/++ and other languages.

card_zero 6 hours ago [-]
People seem to mostly write a typical for loop ending with ; ++i){

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.

johannes1234321 6 hours ago [-]
> why would you use those very particular semantics?

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.

layer8 5 hours ago [-]
In C++ the semantics can differ, in that copying an object for post-increment might require a memory allocation internally (for example in the case of a BigInt class), which may fail and throw an exception. For consistency, using pre-increment by default and unless you really need post-increment, is a good habit.
secondcoming 4 hours ago [-]
It makes no difference if the increment is done on an int, but it can make a different if your `i` is some object with its own ++ operator.
Quekid5 4 hours ago [-]
I've seen either style, but it the argument about which is proper is pointless. Any modern compiler will optimize either equally well, unless you're doing something that actually depends on the order of the increment.
kanbankaren 4 hours ago [-]
No, for fundamental datatypes pre/post-increment doesn't matter, but for classes that overload those operators, the postfix form creates a temporary object hence people write

for(auto it = begin(v); it != end(v); ++it)

tialaramex 6 hours ago [-]
Why use this operator? Like most C and C++ features the main reason tends to be showing off, you learned a thing (in this case that there are four extra operators here) and so you show off by using it even if it doesn't make the software easier to understand.

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.

layer8 5 hours ago [-]
The idiomatic

    void strcpy(char *s, char *t)
    {
        while (*s++ = *t++)
            ;
    }
(straight from K&R) wouldn’t work without it.
n_plus_1_acc 3 hours ago [-]
Which many people find unreadable compared to other versions.
cozzyd 5 hours ago [-]
It's more useful for pointers than for values, IMO
jhatemyjob 3 minutes ago [-]
Can someone link me to an article that explains why C is basically frozen at C99 for all practical purposes? Few projects worth talking about leverage features from C11 and newer
zkirill 6 hours ago [-]
I was going to ask if there is a good list of C books and then answered my own question. It categorizes _Modern C_ as Intermediate level.

https://stackoverflow.com/questions/562303/the-definitive-c-...

emmanueloga_ 2 hours ago [-]
Note that this is not a complete list, fwiw. For example, I doesn't include "Effective C." [1].

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.

--

1: https://nostarch.com/effective-c-2nd-edition

auggierose 7 hours ago [-]
Table of contents in the sidebar doesn't work properly for me when I click on an entry (in macOS Preview).
bwidlar 6 hours ago [-]
I just test some links in the table of content, works fine for me. Using zathura pdf reader.
Jtsummers 6 hours ago [-]
Also works in Adobe and Firefox, but doesn't work in Chrome and Edge.
f1shy 6 hours ago [-]
Doesn't work for me either... but I will not dismiss the book because of that.
channel_t 6 hours ago [-]
Table of contents is definitely broken right now.
soegaard 6 hours ago [-]
Same here.
enriquto 7 hours ago [-]
So happy that we still get the dinosaur mascots! This is a good book.
bitbasher 7 hours ago [-]
Really looking forward to #embed, once the compilers catch up. Until then, Golang.
enriquto 6 hours ago [-]
This is not how C standards work. If it appears in the standard, it means that it is already implemented in some compilers (in that case, at least in gcc and clang).
pjmlp 4 hours ago [-]
That isn't really how it goes, that is how it used to be up to C99.
enriquto 4 hours ago [-]
Thanks for the correction! Do you know if there is a document from the standards body explaining the change in philosophy?
JonChesterfield 2 hours ago [-]
It's a nuisance to implement the thing you want to add to the standard yourself. It's easier to ship it in the language and then complain at compiler devs that they're running behind the edge of progress.

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.

MathMonkeyMan 3 hours ago [-]
I've heard something along the lines of "the standard is to define facilities that will be used in most programs, and to codify widespread existing practice." That was in the context of "I don't like this proposed feature," though. This was for C++, not C.

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.

f1shy 6 hours ago [-]
jpcfl 6 hours ago [-]
Or

    xxd --include <file>
:)
Keyframe 4 hours ago [-]
The anti-Rust approach!
accelbred 5 hours ago [-]
I end up using a .S asm file with .incbin directives to embed files.

#embed would be much nicer

JonChesterfield 2 hours ago [-]
Incbin works just fine from inline asm fwiw
rfl890 6 hours ago [-]
Clang 19 has it.
russellbeattie 3 hours ago [-]
Wow, the use of attributes like [[__unsequenced__]], [[maybe_unused]] and [[noreturn]] throughout the book is really awful. It seems pretty pedantic of the author to litter all the code examples with something that is mostly optional. For a second I wondered if C23 required them.
amomchilov 5 minutes ago [-]
Such is the issue with bad defaults. Opting into the sensible thing makes most of your code ugly, instead of just the exceptions.
nimish 3 hours ago [-]
My kingdom for fully specified, well defined portable bitfields.
leonheld 3 hours ago [-]
One of my favorite books ever.
jumpman_miya 6 hours ago [-]
in example 1.1 i read that as 'tits_square' until i saw the output
glass-z13 6 hours ago [-]
that's by design
einpoklum 2 hours ago [-]
It's only been a few years since I've come to feel I can rely on C compilers all supporting C99, for a library I'm maintaing [1]. And after a couple of years, sure enough - I get an issue opened asking for C89 compatibility because of some arcane embedded toolchain or what-not.

So, C23? ... that's nice and all, but, let's talk about it in 20 years or so T_T

[1]: https://github.com/eyalroz/printf

ralphc 6 hours ago [-]
How does "Modern" C compare safety-wise to Rust or Zig?
WalterBright 5 hours ago [-]
Modern C still promptly decays an array to a pointer, so no array bounds checking is possible.

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.

layer8 5 hours ago [-]
> no array bounds checking is possible.

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. ;)

uecker 5 hours ago [-]
Right. Also it might it sound like array-to-pointer decay is forced onto the programmer. Instead, you can take the address of an array just fine without letting it decay. The type then preserves the length.
WalterBright 39 minutes ago [-]
C: int foo(int a[]) { return a[5]; }

    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:

https://www.digitalmars.com/articles/C-biggest-mistake.html

codr7 3 hours ago [-]
Nice, when you know the length at compile time, which is rarely from my experience.

The holy grail is runtime access to the length, which means an array would have to be backed by something more elaborate.

TZubiri 3 hours ago [-]
"The DeathStation 9000"

The what now?

bsder 1 hours ago [-]
Nasal daemons for those of us of a slightly older vintage ...
layer8 3 hours ago [-]
Google it.
TZubiri 50 minutes ago [-]
Yeah, why have any type of human interaction in a forum when you can just refer your fellow brethren to the automaton.
layer8 26 minutes ago [-]
I’m saying this because any explanation I could offer would provide less insight than the Google results.
renox 5 hours ago [-]
You'd be surprised: Zig has one UB (Undefined Behaviour) that C doesn't have!

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..

uecker 5 hours ago [-]
UB is does not automatically make things unsafe. You can have a compiler that implements safe defaults for most UB, and then it is not unsafe.
duped 3 hours ago [-]
That's implementation defined behavior, not undefined behavior. Undefined behavior explicitly refers to something the compiler does not provide a definition for, including "safe defaults."
fuhsnn 3 hours ago [-]
Compilers are not prohibited to provide their own definition for UB, that's how UBsan exists.
renox 2 hours ago [-]
Well Zig has ReleaseSafe for this.. ReleaseFast is for using these UBs to generate the fastest code.
ahoka 4 hours ago [-]
By definition UB cannot be safe.
marssaxman 3 hours ago [-]
this depends on your choice of definition for "safe"
3 hours ago [-]
secondcoming 4 hours ago [-]
Does C automatically wrap? I thought you need to pass `-fwrapv` to the compiler to ensure that.
greyw 4 hours ago [-]
Unsigned overflow wraps. Signed overflow is undefined behavior.
kbolino 4 hours ago [-]
This distinction does not exist in K&R 2/e which documents ANSI C aka C89, but maybe it was added in a later version of the language (or didn't make it into the book)? According to K&R, all overflow is undefined.
wahern 3 hours ago [-]
I don't have my copy of K&R handy, but this distinction has existed since the initial codification. From C89:

  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.txt
4 hours ago [-]
renox 4 hours ago [-]
-fwrapv is for signed integer overflow not unsigned.
sp1rit 3 hours ago [-]
Yes, as unsigned overflow is fine by default. AFAIK the issue was originally that there were still machines that used ones complement for describing negative integers instead of the now customary twos complement.
jandrese 4 hours ago [-]
Modern C is barely any different than older C. The language committee for C is extremely conservative, changes tend to happen only around the edges.
zerr 6 hours ago [-]
Do they still use 0-terminated strings/char* as the main string type?

Is the usage of single linked lists still prevalent as the main container type?

racingmars 5 hours ago [-]
> Do they still use 0-terminated strings/char* as the main string type?

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.

eps 3 hours ago [-]
Don't feed the trolls.
EasyMark 10 minutes ago [-]
I don’t think that will ever change. Will the possibly introduce a more modern string2 type? Maybe but it will probably be unlikely before 2050
codr7 3 hours ago [-]
Embedded linked lists are pretty cool though.
KerrAvon 3 hours ago [-]
The C standard library provides no recognizable container types, so there's no "main" anything.
Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact
Rendered at 00:16:10 GMT+0000 (Coordinated Universal Time) with Vercel.