Hacker News new | past | comments | ask | show | jobs | submit login

The headline is misleadingly focusing on a soundbite out of the full quote.

"It's a shame, but what can we do? There really isn't anything we can do to prevent memory safety vulnerabilities from happening if the programmer doesn't want to write their code in a robust manner." -- Some (uncredited?) C programmer.

Does C have more footguns as a low level language? Of course. That's part of the freedom of bringing only the baggage a project needs. Sadly, like many dangerous or sharp tools, incorrect use will lead to harms.

If someone has a choice, a safer more modern language can accommodate less skilled practitioners.




It's a deliberate echo of the famous Onion headline about America's absolutely disgraceful pretence that it couldn't do anything about all the shootings.

https://en.wikipedia.org/wiki/%27No_Way_to_Prevent_This,%27_...

> If someone has a choice, a safer more modern language can accommodate less skilled practitioners.

This is the usual mistake. It's not a "skill issue". Footguns are a design mistake, they're much more dangerous than necessary hence the name. As a result the practitioners accomodated by a better language are all those capable of mistake ie all humans.


Rather like the school shooting issue, the C issue is bound up with people at the identity level; they insist on the danger because they cannot stand the possibility of the danger being taken away from them. Their ability to use guns or C safely must not be questioned. They percieve it as an insult to take that choice away from them. They are the safe C programmer that never ships a CVE. They know that they themselves would never shoot up a school, so what the problem? Oh it's bad people. Well, that's happening somewhere else to other people, so it's fine and they can carry on.

