I've preferred C to C++ and D to C++ for a long time. To me when I read C++ code it's like the modem disconnected in the middle of the editing session with all the : and << flying around.
For things that don't need classes (and there is a lot of code) I stick to C. But if I need the OOP benifits then I go with D. As a bonus, the C programmers and JavaScript programmers that I work with can read the D code and get most of it on the first try.
If you haven't used D, spend 30 mins and write a four function calculator. Then take the remaining time adding some kind of internal function line sin, cos, etc. You'll be suprised at how fast you can pick it up.
Granted, the above is a toy exercise, it's not writing an enterprise solution. But it's a little more than the standard "Hello Sailor, new in town" program that people start off with.
I also prefer C and D to C++. But used wisely, C++ can be a really nice language after all changes that started with C++11.
The key is to realize C++ is not a language, but a federation of languages. You need to cherry pick a subset you what you want to use and stay disciplined. Otherwise, it is a mess.
The whole "subset" argument is pretty thin. There are style/convention guides, and there are are more obscure features in C++ the same way there are more obscure features in other languages. But it is not necessary to cherry pick a subset.
Out of places that are vaguely keeping up with C++, the only thing I'd consider sub-setting that is commonly applied to C++, is disallowing exceptions and RTTI. There are at least decent reasons for this. Yes, there are places that have additional constraints (like "C with classes" style, i.e. no templates), but it's much more rare (and even more rarely technically justified).
Sub-setting should not be confused with the fact that in many cases, the language does not push you as hard down a specific path (for better or worse), yet it might be beneficial for a specific company in a specific domain to have a common solution for something, which results in the company style guide saying: "for this use case, use company_lib::foo, not XYZ".
Where I work we use pretty much all of C++, as appropriate, that is there is no blanket ban on anything, or official subset. That does not mean that e.g. there is virtual inheritance all over the codebase; that's a feature you should pretty much never need to use. Writing code appropriately and consistently can only ever be done via discussion and code review; no subset will ever magically fix these issues anyway.
My problem when trying to pick up C++ (to write a simple SDL2 game) was that I just couldn't understand the RAII concepts, especially with modern smart pointer classes and STL container classes, and understanding how to use these three features together to be productive. (I just wanted to make a simple grid of cells to port over my simple PICO-8 lemmings-like game.)
Coming from C and thinking of everything in terms of pointers, it was already foreign enough, but the problem was that I couldn't find a definitive reference on how these features interacted and how to use them together successfully. So I gave up on C++. Maybe if I was motivated by needing it for a client project I might have gotten further, who knows.
I suggest you take a serious look at C++ again. It truly is a versatile and powerful language. While it is true that there are a bunch of language features that can be misused, it places the onus squarely on the Designer/Programmer which is how it should be. You can use it as a thin wrapper over C or go as deeply as you like into the OO and Generic programming paradigms. At the same time all abstractions have minimal runtime overhead and pay-as-you-need only.
I was lucky that i discovered C++ early and hence started with it as a "better C". I was not exposed to a lot of upfront complexity (eg. template shenanigans) thus making my learning curve easier. The big mistake people new to C++ make is trying to learn all language features and dark corners. Instead you should look at various aspects of the language separately and understand their applications. That way you learn how to model the problem domain using the appropriate syntactic features of the language. Here are a few different ways of looking at and using the language.
1) As a better C - You can define stricter types, control memory management using techniques like RAII/Smart pointers and enforce better modularization.
2) As an Object-Oriented language - Here you design class hierarchies and provide interfaces, domain libraries and frameworks.
3) As a Generic programming language - Here you define types and learn how to combine them using composition and delegation.
It might be helpful for you to read some of the older C++ books (Modern C++ IMO is more complicated since it mixes the language features in a free manner) to get at the root of "how to think in C++". To that end you might find the following useful;
1) Ruminations on C++ : A Decade of Programming Insight and Experience by Andrew Koenig and Barbara Moo - short chapters explaining various implementation techniques in C++.
2) Scientific and Engineering C++ : An Introduction with Advanced Techniques and Examples by Barton and Nackman - This book will teach you how to design in C++
3) Multi-paradigm design for C++ by James Coplien - Advanced book teaching you how to map problem domain concepts onto language features.
IMO, If you grasp the gist in the above books, you will understand "the heart of C++" and can easily pick up "Modern C++".
The books i had mentioned are old pre-C++11(hence you can easily get cheap used copies) but which give you insight into "how to think and program in C++". They are still relevant, though might not see usage currently, because the language/libraries have evolved.
I am ambivalent on "Modern C++"(they have made it more complicated and invented a new language) and still ramping up on its features and nuances. Haven't really found any insightful book so far (except for "C++ Concurrency in Action" by Anthony Williams which of course is specialized).
However i recently found "Modern C++ Programming Cookbook" by Marius Bancila which seems like a very nice catalog of all the C++11/14/17 features. This seems to go well together with Stroustrup's book.
That's a pretty vague statement though. vector and unique_ptr are class templates (generic classes). To understand them you need at least a basic idea of how that works, which you probably do. After that, you just need to understand their API, which includes copy/move constructors, and destructors, which encompasses RAII. There are tons of blog points explaining RAII.
To be honest, I don't remember exactly what feature it was. Just that I couldn't easily encapsulate a 2d grid of "replaceable" cells. This was like 4-5 months ago and the only documentation we had on C++ at the time was the official Stroustrup book, which was dense.
That sums up my experience. It's hard to find good quality docs, that's up-to-date and not just a beginner's intro that doesn't answer my questions. The only other alternative usually seems to end up being man-pages or other extremely dense and legalesque docs like a ISO standard or something.
Thanks for the sample grid, I've bookmarked it, this'll be helpful for me and my son to understand where we took the wrong road in our implementation.
1. const correctness is awesome. I miss it so much in C# which I happen to code a lot as well. When you have a function which accepts `const Grid<int>& grid`, you can’t call resize() nor change cell values. If you’ll try, the code won’t compile. My given name is Const so I have bias, but still.
2. asserts. They aren’t even C++, these are from C, but IMO they have better ergonomics than C++ exceptions. They compile into nothing in release builds i.e. don’t affect performance, but in debug builds they trap to debugger right away, showing what exactly is not OK. For production code, sometimes it’s a good idea to use preprocessor trickery to turn failed asserts into scary log messages, in release builds.
P.S. It’s possible to implement much better resize(). When neither old nor new size is empty, the version on that gist will essentially turn old data into garbage. A better solution for that case, make a new vector on the stack, write a loop to crop or expand the items (e.g. calling std::copy_n), then call vector::swap to replace Grid::data vector with the newly built one.
> The key is to realize C++ is not a language, but a federation of languages. You need to cherry pick a subset you what you want to use and stay disciplined.
The issue is that no matter how disciplined you are, others will be using a different subset of the features available and you end up using different "languages", even when everyone on your team is ostensibly writing "C++".
Aren't all powerful languages poised to have this problem?
You can write generic imperative C++ by using lots of templates, OO code that looks like Java, low level imperative code similar to C, and even functional code.
I can personally say the same thing about Haskell or Common Lisp. Perhaps it's not that extreme, but there are many ways to solve problems in those languages. Even some funny jokes about Haskell exploit this:
> Aren't all powerful languages poised to have this problem?
Probably. How many of these powerful languages started out that way and received widespread adoption though? C++ was basically C with classes when it become big. C# and Java are complex beasts now but both gained popularity when they were much simpler.
Programming languages need to be comprehensible to your average programmer and not just a few elites.
I feel a bit that way about rust at the moment. I am still learning, but I have found that a lot of libraries seem to use very different interface modalities, so I spend a non trivial portion of my development time figuring out how to glue these disparate things together.
The alternative is using totally different languages, the barriers between which are much higher than between different subsets of C++. You still benefit from being part of the C++ ecosystem.
> The key is to realize C++ is not a language, but a federation of languages. You need to cherry pick a subset you what you want to use and stay disciplined. Otherwise, it is a mess.
I've noticed two strains in language design, which I internally call Scheme-style and Common Lisp-style:
Scheme-style languages are the trimmed-down languages, often built around a big idea which animates the rest of the design but not always; the defining concept of Scheme-style languages is always how much they can remove to get a pure design, a design which supports writing code in one style. Python, Smalltalk, C, and Scheme are all languages in this mold. Java is kind of a compromised Scheme-style language, or two of them (C and Smalltalk) rammed into each other at high speed, which also goes for Objective-C.
Common Lisp-style languages have mini-languages inside of them, or at least support multi-paradigm programming as a core design goal; you can trim a Common Lisp-style language into a Scheme-style subset, but you can't extend a Scheme-style language into something more Common Lisp-like unless you really are dealing with Scheme and have (hygienic) macros to use to add more syntax. C++, Perl, and Common Lisp are all Common Lisp-style; Unix shell is Common Lisp-style in practice because it encourages programmers to write in a bunch of relatively Scheme-style languages at once, all in the same program.
Note that both Scheme and Common Lisp have a conceptual core in the Lambda Calculus and axiom-like special forms. C++ lacks this and Perl doesn't really have it either, but most people don't write huge codebases in Perl.
> Java is kind of a compromised Scheme-style language, or two of them (C and Smalltalk) rammed into each other at high speed, which also goes for Objective-C.
Not much to add to the conversation here, but for some reason every time I read this it gets funnier and funnier.
But I do like your breakdown, it’s what I’ve been thinking whenever I see discussions like this.
Unfortunately, it seems really hard to learn a modern "good" subset of C++. Too many resources teaching it in too many ways. As an outsider, I have no idea what is good or standard.
Even when I only really need C, the contract programming that D provides is really helpful as it provides a macro free way of doing rigorous assertions etc. in a way that I can turn off without infecting the actual logic code.
If a struct's payload cannot be null, for example, getting the compiler to insert checks into all uses in debug builds can be done easily
That and metaprogramming in D is all compile time so you get RSI-free hands, for free.
>> I've preferred C to C++ and D to C++ for a long time. To me when I read C++ code it's like the modem disconnected in the middle of the editing session with all the : and << flying around.
I'm sorry, but even though I think D is a great language, there are plenty of usecases where D isn't a viable alternative to neither C or C++.
If D had all those missing features, or if you compared D to some other mainly-GC:ed, statically typed and compiled language, I think the syntax comparison would have been more fair.
This is one of the things i dislike most about templates in C++. Even today reading template code with its nested syntax takes me time to unpack and understand. Symbols do affect comprehension and i would be quite interested in knowing whether somebody has done a study w.r.t. the various programming languages.
How much software genuinely requires not having a GC? If you don't want it, don't use it, but using it can make your software completely safe and fast (Garbage Collection is often faster than RC as long as pause times are acceptable, and added memory use).
A lot of the standard library ranges are inherently lazy and therefore require no interaction with the GC. Exceptions are also nogc now.
If you can tolerate GC then like 20 languages (as or less obscure than D) enter the conversation. If you can tolerate GC and the performance implications that go with it, then using C or C++ is rarely a good choice to begin with (well, other than legacy, which is a valid and common reason).
D isn't defined by performance or memory management, however. There are many D features which are just blisteringly professional in how they approach software development: Using D is conducive to writing good software.
Metaprogramming, for example, in D is obvious and almost fun. The compile times are ridiculous compared to C++, i.e. I can build the main D compiler in three different configurations in about 5sec on my machine.
I'm not saying that D is defined by that. I'm simply saying that if you're comparing to C or C++, you are talking about use cases that are defined by performance and/or memory management (or else, they are being used for legacy reasons). If you have a green field project that doesn't have massive performance concerns, and doesn't have to be specifically in C or C++ for other reasons (toolchain availability, available developer resources, etc), you probably aren't using C or C++.
Some people would argue that you can use D for equally high performance things to C++, and make sure you use the GC very selectively, etc. However, you don't appear to be making that argument. If you aren't, then there's just no real point comparing C++ and D. If you don't have any of those requirements, and you are ok with obscurity, you have much stiffer competition from many other languages like Haskell, Kotlin, etc.
There are people who specifically use D for writing high performance applications. They find it faster than C++ for a rather subtle reason - D code is more plastic, meaning it is easier to refactor D code trying out different algorithms looking for a faster one.
I'm sure these people exist but they're the exception rather than the rule. The two biggest industries in SG14, the C++ low latency study group, are games and HFT. In both these areas, D penetration is pretty much zero. And in HFT at least while many features would be nice (especially reflection), I can't imagine it would be anything but a performance hit. Just the fact that the best D implementation for performance is llvm based, and most people do not find that llvm produces assembly as good as gcc or icc, is already an instant global hit.
I didn't intend to imply otherwise but I see my wording was unclear. I meant that D generally would be a hit, though it's only really speculation either way.
There is no inherent reason that D code would be slower than C++ code.
I should know, I've written a C++ compiler and know where the bloat is buried, and designed D to eschew features that would be inherently slower than the C++ counterparts.
You can even turn off the runtime array bounds checking.
LLVM and GCC are so close together these days that it makes almost no difference practically, and when it does you should start optimizing based around your code not your compiler.
Dude I mean the context in which I'm discussing this is a large organization operating on timescales shorter than microseconds with numerous people involved in optimizing every single part of the pipeline. We are waaaaaaaaaaay past the point of "premature" optimization. This is just optimization, and optimization where a few percent difference between compilers is huge.
Your comments read like you're explaining optimization to a beginner, it's a bit bad faith tbh.
GCC and LLVM have broadly similar performance characteristics(i.e. for any arch/uBenchmark combo there is another that puts the other one faster).
If GCC is appreciably faster for your purpose, then fair enough but LLVM is not drastically slow by any means (Especially when you consider that any "Anything you can do..." Between LLVM and GCC moves much faster on the LLVM side due to a much saner codebase)
You are talking about it like comparing a Ford to a VW they are pretty much the same, but what the poster above talks about is more like comparing formula 1 cars where a single percent of engine power can win or lose you the race. The difference may be small enough for by far the most purposes, but this one is where the small difference can cost a lot.
HFT doesn't subset C++ nor does it ignore the standard library. Given you're totally wrong about one I'm not inclined to believe you on the other, and I have an examples from the standard library used in game dev eg atomics.
Well you can certainly elect to torture yourself if you really want to. Atomics in C++ compile to assembly in very straightforward ways and there isn't really an enormous design space there. Preshing certainly makes it seem like Ubisoft is using C++ atomics; if it makes sense for them with so many developers and creating AAA games... I'd be curious to know the motivation for writing their own.
Except that in HFT we don't generally disable RTTI or exceptions. I work at a top HFT firm, have friends/colleagues at other top firms, have seen multiple people like Carl Cook at Optiver state in talks that they use exceptions... So what on Earth are you talking about?
Not using some or most of the standard library is precisely not an example of subsetting C++. Just because the C++ standard library has a hash table available doesn't mean that every project has to use it. Companies standardizing their own high performance data structures where it makes sense, and other things as well, is just something that happens and often makes sense independent of language.
Ultimately, though, this is the real tragedy of D. It started as a C clone, and continues to be named like it's a C clone, but it is no longer a C clone. It is a general-purpose memory-aware compiled programming language inspired by C++, but it is pretty different by now! People expecting a C clone miss what else it has to offer.
(Can it be a C replacement? Sure, but so can Rust/Swift/Go/etc)
Just like C does for calling into main(), doing floating point emulation if CPU not available, calling library initializers (common C extension), handling VLA allocations.
Just because it is tiny does not make it inexistent.
Then there is the whole POSIX, which is kind of runtime that wasn't made part of ISO C, but follows it everywhere.
Linux kernel surely used it during the time they had VLAs in, they are now mostly removed thanks to Google efforts reducing memory exploits on the Linux kernel.
One just links another implementation, just like printf() vs printk().
I’ve seen large, poorly written Java programs[0] where an entire core at 100% is dedicated to the GC. I have never seen that behavior for things like ~shared_ptr in C++ or similar languages.
Then again, OOMing due to leaks is something you’re more likely to see in poorly-written systems in non-GC languages (Objective-C, C, C++, etc), so there are certainly trade offs.
[0]: Maybe “poorly written” is unfair. Perhaps “written by people who are unaware of how to write code that performs well in a GC system”.
Probably the most important usage of C are shared libraries. How well it will work if I have D library with it's own GC, another Go library with second GC and app written in C# with yet another GC?
You are right most software it doesn't matter if you have a GC. However then D is not competing against C and C++ it's competing against almost every other language.
If you never allocate from it (which is able to be verified very easily with the @nogc attribute), it will never trigger.
You can disable it and trigger when you feel it is needed.
You can choose to just leak.
The default GC is OK, but not great. However it recently was made multithreaded in the mark phase so it is much faster than it used to be.
There is a fork based (on linux only IIRC) GC, used by several companies for hard realtime systems, that marks and sweeps in a separate process with CoW pages.
Not using the GC is rather easy, there are plenty of mitigation methods.
Not using the D runtime is much more involved.
Not linking with the D runtime is a more hardcore endeavour.
D has a built-in GC by default and doesn't force you to manage memory like Rust. Also, the author mentioned that D is pretty straightforward to anyone with some basic programming knowledge (think C, C++, Java, and C# which fits the majority of coders). Rust is very different from those languages. My main goto language is Python and I can understand D a lot more than Rust which confuses me to no end (disclaimer: I'm a total noob).
Go is probably as easy to read as D if not more and is also GC and fast. It is also very spartan. I bet D has a lot of additional features.
With Rust I always find myself fighting the compiler just like with C, it's very strict about everything, no GC, steep learning curve.
Go is okay but you can't get clever with it. You're productive from the start butit becomes routine quite fast and at a certain point there isn't anything new to learn. Error checking is also quite annoying.
D has the best of both worlds and it also plays nicely with C/C++, something that the author of Zig also understood is a must have in any language that aims to be a better C/C++. In D one is productive from the start and there's always something new to learn. And one learns it as they code, without having to first read Programming in D by Ali Çehreli to make any sense of the language.
I also tried Crystal and liked it but one is more productive in D and Go without any prior knowledge of those languages. I was kind of annoyed by the fact that Crystal removed the for loop, which works perfectly fine in Ruby.
If you fight with the rust compiler the same amount as you do with a C compiler than you're making something wrong in both languages. Don't get me wrong but the rust and C compiler are on the direct opposite of the spectrum – for me. I love how the rust compiler is holding my hand to avoid all the nasty bugs i would introduce into my code.
The overall experience is similar. Rust definitely has better error messages. The difference is that in C once your program actually compliles it can also easily segfault at runtime. With D and Go you just compile the program and it works. It only complains when you're doing something awfully wrong instead of being a grammar nazi all the time. Additionaly, D allows you to enable or disable certain features if you wish to. Want safety? Use @safe and @trusted functions. Want to disable the GC? Use @nogc functions. And the list goes on. One can enable all of these on demand.
Don't get me wrong, I like a language with a Gc by default, my favourite language is OCaml, but I can't claim it to be a drop-in replacement for C projects. Go, Rust (or D) stand a better chance at that.
The richness and flexibility of compile-time features almost make it feel like a lisp, with the slight downgrade of sometimes working on strings of source code instead of sexps. Regardless, like the article says, D's metaprogramming alone is worth the entry price of reading a few books or tutorials about the language.
The way you characterize D's metaprogrammability is exactly how I would put it too. It's hard to describe but D just makes metaprogramming so fun and easy compared to anything besides Lisp and I find it sorely missing when working with most other languages.
I never understood the use of D, or any of the "C replacements" languages trends. C does work pretty well, but it's like a sharp tool. Use with a lot of care with your remaining fingers.
There are a LOT of things C is blamed for, and languages try to introduce complicated syntaxes and abstraction to prevent them from happening, however, most of these other languages are ALSO open to the same security issues (or sometime, more), just on a different level. Perhaps they look a lot safer because they haven't had the same level of scrutiny as good old C. In the meantime they introduce a 'duh/WTF' effect when you try to bring someone else in on the codebase.
C with good static analyser coverage (cppcheck, clang 'scan-build', smatch[0]) and reasonable discipline is pretty easy to maintain, VERY easy to debug, extraordinarily low footprint and very fast, so taking the time and having a BIT of discipline to harness that power is well worth it.
As the article mentions, I'm one of the guys who did 20 years of C++ before 'reverting back' to C, and I'd LOVE a few of the things I've had to give up -- however, a new language is not really something I need.
I do not think it is a significant problem in C. It might be more of a problem in C++.
> You can use D in -betterC mode, write code pretty much just like you would in C, and not have that problem.
D is an improved, more polished version of _C++_, not C; "-betterC" mode is misleading, it is more of a "betterC++" mode (features: classes, exceptions, RAII, templates).
"The simplicity of C is more useful than the additional features of C++." -- (Sam Watkins) s/C++/D/g
Don't get me wrong: D would be a great replacement for _C++_, but not C. For a better C I would look at a subset of Go (GC would have to go away).
>most of these other languages are ALSO open to the same security issues
How often do programs in non-C/C++ languages get remote code execution vulnerabilities? I'm sure the CVE numbers will overwhelmingly show that most RCE-capable vulnerabilities are in C/C++.
Sure, it occasionally happens in other languages, but practically only when someone does something like misuse one of a specific set of APIs that allow code loading. In C/C++, any code that slightly fucks up writing to an array or passes a pointer to previously-freed memory around can cause a RCE vulnerability that lets an attacker literally run code on your system and take it over. Inspecting a C/C++ codebase made by a junior person for vulnerabilities is a total nightmare. Inspecting a codebase in any other language for RCE vulnerabilities generally means you grep for a couple APIs and look at how they get used.
I would like to see those stats divided by total SLOC in all projects for each language. C might top the list simply because there's a lot of C projects in the dataset.
I don't believe that, you mention this because you put 100% trust on the runtime. When I see syntax like: var array=something[4...$] I don't see security there, all I see is someone pushing the problem further under the carpet, into the runtime.
So Sure, RIGHT NOW the runtime might be safe from scrutiny, but is it because its SAFE or just not as such a high profile, just yet?
On the other hand I DO agree that 'junior' programming will always be a problem, regardless of the matter, and pretty much, regardless of the language.
> When I see syntax like: var array=something[4...$] I don't see security there, all I see is someone pushing the problem further under the carpet, into the runtime.
The runtime (whichever one that is) is safe because its behavior is defined by design for all inputs. Yes there could be a bug, but the design is that all inputs should be defined, and doing something for any given input isn't rocket science. Out of bounds array indexes either panic/throw or return empty lists. That can lead to crashes and logic errors, sure, or maybe DOS attacks, but it cannot lead to undefined behavior or remote code execution. That's a world of difference.
C and C++ by design expose heaps of undefined behavior to the caller. The tools for detecting UB are imperfect and don't work if the relevant inputs never show up in test cases. Even when veteran C coders develop the techniques and habits that let them write correct C, mistakes tend to show up at the integration points between libraries written by different people with different techniques and habits (https://youtu.be/HgtRAbE1nBM?t=2344).
That's kinda like claiming "a single implementation that everyone uses is no more secure/stable than a million independent implementations", which is fairly ridiculous. It's not (just) "pushing the problem further under the carpet", it's centralizing risk and attention and fixes, which does work out quite well in practice.
Let's say there's a trillion lines of code written in C, and a million lines of code written in the runtime for a safe language where the trust is being pushed into. That means you can throw a million times the resources at finding and fixing the bugs in the runtime. While the payoff ratio is going to be far less than linear, you're looking at the C code being buggier and problematic by 100×.
Actually, the situation for C is even worse than that makes it appear. C, both the language itself and the infrastructure ecosystem around it, actively fight you when you try to build and use tools to proactively find problems. The only tooling that tends to be effective in practice is built on dynamic analysis, which means that you can only be in confident in the safety of your code as you can be in your testing regime--and there are very few, if any, codebases that have a strong enough testing regime.
We, as an industry, have almost 50 years of coding in C. And that experience has told us that the effort it takes to build safe C code is beyond the discipline of most, if not all, development teams.
>all I see is someone pushing the problem further under the carpet, into the runtime.
How often are there bugs in language runtimes that cause vulnerabilities in programs that are exploitable by users putting in different data into an otherwise secure program? (I'm not talking about the case where the runtime has a sandbox that breaks when the user loads bad code into it; this is a use-case C/C++ don't even try to address so that would be comparing different things. I'm talking about cases where someone is running a program that communicates over the network and the program's own code is written correctly, but a problem in the runtime makes it so the program is vulnerable to remote code execution.) The main example I can think of like that was Shellshock, which was horrible, but I would never have held up Bash as an example of a safe language. Bash has always been on my short list next to C and C++ of languages to avoid because of how easy and common it is to make mistakes that are practically unnoticeable in review.
>On the other hand I DO agree that 'junior' programming will always be a problem, regardless of the matter, and pretty much, regardless of the language.
Junior developers can write a buggy mess in any language. C/C++ are practically the only popular languages where a junior developer can accidentally write a backdoor in average-looking code that doesn't use any APIs.
I can tolerate some bugs in my software sometimes, but exploitable bugs are an entirely different thing to me.
The difference is that if the bug is in the runtime you have to fix one place and everyone downstream gets that. If you solve one such bug in curl, you solved it just in curl.
EDIT: I thought about it a bit more and realized it cuts both ways. If you have an RCE in a standard library a lot of programs get an RCE for free while it's not patched. So yeah, a bit of a mixed bag.
I posted a bit more beside this comment, but I think the difference isn't so much updates or code review but rather the design intention of the language. A language like Java or Python or Rust is designed for every operation to produce some defined behavior, and you can't generally trigger RCE unless you call some function like `unsafePerilousDangerZone(heinousRawPtr)`. Those design defaults bubbles up through libraries written in those languages, so that when you use other people's code and make a mistake, the result is either a crash or an unfortunate-but-at-least-well-defined logic bug. The opposite is true in C and C++, and that bubbles up through the libraries in those languages too.
There's also the difference that, in a GC'd language, you don't have both the language authors and the language users both competing to put these kinds of bugs in their software. :)
True, but when you want to interface between code written in two different GC'd languages then you can get similar problems if you're not careful. As the number and use of these various next generation languages proliferates, I suspect we might start to see more of that kind of thing appearing over time.
Hi Klez, I determined this to be the only way to get in touch regarding a previous discussion we had on Blendle [0] as I can no longer reply on that thread.
This was back in 2017 so they may have fixed their issues.
Alexander Klöpping <alexander.klopping@blendle.com> emailed me, I'll grant it was a generic welcome email, asking for feedback. I took the opportunity to email back informing him of the problems with using my email address [1]. I don't recall the problem now, but it seems they accepted my email address but then later rejected it during the sign-up process (as I clearly received an email correctly...).
I no longer have their response, but from memory, it was a different customer service person, and they rejected the idea that they had any problem AND/OR explicitly said they weren't going to make any changes. Oh, and they sent that response to the email that I explicitly said wouldn't work (it automatically gets sent to the trash to be deleted which is also why I don't have it any more).
Their rejection of fixing their service is what made me decide to put Blendle in the trash category.
When I first heard about Blendle I was excited at the concept of being able to read high-quality journalism from multiple different sources through one - ad-free - platform and one reasonably-priced payment system, and so I signed up for the beta. I successfully started the process of joining Blendle by unlocking and selecting some preferences. However, I have been unable to proceed past entering my email address, X+Y@gmail.com. I should make it clear that the email address X@gmail.com cannot be used.
Could you please assist me and help me get started with Blendle?
Even if you corrected for that, I would still expect this to be true. A potential RCE in C/C++ is literally as easy as writing past the end of an array or writing to a pointer to something that's already been freed. Few other languages give such huge consequences to minor mistakes in average code.
The arguments that C/C++ are just as safe as other languages and that's it's just a matter of opinion or experience are unfounded. It's like saying a minefield is just as safe as anywhere else to walk because you can randomly suddenly die anywhere else too because of stuff like strokes or heart attacks. You just gotta have good landmine senses and it's your own fault if you can't avoid them.
> Compared to Java, Javascript, Python, put together?
Yes still. Look to your laptop right now and count the software running in C/C++ vs anything else. They still dominate by far.
And the current fashion of writing everything in Javascript + Electron/NodeJS, just made the C/C++ backend more important than ever. NodeJS is in C/C++, V8 is in C++, Chromium is in C++, Firefox is in C++/Rust and every library behind are in C.
So lets us constrain to the Linux kernel, with its patch review process, ksan and static analysis tooling.
As per Google talk at Linux Kernel Summit 2018, 68% of Linux kernel exploits are caused by C's lack of safety against memory corruption, in spite of all the tools and processes they have in place.
Hence, the Kernel Self Protection Project sponsored by Google.
Amen, brother! But i would add C++ too (with reservations on the "easy" part); to me they go together. In one of my previous posts i had mentioned the advantages of C, two of which are worth listing here.
a) C allows you to program "everything"; from big honking servers (backend and frontend), networking protocols and systems etc. and all the way down to itty-bitty 8-bit MCUs. Basically from above assembly to any sort of application you might care about.
b) C is the de-facto "glue" language to everything i.e. the "assembly" of high-level languages. Almost all languages allow you to link to a C module.
I am often mystified when people say C is "hard" when they program in languages like Java/Python/Javascript etc. To me these languages are baroque and hard since they require more mental effort to memorize and use the bazillion frameworks, libraries, objects and methods. I find them all quite overwhelming.
>That this binary was originally built with C, C++ in extern "C", fortran, ADA, D -betterC, Pascal... does not matter at all.
Not quite; the point of using the phrase "link to a C module" was to point out that all of them conform to the applicable "C ABI" for a platform and therein lies C's strength as a "glue" language.
C does not have an ABI in itself, it just uses the conventions of its host system.
e.g. how function calls are done, how names are mangled (for instance on macOS all function names are prepended with an underscore and not on 64-bit windows nor linux...), etc... none of this is defined in the C standard, and it varies across platforms.
Was the phrase i used. It is well known that the C standard does not define an ABI but a processor arch+OS i.e. platform defines it. Over time that defined for C binaries on a platform have become the "de facto" standard and almost all other language run-times hew to it as needed.
Improvements in tooling like address sanitizer, valgrind, afl also help a lot in improving code quality, especially if you make them part of your CI.
However it should possible to have a language that gives you both the speed and safety guarantees.
There are many languages that contend for that category: Go (not on speed though), Rust, etc.
Might be worth looking at some of the Rust projects like ripgrep that are really focused on performance and faster than C implementations like grep.
Go would never replace C because of speed and lack of direct memory access. Rust syntax is very ugly, Rust is more complex than C and harder to read. is very simple.
Go can do the same direct memory access as C does via unsafe.Pointer.
If you mean real hardware access, not even C can do that unless it is running bare metal without an underlying OS or MMU controlling the access, both situation outside of ISO C specification.
I am admittedly a D novice. I would choose it over C in a heartbeat for most things. However, with how good Rust has become, it's hard for me consider D for anything. Rust is a fantastic C/C++ replacement IMO, and I'm hugely invested in C++.
Some people bristle at any restriction on their freedom of action (which psychologists sometimes call "trait reactivity") whereas some people are OK with a restriction if there are practical benefits.
There are other languages who achieve the same goal (C-like performance, controlled latency, memory safety) by less intrusive means. E.g. Nim, or D. So yes, I am perhaps one of those “some people”, if there are other ways to safety, I prefer my freedom of action unconstrained.
I would call this phenomenon "cowboy development". Why would you fight borrow checker when you can employ your users to flush out crashes and undefined behavior?
D is one of those language that while it's technically very good, it's small eco system makes me look over it. I'm quite conservative about what libraries I rely on, and with these smaller languages 90% of the libraries you want are someones hobby.
The betterC scenario has intrigued me before though, it seemed like a win all around since you'd be using C libraries anyway. I'd love to hear others experiences and anecdotes with this.
It looks like you still have to write interfaces for c libraries (https://dlang.org/spec/interfaceToC.html) and that it can't use header files directly like zig. This is fine for interop but it's not great as a drop in replacement, there is still some manual work and likely many edge cases around macros in the c libraries.
I'm yet to put zig through it's paces but you can include header files directly, theoretically that makes it a much better drop in replacement.
> You have relatively clean access to all C libraries.
I thought much of the point of D as a programming language would be its "relatively clean" access to external C++ libraries, which is not a feature many other languages share? (whereas it is of course quite common to have a foreign-function interface with C.)
> its "relatively clean" access to external C++ libraries
They're working on it, but there's a lot more work to do:
https://dlang.org/spec/cpp_interface.html
There's a reason other languages don't have easy C++ interoperability.
> it is of course quite common to have a foreign-function interface with C
Yes, but it's not trivial for the most part to interface with C code. D is designed to make it easy - to the point that you can #include a C header file and you're good to go.
I think the author blows his credibility very early with this comment:
> As I said, it’s a superficial example, but I think it shows a general difference in philosophy between C++ and D. (If I wanted to make the difference even clearer, I’d use an example that needed iomanip in C++.)
iostreams are not in C++ because C++'s philosophy is actually that iostreams are great. Everyone knows they suck. They are there because without variadics, there isn't a good way to do type safe text output in the style of printf. And I mean, not even runtime type safe. Chaining of some kind is the obvious way to simulate variadics when you don't have variadics. And at the time, they thought it was better to get something type safe into the standard library, then gate it behind variadics which could (did) take a long time. Voila, iostreams.
C++ is quite literally in the process of standardizing a library that will bring type safe printf (a la D) to C++. The same way that 8 years ago, C++ finally managed to standardize variadics after quite a lot of effort.
The disadvantage of being an old language is that it can be hard to stay caught up with features. The advantage is that you get a huge base of existing developers, knowledge, libraries, etc.
Bloggers love to make things about big picture philosophy because it makes for better blurbs but many things in reality are just engineering decisions. D had the luxury of creating metaprogramming syntax from scratch after one if its creators was one of the main people to discover the power of "accidental" TMP in C++. C++ is still trying to bend accidental TMP into something more bearable to use without breaking everything. The differences here are more practical than philosophical.
> They are there because without variadics, there isn't a good way to do type safe text output in the style of printf.
I think an equally important point is that IO streams in C++ are extensible. You can make your own user defined types work with them. There's no way to add "printf() support" to your own type in C.
It's part of libc, so not GCC specific, it's basically native on linux and usable just about anywhere you'd want printf functionality. If the bloat (like this feature) of gnu libc are too much then I doubt the c++/d/rust equivalents will be an acceptable option. Likewise if gcc and clang (which supports printf) aren't possible then I very much doubt the platform is a compiler target for modern c++/d/rust.
In my opinion the most interesting aspect of iostream is the ability to inject a state to the stream: xalloc, iword and pword; they are required to make your own manipulators like `hex` or `setw`. Of course, this is to support a (questionable, again in my opinion) design of manipulators conceptually affecting the state of stream, and there are many saner alternative designs not requiring this kind of "extensibility".
Scope guards seem like a really good idea. Has there been any effort to write a C to -betterC transpiler just like there has for Rust (e.g. C2Rust [0], Corrode [1])?
Yes. D's design is based around the idea that semantically valid C will compile in D (apart from crud like function pointer syntax etc.) but there are tools to convert C to D (As used in the dmd compiler backend (which dates back to the 80s I believe, but is now in a tasty form of spaghetti D)
> D's design is based around the idea that semantically valid C will compile in D
AFAIK it's that code which compiles in both c and d should have the same behaviour; not that c code should necessarily compile in d. Examples of c that don't work in d are trivial.
> there are tools to convert C to D (As used in the dmd compiler backend (which dates back to the 80s I believe, but is now in a tasty form of spaghetti D)
Dmd's backend was ported by hand. What tools are those? I know there are tools to convert header files, but I don't know of any that actually convert code.
Not quite, the c++ source was massaged to be free of weird corner cases of the grammar and preprocessor (e.g. enforcing no spaces between '#' and the preprocessor keyword) and then a tool called magicport was used to convert the frontend in one shot. The backend was mostly converted by "hand" (i.e. grep after some more massaging).
There are several tools to translate headers though dstep[1] and dpp[2] come to mind
Could you write(apologies if I'm missing something) some documentation on how the backend is organised and the overall motion through it? I've often looked at it but been almost totally unable to grok how any thing actually works beyond the abstract of what the optimisations say on tin.
Could a step forward be to start afresh and then call into the old backend stages but ultimately rewrite them in more bearable code where possible? Might be a compile time trade-off there, to an extent.
The linked article on C++ method pointers loses some relevance now that C++11 and newer have lambdas and std::function. Still an interesting rabbit hole.
I agree that the Digital Mars implementation looks like the sanest.
Cool! -betterC looks well worth exploring. Also, pure and safe are fantastic features, I just wish the default was the other way around, so you need to specify impure and unsafe.
If the language were being done today I suspect that could've ended up being the case, but keep in mind that (given that idiomatic D encourages use of templates) these will be inferred automatically upon instantiation of a given template
D does technically run on all three of those platforms (ios, android, wasm). Of the three, android is probably the most mature. Wasm is not very usable, because wasm itself doesn't yet have a garbage collector, so the d runtime is not ported. For iOS, there is a fork of GDC (GCC-based compiler) which is supposed to run, but there isn't very much interest in it. If you put in the work, you could likely get all three to work, but there's nothing OOTB yet.
D uses the same backends, so in principle it's similar coverage to C++ (gdc targets loads and loads of architectures, for example) however druntime hasn't been ported to many.
WASMV is completely doable but there's no C environment( at all) so you have to do it all yourself
D is not a C replacement. It's more targeted at C++. Honestly, I prefer D to C++, but still prefer C to D. I would say the biggest turn off to C is generally it's very small STD library. While there are a lot C libraries that exist to achieve certain things it's not part of STD C. It's not part of the "out of box" experience.
It also depends on what your programing if you looking at system level stuff currently the two languages I would look at are C and Rust. Heck even for some system stuff your still required to use assembly.
Anything else choices are more flexible and productivity might matter more.
Honestly, despite C's flaws very few languages occupy it's niche and flexibility. Like you have to be doing something right to some degree to last almost 50 years. I would say a lot this comes down to C's simplicity yet still having a lot of power as a language.
in particular, and i've read that writing kernels isn't feasible in Go. it appears not to be an issue of performance. something about the runtime prevents writing kernels altogether.
rust has a lot of features (generics, etc.) aside and apart from the memory management strategy.
i'm also unaware of a procedural language that has the feature stability of C or Go as well as the ability to write kernels or embedded code with the memory safety of rust.
currently wondering how easy it'd be to tack rust's memory model onto another language, alef in particular.
I like D a lot but find it's not a good fit for many things. C++, Java and Python have great tools and libraries to rely on. Not so much with D's small community. Writing wrappers and interop always feels like a hassle, so it's easier to stick with just one language.
First time I used D was before the version 2, back when there was the Tango library. D seems to have improved quite a bit since then, but it's still in the same position as before.
I disagree about being better then C. Compile time asserts can be created using #error in C, and having something happen out of order at the end of a scope is a terrible idea. One of the main strengths of C is that everything is explicit and nothing happens without you saying so.
> Compile time asserts can be created using #error
Really not the same thing. Compile time asserts allow you to check any compile time value. For instance say you were writing OpenGL code and your struct needs to have a size that is a multiple of 16 bytes. In D that's easy:
build time asserts can be defined in C macros with some tricks. The error messages are slightly misleading, but when people look at the source code it is usually clear what is happening.
That's a great trick and all, but it falls disappointingly short of D's `static assert`: There's no guarantee it'll ever run. The trick assumes that the C compiler will evaluate the expression at compile time, but there's every chance that it just won't and will silently fail instead.
char * i = malloc(1);
ASSERT_BUILD(i);
That just silently compiles and runs without triggering any failure. Whats worse is you can never be sure whether your assert is passing or the compiler decided not to run it.
That compiles because with gcc extensions or sufficiently recent standards, variable size arrays are now a thing. The compiler doesn't insist on a static array size. What about something like this:
#define ASSERT_BUILD(x) extern int STATIC_ASSERT_FAILED[(x)?1:-1]
Then don't use them? They are completely explicit, you have to write scope(exit) to make anything happen.
The use of scope(exit) is actually very useful because, for example( short of using RAII) how many times do we all forget to free or close a file. Programmers make mistakes, but if you give them the tools to not fuck up as idiomatic parts of the language: They will use them.
The post this is referring to claimed as its major complaints about rust its 1) lack of a spec and 2) its increasing number of features, neither of which are addressed by the author.
For things that don't need classes (and there is a lot of code) I stick to C. But if I need the OOP benifits then I go with D. As a bonus, the C programmers and JavaScript programmers that I work with can read the D code and get most of it on the first try.
If you haven't used D, spend 30 mins and write a four function calculator. Then take the remaining time adding some kind of internal function line sin, cos, etc. You'll be suprised at how fast you can pick it up.
Granted, the above is a toy exercise, it's not writing an enterprise solution. But it's a little more than the standard "Hello Sailor, new in town" program that people start off with.