I do not agree that the integer promotion or casting (?) rules are broken in C. That some people make mistakes because they do not know them is a different problem.
The reason you should make length signed is that you can use the sanitizer to find or mitigate overflow as you correctly observe, while unsigned wraparound leads to bugs which are basically impossible to find. But this has nothing to do with integer promotion and wraparound bugs can also create bugs in - say - Rust.
I meant implicit casting, but I guess that really falls under promotion in most cases where it's relevant here (I'm on a train from Aarhus to Copenhagen right now to catch a flight, and I've slept considerably less than usual, so apologies if I'm making some slight mistakes).
The issues really arise when you mix signed/unsigned arithmetic and end up promoting everything to signed unexpectedly. That's usually "okay", as long as you're not doing arithmetic on anything smaller than an int.
As an aside, if you like C enough to have opinions on promotion rules then you might enjoy the programming language Zig. It's around the same level as C, but with much nicer ergonomics, and overflow traps by default in Debug/ReleaseSafe optimization modes. If you want explicit two's complement overflow it has +%, *% and -% variants of the usual arithmetic operations, as well as saturating +|, *|, -| variants that clamp to [minInt(T), maxInt(T)].
EDIT to the aside: it's also true if you hate C enough to have opinions on promotion rules.
I prefer C to Zig. IMHO all the successor languages throw out the baby with the bathwater and add unnecessary complexity. But Zig is much better than Rust, but, still, I would never use it for a serious project.
The "promoting unexpectedly" is something I do not think happens if you know C well. At least, I can't remember ever having a bug because of this. In most cases the promotion prevents you from having a bug, because you do not get unexpected overflow or wraparound because your type is too small.
Mixing signed and unsigned is problematic, but I see issues mostly in code from people who think they need to use unsigned when they shouldn't because they heard signed integers are dangerous. Recently I saw somebody "upgrading" a C code basis to C++ and also changing all loop variables to size_t. This caused a bug which he blamed on working on the "legacy C code" he is working on, although the original code was just fine. In general, there are compiler warnings that should catch issues with sign for conversions.
> Recently I saw somebody "upgrading" a C code basis to C++ and also changing all loop variables to size_t. This caused a bug which he blamed on working on the "legacy C code" he is working on, although the original code was just fine.
I had the same experience about 10 years back when a colleague "upgrade" code from using size_t to `int`; on that platform (ATMEGA or XMEGA, not too sure now) `int` was too small, overflowed and bad stuff happened in the field.
The only takeaway is "don't needlessly change the size and sign of existing integer variables".
I don't think this is the only takeway. My point is that you can reliably identify signed integer overflow using sanitizers and you can also reliably mitigate related attacks by trapping for signed integer overflow (it still may be a DoS, but you can stop more serious harm). Both does not work with unsigned types except in a tightly controlled project where you treat unsigned wraparound as a bug, but this fails the moment you introduce other idiomatic C code that does not follow this.
Yes, this is one of the more subtle pitfalls of C. What helps is that in most contexts the value of 2 billion is large enough that a wraparound would be noticed almost immediately. But when it isn't then it can lead to very subtle errors that can propagate for a long time before anything goes off the rails that is noticed.
It's interesting to hear these takes. I've never had problems catching unsigned wrap bugs with plain old memory sanitizers, though I must admit to not having a lot of experience with ubsan in particular. Maybe I should use it more.
GCC's sanitizer does not catch unsigned wraparound. But the bigger problem is that a lot of code is written where it assumes that unsigned wraps around and this is ok. So you you would use a sanitizer you get a lot of false positives. For signed overflow, one can always consider this a bug in portable C.
Of course, if you consistently treat unsigned wraparound as a bug in your code, you can also use a sanitizer to screen for it. But in general I find it more practical to use signed integers for everything except for modular arithmetic where I use unsigned (and where wraparound is then expected and not a bug)
I've had some fun reviewing some very old code I wrote (1980's) to see what it looked like to me after such a long time of gaining experience. It's not unlike what the OP did here, it reads cleanly but I can see many issues that escaped my attention at the time. I always compared C with a very fast car: you can take some corners on two wheels but if you make a habit of that you're going to end up in a wall somewhere. That opinion has not changed.
I think the correct comparison is a sharp knife. It is extremely useful and while there is a risk it is fully acceptable. The idea that we should all use plastic knifes because there are often accidents with knifes is wrong and so is the idea that we use should abandon C because of memory safety. I follow computer security issues for several decades, and while I think we should have memory safety IMHO the push and arguments are completely overblown - and they are especially not worth the complexity and issues of Rust. I never was personally impacted by a security exploit caused by memory safety or know anybody in my personal vicinity who was. I know many cases where people where affected by other kinds of security issues. So I think those are what we should focus on first. And having timely security updates is a hell lot more important than memory safety, so I am not happy that Rust now makes this harder.
That's an interesting point you are making there. The most common exploits are of the human variety. Even so it is probably a good idea to minimize the chances of all kinds of exploits. One other problem - pet peeve of mine - is that instead of giving people just security updates manufacturers will happily include a whole bunch of new and 'exciting' stuff in their updates that in turn will (1) introduce new security issues and (2) will inevitably try to extract more money from the updaters. This is extremely counterproductive.
The real problem with C is that it's not just a sharp knife, it's a knife with poor ergonomics that makes it more prone to cutting yourself.
The answer to that though is probably more something like Zig than something like Rust.
Hi, are you interested in Zig? Then please check out my port of jsmn to Zig. I wanted to know if people will like it and if there are any downsides others might not. https://github.com/Ferki-git-creator/jsmn_zig
Except any good chef or butcher knows that they should be wearing protective gloves when using sharp knifes.
> Cut-resistant gloves are an essential piece of safety equipment in any kitchen.
https://www.restaurantware.com/blogs/smallwares/how-to-choos...
Where are C's gloves?
I’m sorry, but there is an incredible amount of hard data on this, including the number of CVEs directly attributable to memory safety bugs. This is publicly available information, and we as an industry should take it seriously.
I don’t mean to be disrespectful, but this cavalier attitude towards it reads like vaccine skepticism to me. It is not serious.
Programming can be inconsequential, but it can also be national security. I know which engineers I would trust with the latter, and they aren’t the kind who believe that discipline is “enough”.
CVE are important but there’s also a lot of theatre there. How many are known exploitable? Most aren’t if you follow threat intel. Most of the Internet infrastructure is running c/c++ and is very safe.
It's fine to have a sober view of the severity, but we can hopefully agree in general that writing any program in C or C++ that faces the internet requires extreme caution.
I think anything that faces the internet needs extreme caution. I've done enough pentesting myself to see that mistakes are abound and most of them are logic problems.
Number of CVEs is completely irrelevant. Also Google's or Microsoft's priorities are completely irrelevant. If you have reliable data from the real world, please show it to me.
So what do you propose to do?
I propose that we start taking the appropriate amount of professional responsibility.
That includes being honest about the actual costs of software when you don’t YOLO the details. Zero UB is table stakes now - it didn’t use to be, but we don’t live in that world anymore.
It’s totally fine to use C or whatever language for it, but you are absolutely kidding yourself if you think the cost is less than at least an order of magnitude higher than the equivalent code written in Rust, C#, or any other language that helps you avoid these bugs. Rust even lets you get there at zero performance cost, so we’re down to petty squabbles about syntax or culture - not serious.
Thankfully the new cybersecurity laws will help here, when companies map production costs to languages, the needle will keep moving away from those that tank security budgets.
I was actually hoping for far more strict enforcement but so far they're taking it relatively easy.
Indeed, however better slowly than nothing at all.
> I propose that we start taking the appropriate amount of professional responsibility.
I agree. For me that means: software engineering should start taking the same attitude to writing software that structural engineers bring to the table when they talk about bridges, buildings and other structures that will have people's lives depending on them. I'm not sure how we're going to make rings out of bits but we need to realize - continuously - that the price of failure is often paid in blood, or in the best case with financial loss and usually not by us. And in turn we should be enabled to impose that same ethic on management, because more often than not that's the root cause of the problem.
> That includes being honest about the actual costs of software when you don’t YOLO the details.
Does that include development cost?
Maintenance costs?
Or just secondary costs?
Why the focus on costs?
> Zero UB is table stakes now - it didn’t use to be, but we don’t live in that world anymore.
This is because 'Rust and C# exist'? Or is it because Java, Erlang, Visual Basic, Lisp etc exist?
> It’s totally fine to use C or whatever language for it, but you are absolutely kidding yourself if you think the cost is less than at least an order of magnitude higher than the equivalent code written in Rust, C#, or any other language that helps you avoid these bugs.
We were talking about responsibility first, and that goes well beyond just measuring 'cost'. The mistake in bringing cost into it is that cost is a business concept that is used to justify picking a particular technology over another. And just like security is an expense that doesn't show anything on the balance sheet if it works besides that it cost money the same goes for picking a programming language eco-system.
So I think focusing on cost is a mistake. That just allows the bookkeeper to make the call and that call will often be the wrong one.
> Rust even lets you get there at zero performance cost, so we’re down to petty squabbles about syntax or culture - not serious.
The debate goes a lot further than that. You have millions of people that are writing software every day that are not familiar with Rust. To get them to pick a managed language over what they are used to is going to take a lot of convincing.
It starts of with ethics, and I don't think it should start off with picking a favorite language. You educate, show by example and you deliver at or below the same cost that those other eco-systems do and then you slowly eat the world because your projects are delivered on-time, with provably lower real world defects and hopefully at a lower cost.
And then I really couldn't care what language was picked, in the rust world that translates into 'anything but C' because that is perceived to be the enemy somehow, which is strange because there are many alternatives to rust that are perfectly suitable, have much higher mind share already.
C is - even today - at 10x the popularity that rust is, it will take a massive amount of resources to switch those people over, and likely it will take more than one generation. In the meantime all of the C code in the world will have to be maintained, which means there is massive job security for people learning C. For people learning rust to the exclusion of learning C that situation is far worse. This needs to be solved.
These are not 'petty squabbles' about syntax or culture. They are the harsh reality of the software development world at large, which has seen massive projects deployed at scale developed with those really bad languages full of undefined behavior (well, that's at least one thing that Assembly Language has going for it, as long as the CPU does what it says in the book undefined behavior doesn't exist). People are going to point at that and say 'good enough'. And they see all those memory overflows, CVEs etc as a given, and they realize that in spite of all of those the main vector for security issues is people, and configuration mistakes not so much the software itself.
This is not ideal, obviously, but C, like any bad habit, is very hard to dislodge if your main argument is 'you should drop this tool because mine is better'. Then you need to show that your tool is better, so much better that it negates the cost to switch. And that's a very tall order, for any programming language, much more so for one that is struggling for adoption in the first place.
Cost is a useful metric because it reflects a number of relevant things: Time to develop, effort to maintain - yes, but also people turnover, required expertise levels, satisfaction, and so on. Whether or not you like it, you have to care about cost if you want to make rational decisions. I'm not talking about assigning a Euro/Dollar/Yuan value to each hour spent on a project, but you need a rough idea about the size of the time and energy investment you are making when starting a project.
> This is because 'Rust and C# exist'? Or is it because Java, Erlang, Visual Basic, Lisp etc exist?
Things have changed for three important reasons: (1) C/C++ compilers have evolved, and UB is significantly more catastrophic than it was in the 90s and early 00s. (2) As societies digitize, the stakes are higher than even - leaking personal data has huge legal and moral consequences, and system outages can have business-killing financial consequences. (3) There are actual, viable alternatives - GC is no longer a requirement for memory safety.
> To get them to pick a managed language over what they are used to is going to take a lot of convincing.
Perhaps you didn't mean to say so, but Rust is not a managed language (that's a .NET term referring to C#, F#, etc.).
Me and other Rust users are obviously trying to convince even more people to use the language, and that's because we are having a great time over here. It's a very pleasant language with a pleasant community and a high level of technical expertise, and it allows me to get significantly closer to living up to my own ideals. I'm not making a moral argument here, trying to say that you or anyone is a bad person for not using Rust, but I am making a moral argument saying that denying the huge cost and risk associated with developing software in C and C++ is bullshit.
> And then I really couldn't care what language was picked, in the rust world that translates into 'anything but C' because that is perceived to be the enemy somehow, which is strange because there are many alternatives to rust that are perfectly suitable, have much higher mind share already.
The point here is that, until Rust came along, you had the choice between wildly risky (but fast) C and C++ code, or completely safe (but slow) garbage collected languages with heavy runtimes and significant deployment challenges.
C is certainly not "the enemy" - I never said that, and I wouldn't. But that old world is gone. The excuse of picking risky, problem-riddled languages that we know are associated with extreme costs for reasons of performance no longer has any technical merit. There can be other reasons, but this isn't it.
> C is - even today - at 10x the popularity that rust is, it will take a massive amount of resources to switch those people over [...]
It's insane to me that anyone would limit themselves to a single language. Every competent programmer I know knows at least a handful. Why are we worried about this? I'm a decent C programmer, and a very good C++ programmer - better at both because I'm also fairly good at Rust.
> And they see all those memory overflows, CVEs etc as a given, and they realize that in spite of all of those the main vector for security issues is people, and configuration mistakes not so much the software itself.
"Pobody's nerfect." I'm sorry, I really dislike this attitude. We can't let the fact that security is hard, or that perfection is unattainable, be an excuse to deliver more crap.
> This is not ideal, obviously, but C, like any bad habit, is very hard to dislodge if your main argument is 'you should drop this tool because mine is better'
Again, that's not my argument. My argument is that you should be honest about what the actual costs, or alternatively the actual quality.
> The point here is that, until Rust came along, you had the choice between wildly risky (but fast) C and C++ code, or completely safe (but slow) garbage collected languages with heavy runtimes and significant deployment challenges.
Not really, I have been mostly coding in managed languages for the last couple of decades, and this has been not really true for quite some time.
Yes if we go down language benchmark games, they won't win every little micro benchmark, however for like 99% of commercial use cases, what they deliver is fast enough for project requirements in execution time, and hardware resources.
Now where they fail is in human perception and urban myths, of where they are suitable to be adopted.
Languages like Rust overcome this, with their type system approach to resource management, the naysayers have run out of excuses.
I think you are pointing out that garbage collected languages can be very fast, right? I agree about that, but it does fundamentally comes with some very big caveats.
There's a huge number of use cases that are perfectly served by GC languages, even where performance matters, but there's also a huge number that benefit from the extra boost and significantly lower memory usage of a compiled language.
There are plenty of compiled languages with GC, value types and low level programming capabilities, including playing with pointers C style.
D, C#, Nim, Swift, Go for mainstream examples.
If we dive into less successful attempts from the past,
Cedar, Modula-2+, Modula-3, Oberon, Oberon-2, Active Oberon, Component Pascal, Oberon-07, Spec#, System C# among plenty others that are probably listed on ACM SIGPLAN list of papers.
As for some commercial examples,
https://www.withsecure.com/en/solutions/innovative-security-...
https://dlang.org/blog/2018/12/04/interview-liran-zvibel-of-...
https://www.wildernesslabs.co/
https://www.astrobe.com/boards.htm
> It's a very pleasant language with a pleasant community
I'm sure.
> bullshit.
> But that old world is gone.
> Every competent programmer
> an excuse to deliver more crap.
Yeah. Very pleasant indeed.
Some serious cognitive dissonance is in your post. You claim how you're part of a community that so damn pleasant, but you're out throwing shade...
> Cost is a useful metric because it reflects a number of relevant things: Time to develop, effort to maintain - yes, but also people turnover, required expertise levels, satisfaction, and so on. Whether or not you like it, you have to care about cost if you want to make rational decisions. I'm not talking about assigning a Euro/Dollar/Yuan value to each hour spent on a project, but you need a rough idea about the size of the time and energy investment you are making when starting a project.
You are missing the cost to switch and that's a massive one and the one that I think most parties are using to decide whether or not to stick with what they know or to try something that is new to them. If you have a team of 50 embedded C++ developers and a deadline 'let's use rust' is a gamble very few managers will make.
> Things have changed for three important reasons: (1) C/C++ compilers have evolved, and UB is significantly more catastrophic than it was in the 90s and early 00s.
That depends on what industry you are looking at. For instance, in aviation the cost of undefined behavior, crashing software or wrong calculations was always that high. The difference is that in that industry (and a handful of others) there is enough budget to do it right resulting in far fewer in production issues than what we have come to accept in the 'always online, auto-update' world. That whole attitude is as much or more to blame for this than any particular language.
> (2) As societies digitize, the stakes are higher than even - leaking personal data has huge legal and moral consequences, and system outages can have business-killing financial consequences.
Show me the names of the businesses that have died because of data leaks or UB. See, the problem is that for those businesses it usually is just a speedbump. They don't care and no matter what the size of the breach the consequences are usually minor.
The employee sticking a USB drive found on the street into their laptop causing a cryptolocker incident is a much more concrete problem.
> (3) There are actual, viable alternatives - GC is no longer a requirement for memory safety.
GC is a convenience, and if you're going to switch languages you might as well pick one that is is more convenient. Java for instance is suitable now for 90% or so of the use cases where C or C++ would be your only option 15 years ago.
> Perhaps you didn't mean to say so, but Rust is not a managed language (that's a .NET term referring to C#, F#, etc.).
I know, but Java, Lisp and so on are managed languages, and they offer both safety and convenience. Rust only offers safety, other than that it is only marginally more convenient than C and some would argue less so.
> Me and other Rust users are obviously trying to convince even more people to use the language, and that's because we are having a great time over here.
Show, don't tell.
> It's a very pleasant language with a pleasant community and a high level of technical expertise, and it allows me to get significantly closer to living up to my own ideals.
Yes, but those are your ideals, which don't necessarily overlap with mine. I don't particularly care about one programming language or another, I've learned enough of them by now to know that all of them have their limitations, their warts, their good bits and their bad bits. I also know that the size of the eco-system is a large function in whether or not I'll be able to get through the day in a productive way.
> I'm not making a moral argument here, trying to say that you or anyone is a bad person for not using Rust, but I am making a moral argument saying that denying the huge cost and risk associated with developing software in C and C++ is bullshit.
See, your use of the word 'bullshit' triggers me in a way that you probably do not intend, but it is exactly that attitude that turns me off the language that you would like me to switch to. I don't particularly see that huge cost and risk as applied to myself because I'm not currently writing code that is going to be part of some network service. If I see an embedded shop doing their work in Rust then I'm happy because I can ignore at least one small aspect of the source of bugs in such software. But there are plenty remaining and Rust - no matter what you think - is not a silver bullet for all of the things that can go wrong with low level software. There are other, better alternatives for most of those applications, I'd be more inclined to use Java or Erlang if those are available, and Go if they are not. The speed at which I can develop software is a massive factor in that whole 'cost' evaluation for me.
> The point here is that, until Rust came along, you had the choice between wildly risky (but fast) C and C++ code, or completely safe (but slow) garbage collected languages with heavy runtimes and significant deployment challenges.
That just isn't true. There are more languages besides Rust that allow for low level and fast work. Go for instance is an excellent contender. And for long running processes Java is excellent, it is approaching C levels of throughput and excels at networked services.
> C is certainly not "the enemy" - I never said that, and I wouldn't. But that old world is gone.
Sorry, but this is not a realistic stance. That old world is not gone, and it is likely here to stay for many more decades. There is so much inertia here in terms of invested capital that you can't just make declarations like these and expect to be taken serious.
> The excuse of picking risky, problem-riddled languages that we know are associated with extreme costs for reasons of performance no longer has any technical merit. There can be other reasons, but this isn't it.
Do you realize that this is just your opinion and not a statement of fact?
> It's insane to me that anyone would limit themselves to a single language.
'Insane' is another very loaded word. Is this really the kind of language you want to be using while advocating for Rust? There are many programmers that learn one eco system well enough to carve out a career for themselves, and I'm not going to be the one to judge them for that. I'm not one of them, but I can see how it happens and I would definitely not label everybody that's not a polyglot as not entirely right in the head.
> Every competent programmer I know knows at least a handful.
I know some very competent programmers that only know one. But they know that one better than I know any of the ones that I'm familiar with. For instance, I know a guy that decided early on that if nobody wants to work on COBOL projects that that is exactly what he's going to do: become a world class expert in COBOL to help maintain all that old stuff. At a price. He's making very good money with that, far more than he'd have ever made by going with something more popular. I know plenty of Java only programmers and a couple that have decided that python is all they need. That's their right and it isn't up to me to look down on them or call them incompetent because they can do something that I apparently can't: focus, and get really good at one thing.
> Why are we worried about this? I'm a decent C programmer, and a very good C++ programmer - better at both because I'm also fairly good at Rust.
I would not label myself as 'very good' in any language, I always hope to get better and in spite of doing this for 4+ decades I have never felt that I was 'good enough'.
> "P[sic]obody's nerfect." I'm sorry, I really dislike this attitude.
Again, why the antagonism. We have many different classes of issues, and depending on the context some of them may not be a problem at all. I've built stuff in JavaScript because it was the most suitable for the job. But I stay the hell away from node and anything associated with it because I don't consider myself qualified to audit all of the code that could be pulled in through a dependency. And that's a good chunk of this: just know your limitations, and realize that not just 'nobody's perfect' but also that you yourself are not perfect and more than likely to mess up when you go into territory that is unfamiliar to you.
> We can't let the fact that security is hard, or that perfection is unattainable, be an excuse to deliver more crap.
Ok. So now you are labeling what other people produce as 'crap'. This isn't helping.
> Again, that's not my argument. My argument is that you should be honest about what the actual costs, or alternatively the actual quality.
So I'm not honest. If you are wondering what I meant when I wrote earlier that it is the attitude of some of the Rust advocates that turns me off then here in this thread you have a very nice example of that. All of this pontification and emotionally laden language serves nobody, least of all Rust.
If you want to win people over try the following:
- refrain from insulting your target audience
- respect the fact that your opinions are just that
- understand that there may be factors outside of your view that are part of the decision making process
- understand that you may not have a complete understanding of the problem domain or the restrictions involved (is a variation on the previous one)
- try to not use emotional language to make your point
- showing beats telling any day of the week
I don't have time to respond to all of this, but let me just say that you seem to be under the impression that it is somehow my responsibility to "win you over" and convince you to use Rust. I have stated very clearly that that's not my point. My point is that we should all stop lying about the actual cost of delivering reliable software written in C or C++, and in particular that we as an industry need to stop downplaying the consequences of things like UB.
Are you personally doing any of those things? I don't know, and I don't think I have accused you of that.
I'm not here to swoon you by sweet-talking you into using a different programming language. All this "show don't tell" - what are you talking about? Do you need real-world examples of successful Rust projects? There's a myriad of impressive ones, but you are fully capable of googling that.
I'm not a representative of Rust the language (how could I be), and I reserve the right to call out moral corruption as I see it. I frankly do not need any "well-meaning" advice about how best to advocate for Rust - that's not my job.
> I'm not a representative of Rust the language (how could I be), and I reserve the right to call out moral corruption as I see it. I frankly do not need any "well-meaning" advice about how best to advocate for Rust - that's not my job.
Whether you realize it or not, you are an advocate and you are doing a very, very poor job of it.
If you are making technical decisions based on how strangers on the internet make you feel, I fear there's not much I could have done anyway.
> That some people make mistakes because they do not know them is a different problem.
We can argue til we're blue in the face that people should just not make any mistakes, but history is against us - People will always make mistakes.
That's why surgeons are supposed to follow checklists and count their sponges in and out
Could you expand on how these wraparound bugs happen in Rust? As far as I know, integer overflow panics (i.e. aborts) your code when compiled in debug mode, which I think is often used for testing.
>while unsigned wraparound leads to bugs which are basically impossible to find.
What?
unsigned sizes are way easier to check, you just need one invariant:
if(x < capacity) // good to go
Always works, regardless how x is calculated and you never have to worry about undefined behavior when computing x. And the same invariant is used for forward and backward loops - some people bring up i >= 0 as a problem with unsigned, but that's because you should use i < n for backward loops as well, The One True Invariant.