(edit: someone was helpful to provide an example in this thread; https://news.ycombinator.com/item?id=40438716 )


Here's an attempt to explaining the irrational part of programmers deciding which programming language to use. Part of it goes into more detail on ideas like C programmers insisting 'on the danger because they cannot stand the possibility of the danger being taken away from them'.

"The Pervert's Guide to Computer Programming Languages" https://www.youtube.com/watch?v=mZyvIHYn2zk

It's pretty out there, but I thought also interesting.


Congratulations, you just landed in my very small number of favorite comments.


One can in fact not do anything about something for which one refuse to even consider the root causes of.

In the case of shootings, one must talk about what may have caused shootings to start noticeably increasing in around the 1970s, to the point where a single year started having as many shootings as all the shootings prior to the 20th century combined.

Fun fact; the first documented school shooting was perpetrated by some Lenape indigenous people, in Pennsylvania in 1764, during which attack they also beat to death all of the students and scalped everyone.


> Fun fact; the first documented school shooting was perpetrated by some Lenape indigenous people, in Pennsylvania in 1764, during which attack they also beat to death all of the students and scalped everyone

That would probably count under modern definitions of "terrorism". I would also say that most school shootings should also be counted as "terrorism", except there's a very strong ideological push to not look at them this way. Don't look at the radicalisation. Don't read the (suspiciously similar) manifestoes. Don't look at who they cite for inspiration.


If you're going to go back there far, before it was independent, I bet there was something documented in medieval Oxford or Cambridge involving a longbow and/or an anelas.


[Also writing "skill issue" caused me to immediately think of Gill Issue, the Grand Poo World 3 course with a name that's a pun on skill issue, so for anybody else whose brain works the same way, here's somebody competent beating Gill Issue: https://www.youtube.com/shorts/I6PdLgUGHaw ]


>If someone has a choice, a safer more modern language can accommodate less skilled practitioners

The implication here is thoroughly debunked. We’ve seen over and over again that memory safety bugs will happen in every C codebase(ignoring toy or small codebases). Even for the mythical and infallible “skilled practitioner” spending time re-solving the solved issues inherent to C just isn’t a good use of developer time.



> a safer more modern language can accommodate less skilled practitioners.

That’s really what it’s all about. SV is absolutely obsessed with hiring bad programmers, treating them like crap, so they don’t stick around, and somehow, magically, forcing them to write good code.

We have this belief that if we just use the “right” tool (in this case, a particular programming language), all of our products will be good. Couple that, with the belief that we should be able to build our stuff on someone else’s code, for free, and you have a recipe for disaster.

People like Linus Torvalds are living proof that it is quite possible to write amazing stuff, in old languages (he is a C guy), but people like that, are rare as hen’s teeth, and may be difficult for today’s tech managers to handle.

There really is no substitute for running a good shop, hiring good people, training them well, treating them well, paying them well, and keeping them around for significant lengths of time.

Also, we need to be able to hold ourselves accountable for the Quality of our own work -regardless of the tools we use. Torvalds is notorious for being a tough taskmaster, because he’s serious about the Quality of his work, and casts that onto others.

“Treating people well” does not mean using kid gloves. It can mean expecting people to act like grown-ups, produce grown-up work, and not accepting less. I worked in an environment like that for decades. It was often quite stressful, but was also personally rewarding.

It isn’t the tools that are broken; it’s the management culture, and no one wants to fix that.


I have a longer nuanced post to write out at some point about my feelings here, but here's the cliff's notes:

We should not be developing new projects in C, and we should make it politically hard to make new projects written in C in distribution repositories. C is going to stay around for a long time and that's okay, but there are so many inherent problems of its design and implementation that make it unsuited for unsupervised use. I wish that we didn't have to make such hard-line stances about this, but people have continuously proven that this tool is unsuitable for modern development and it's only going to get worse as time goes on.


> We should not be developing new projects in C

I would give at least embedded development a pass, since you rarely do have a choice there


It's difficult, because "embedded not networked" is an environment where security risks are low, but "embedded networked" is a really nasty environment of haunted routers and abandoned IoT devices.

Is it acceptable to risk buffer overruns on the HTTP server running on an insulin pump?


Mostly because you often get shipped half-broken pile of C-based SDK as your blessed environment.

Not because there's no other options (and long before Rust had its name coined)


Well, yes. I'm more focusing on user space applications here. This is why that nuance post would have to be so long. People here would probably misconstrue it to be that I want to "kill C" though. That's part of the reason why I haven't written it yet lol


I agree. I regularly use Rust on embedded devices and what many advocates like to leave out is that you're just using regular old C under the hood anyway. It's bindings all the way down.


If your distribution refuses to package useful free software, I'll use another distribution. I think this is true for most people.


[flagged]


Fine, but there's no reason you should be allowed to ship C in devices which have safety critical consequences to the public, including unnecessary risk of data security breaches through allowing buffer overruns. You can write in a nice safe sandbox over there.

(maybe this will eventually be part of the UL/CE requirements, "contains no C code")


C and C++ have another brand of safety "safe" languages du jour don't have: it's an ISO standard. Granted, it's not a very good standard, but the upside is that everyone and their dog has a C compiler for every new architecture, and I bet C++ too. You are not, strictly speaking, beholden to any single compiler implementation out there, they all have their idiosyncrasies, but C won't disappear overnight.

It's more even than having a single foundation oversee it. C and C++ are largely immune to, say, a foundation dissolving because one day it turns out its members cannot comport themselves as responsible adults and a big enough drama ensues. The fact that C and C++ have no singular "community" to speak of is a very strong side.

So when there are at least two (or better three) fully independent implementations of your C alternative, we'll talk about gatekeeping C.

Now, if you are a vendor and capital owner, nothing forbids you from forbidding C within your company and devices you produce. That has been your prerogative since forever, predicated on your ability to afford it. I'm more wound up about activists and evangelists trying to impose their hobby horse upon others. You do you. Show, don't tell.


That's not how society progress.

At some point you just disallow horses to go onto the high-speed road, and aren't waiting for literally every last farmer to sell their horse over a car.


High-speed roads are a low percentage of all roads.


Thank you for demonstrating why I haven't written that longer post yet. I'd just be torn apart and berated for bothering to express nuance.


The story is satire. But the satire is illustrating true problems and attitudes.


A safer language accommodates every programmer.

Nobody writes flawless code.


What’s missing from Rust? I don’t care if you use C, but don’t pretend like there isn’t another option


Simplicity.


With all the language features I agree.

However, if you reduce the language surface it is possible to have something safe and simple enough (IMHO).

For instance, you can say no async, no custom traits and only {Debug, Display, Eq, PartialEq, ...} are allowed for your structs and generics. From limited personal experience that takes away more than half of the complexity of navigating rust code.


The more you take away, the closer you are to a simple but unsafe language. If you remove the "unsafe" keyword, many things you can't solve easily nor optimally.

You might be able to outsource some complexity to external libraries, but integrating libraries is itself a major headache, and it can lead to security issues too.


Fair enough. But unsafe for kernel code I guess it's a necessary evil (that's why I didn't mention it).

However, I believe the being "opt-in" by explicitly marking sections unsafe is the way to go instead of having unsafe by default (which is the only way using C).


Unsafe is a daunting language feature, but ultimately it’s a feature. You’re meant to use it if you need it.

You don’t need to outsource complexity anywhere. Rust is fully capable.


This. Learning Rust is like a long series of jousting matches with the compiler.


It's more than that. Even after you learn it, you will struggle a lot with productivity, especially fast iteration and incremental changes and fixes, as Rust forces you to redesign and refactor your code.


How is that different than learning any other language?

I think Rust’s reputation for being hard to learn is overblown. Yes it is probably a little more esoteric than what you’re familiar with but any decent dev should be able to pick it up


The alternative is jousting with attackers. In production. When you're not aware of it. You're not even on the field, let alone mounted on your horse and wearing armour.


Simple | Fast | Safe. Choose two.


Sacrificing safety might have been defensible on a 40 MHz 80386 that could barely run a C++ compiler (I think this is what I started my career on) but today we find eight-core 3.8 GHz CPUs in desktops and dual-core 1.8 GHz SiPs in wristwatches. Safety should have become table stakes a while ago; Moore's Law already paid for it.


Exactly this. It's all about trade-offs. You need to pick the right tool for the job. Sometimes the right tool is dangerous. Knives are dangerous tools, but you need them if you want to cook a meal.


I love one comparison to hole hawg[0] (used for unix vs windows&mac), which is usable here. Memory safe languages are like a fancy electric drill from target. They get the job done, but if you want some more serious drilling they die on you instead of doing something dangerous. C is like one of those drills which are comprised of engine and a handle (cheap, changeable piece of steel pipe). They are designed to do one thing: they rotate a drill. When drill blocks, they rotate you. But when you take appropriate precautions, boy do it drills...

This is from my favourite book: In the beginning was the command line.

[0] https://web.stanford.edu/class/cs81n/command.txt , search "HOLE HAWG".


So what are you able to do with C that you can’t do in Rust? Somethings are more difficult to pull off, but everything possible in C is possible in Rust. Worst comes to worst you can always drop into unsafe Rust

This “sometimes you need a knife" argument just doesn’t make sense. You can have the power and the safety. Stop cutting off your fingers.


Make a program for 8051 MCU. Or a lot of other microcontrollers.

> This “sometimes you need a knife" argument just doesn’t make sense. You can have the power and the safety. Stop cutting off your fingers.

Sometimes you need a scalpel.

I'm not against Rust. I still want to start a project where Rust will be worth learning for me, but I'm currently busy writing erlang and java for big systems and C for microcontrollers. This is of course only my local situation, I don't advocate for those languages (I love erlang though and I'm wishing for advertised easy concurrency in Rust, maybe someday).


Actually I’ve been pleasantly surprised by Rust on the Arduino! Things are certainly much easier with the large community behind it, I probably would’ve chosen C if there wasn’t an existing community.

I don’t think Rust should be used everywhere, but I’m firm that it could be used anywhere that C can (short of an unsupported architecture).


People overwhelmingly chose Simple and Fast. Still.


This isn't a crazy thought to have about K&R C. They're trying to fit a high level language onto a 1970s computer and so sacrifices must be made. Some of the trades they make are... questionable and others I'd say clearly wrong (they just don't need Tony's billion dollar mistake, nor to be so cavalier with types in general), but it's not as though they're targeting a machine with gigabytes of RAM and a multi-core CPU.

But, people aren't writing K&R C. These days most of them are writing something closer to C99 or C11 and some may be using something newer (e.g. C17) or proprietary (GNU's C dialect) either deliberately or just because it compiled and nobody told them not to.

At that point you've actually given away much of the simplicity, and yet what you get for your trouble is largely more footguns. Trade up for a sound type system and fewer footguns instead.


ISO C comes with a list of constructs that the language is entitled to silently miscompile. Varying from "those bytes can't be an integer, I'll delete that function for you" through "signed integers can't overflow, so that overflow check you wrote must return false, so I'll delete it for you".

This makes simple application code slightly faster. That's kind of a reasonable domain for C++ but on dubious ground for C. Where C is superb is talking to hardware and language implementation, exactly the areas the ISO group has chosen to cripple the language for.

Thankfully compilers know about this and provide fno-strict-aliasing et al. Maybe there should be a std= style flag to change to the language subset that doesn't actively try to miscompile your program chasing benchmark numbers.


What things simplicity do you lose? I think there isn't much of a difference between these dialects. Most important one might be possibility to define variables on the spot instead of at top of block. Then, some people like C99 compound literals. I don't think any of these break simplicity, they are quality-of-life improvements with no interactions with the rest of the language semantics.

Next one is what, the C11 memory model? Doesn't take away any simplicity, just defines things better.


Here's my thinking. It's fair to say C99 isn't that much more complicated than C89, which formalizes various things that are a bad idea such as "volatile", as well as numerous good ideas like hey we should let you define a variable where you use the variable - however C99 adds more of the bad like "restrict".

In both those cases the K&R C model was very simple. You could decide you love how simple this model is, and when smarter compilers optimise it into a pretzel or other languages are faster that's OK. This code used to drive the serial port, now it does nothing, OK, don't use C to write such drivers. This code used to go real fast, now everybody else is faster, OK, don't use C if you need the best performance.

C89 and C99 choose different, making the language more complicated to keep addressing performance and compatibility. In C99 I can write the fast serial port driver, but it's significantly more complicated as a result. The beginner will definitely get it wrong and explaining why is pretty subtle.

Then C11 says actually you're not writing sequential programs, which was a crucial simplification in K&R C - the programs you can write do one thing at a time, in order, and then maybe stop. The memory model in C11 is needed because it says actually your programs might do more or different things at once.

Now, in reality by 2011 lots of people were writing C that's not actually sequential - after all SMP Linux long pre-dates C11. But those weren't legal C programs, they're GNU's dialect and so all bets are off. Nobody is claiming Linux is simple.

So C11 definitely isn't the simple language for a 1970s computer any more. C11 is a competitor with C++ or today Rust. And it doesn't fare so well by that comparison.


So you're focusing on the memory model. To which I'll say, it's not the language but it's the _machines_ that have SMP so effects will be visible out of order. The machines provide you with escape hooks to serialize read and write effects, and the language must give you access to these hooks because if the language opts to keep up the illusion of all sequential effects even in the face of SMP, it has to do so by serializing basically all memory accesses, which is going to be incredibly slow.

That code doesn't appear to be sequential from other threads is not the language's fault. It's just a fact of reality.

If you don't want to do SMP / lock-free programming, you don't have to pay for the complexity. You can still write "sequential programs" like it's K&R. Don't do multiple threads, and you don't have to care. You can also do multiple threads but use mutexes for synchronisation, everything will be fine.

Pretty much nobody uses restrict or volatile. There is some fringe stuff that is obsolete and maybe badly designed, but by and large you don't have to use it or interact with it. Compare this to other languages / ecosystems which have this amount of cruft times 100.

It's still this language where you can define a struct, then define a function that receives a pointer to the struct, and it does as you say, and you can actually read and understand the code a month later.

If you need to go fancy for a reason or another (most likely a bad reason), you can do that too, and of course you'll have to pay for it. But most likely, the complexity is not "cancerous" -- define a simple function interface with the most basic assumptions you require, and the complexity is pretty much isolated behind a simple pointer to an opaque structure.

> So C11 definitely isn't the simple language for a 1970s computer any more. C11 is a competitor with C++ or today Rust. And it doesn't fare so well by that comparison.

That's ridiculous and you know it.


> which is going to be incredibly slow.

"Faster but wrong" isn't really faster it's just wrong.

> That code doesn't appear to be sequential from other threads is not the language's fault. It's just a fact of reality.

What other threads? In a sequential programming language we certainly can't have "other threads".

> You can still write "sequential programs" like it's K&R.

"You can just not use the bits you don't like" is how we got C++ dialects that C++ proponents love to pretend don't exist. If the language is "simple" except for all the new bits that are complicated it's not simple.


With all 3 of these quotes and replies I feel completely misunderstood. I'm not at all trying to say the things that you're contradicting with. Won't engage anymore, but maybe you can reevaluate.


K&R gets used as a shorthand for the "portable assembly" C that some people remember and others swear never existed. That's my guess at what the parent meant, not the syntactic strangeness of writing types after the parameter list.

C11 atomics would have been much better if they'd standardised existing practice instead of inventing something based around the application centric design C++ was inventing. It's nice that there's an acquire/release scheme one can reason with but a real shame that they conflated it with type annotations.


Well computing has gotten a lot more complex since c’s inception. It’s model has held up well but the cracks are there


Programs are a lot more complex. The computers aren't.

They were far less homogenous in the early years. Today you have a octet addressed little endian integer machine, with ieee float hardware and a small vector unit. Maybe you have two different instances of that on the same address space, but probably just one.

I think reasonable argument could be made that the complexity in modern computing is primarily self inflicted by software engineers.


Computers have gotten much more complex. Maybe your OS provides that nice little bubble for you, but it’s an illusion


If the borrow checker was optional and not baked into the language. If it compiled fast and not slower than c++.

I might try it out if these two requirements were met. I have a mental model between what I write in C and what I can expect the assembly to be.

Or maybe the problem is that we have the same stack for code and data? could that be it?.


My problem with Rust is that it’s a hammer, and every nail is memory safety. If I’m writing a TLS library or kernel module, yes, memory safety is paramount. But if I’m writing pretty much anything else, Rust isn’t worth the slog.


How is that a problem with rust? Like any language it's a tool with strengths and weaknesses. Don't use it when it doesn't make sense


Sorry, should have been clearer. I was thinking in the context of every online discussion I’ve run across around “Rust vs. any other language,” where the diehard Rustaceans insist that we should just rewrite everything in Rust.

Totally agree with you: use the best tool for the job.


> only the baggage a project needs

What projects need manual memory management? Those where the hardware costs are comparable to development/maintenance costs. That is much rarer than people think.

RAM is cheap, and few applications really need bespoke allocation. And it's not just a question of skill; even the most disciplined make mistakes. It's one of how much brainpower you want to allocate to... memory allocation.


>What projects need manual memory management

Games.

Big/specialiased games to be precise, as for smaller projects managed language offer good enough performance.


In practice I found C/C++ "manual memory management" fans to know surprisingly little about memory management, or even how much manual memory management costs them in performance.

High end games programming sometimes knocks the love of malloc()/free() (and naive RAII) out of them.


In practice I find malloc()/free() mentioned almost exclusively by GC proponents. I don't know any "manual memory management" fans but only people who think that GC is worse than fully designing a memory management scheme for data-intensive applications. And these people shy away from using malloc(), because it is 1) slow/unpredictable (and yes, slower than GC allocation) 2) A general purpose allocator, which gives us little control 3) requires to match every call with a corresponding free(), which is tedious and error prone.

Note also that with (tracing) GC, you not only pay for the cost of allocation, but also pay for the time your allocated objects live.


Then you have a pretty nice bubble (this is not in jest, I honestly envy you).

A more nuanced take is that I can, in practice, map considerable chunk of developers I met and their understanding of memory as:

"it's magic" > "manual memory management rulz! my C/C++/Rust will be always better than GC" >>> people who actually studied automatic memory management a bit >> people who adjust memory management techniques to specific use cases

The fall off is pretty much exponential in amount of people, which I will admit does make some people defensive thanks to second group being both numerically big and undying because programmers suck at educating new generations. Same can be seen in various other areas, not just GC (though GC links with general trope of "opinions formed from people repeating things learnt from xeroxed half dog eaten copies of notes somewhat relevant on 256kB IBM 5150 PC and Turbo Pascal")

A lot of the time I do encounter people who essentially parrot claims about GC without any understanding of what's happening underneath, and indeed sometimes going about "crazy awesome non-GC solution!" that turns out to call out to whatever malloc()/free() happens to live in libc when I pull the cover and with just as much unpredictability as you can get.

As for paying - allocations in GC systems tend to be blindingly fast, but yes, collection does involve other costs.


What does knowing a lot about memory management look like? Is it like being familiar with heap allocation, the structure of fastbins and co., and some knowledge of lifetime and ownership?


In addition to that, also knowing at least basics (a bit like "classes of algorithms") of automated memory management, memory pooling, understanding and using the knowledge of allocation and mutation patterns in one's program.

A funny story related to all that from one of my previous jobs includes a high performance C++ code base that used forking to utilize preloaded code and data and to easily clean up after itself.

Turned out that naive-ish (even with jemalloc dropped in) memory management in C++ code resulted in latency spikes not because of allocation/freeing, but because they put no control over where things got allocated which resulted in huge TLB and pagetable thrashing as copy-on-write got engaged after forking.

To the point that using something like Ravenbrook's Memory Pool System (a GC) with threads quite possible would let them hit performance targets better.


The lack of memory safety is a property of C implementations, not the language itself. You can have an implementation that reliably crashes on memory errors if you want, though it does get easier if you leave out a few features like casting pointers to ints and back.


There is a multiple decade long history of coaxing a memory safe C embodiment. If it was possible to simply implement memory safety without fundamentally changing the language, you would have a compiler flag that would give to you, requiring no source modifications.

No compiler gives you that memory safety flag (aside from very specific security features, ex. -fstack-protector) because it's not possible in C.


It is totally possible for pointers with bounds checking to exist in C. Turbo C did that 30 years ago. However, why bother trying when whole culture is biased against it and you can use any of the other languages?


>Turbo C did that 30 years ago.

I used Turbo C++ (not C) back in the day, and recall near pointers, far pointers, based pointers, but I don't recall bounds-checked pointers. Do you remember any details?

>However, why bother trying

Because it would immediately make the large amounts of C code still in the wild safer for the users, who do care?


I remember it as an compiler otion.


For the sake of the example let's assume you make a C to Python compiler/transpiler which uses native Python arrays for every stack or heap based allocation and every pointer arithmetic would be replaced by a checked operation, you'd quite likely get a memory safe language in the 'shellcode impossible unless you really set the program up for it' sense.


https://discourse.llvm.org/t/rfc-enforcing-bounds-safety-in-...

You can also run with ASan on, or compile into WebAssembly and run that… obviously not perfect though.


This "accommodate less skilled practitioners" you wrote is something that never fails to puzzle me from C/C++ practicing colleagues: the idea that in the problem space there is a high-risk high-reward zone of top performance where only the heroes dare tread.

I am sorry but my observarion is that Nim and LLVM and dare I say even GC-less Java all achieve the same performance as C without the footgun baggage. Sometimes it is good to find a way to move on.


>I am sorry but my observarion is that Nim and LLVM and dare I say even GC-less Java all achieve the same performance as C

This is just false. Sure, they probably are 'close enough', but you can't claim the performance is 'the same' simply because those languages inherently do more to achieve the same results.


C inherently does spend more CPU time on memory management than some GC systems.

The fact that a lot of C/C++ fans do not properly profile those areas of their programs does not mean they do not pay for them.

Or that defensive coding (assuming they do not want to introduce a happy little accident of CVE into the world) is free compared to having compiler lift necessary asserts outside of hot loop.

Some memory-safe languages even provide a faster feedback loop on exact instructions that will be executed for all code you write (probably most famous example is Common Lisp, which mandates that DISASSEMBLE is accessible to developer at all times)


I'm trying to decide if this is a good satirical response to a satirical article? If so, well done!

Because at this point, where can we find programmers who aren't 'less skilled practitioners'? Because it's clear Microsoft, Google, Apple, and major open source projects, have failed to find them based on memory corruption issues in the Linux, Windows and Mac kernels, chrome, and firefox.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: