[HN Gopher] Imposing memory security in C [video]
       ___________________________________________________________________
        
       Imposing memory security in C [video]
        
       Author : transpute
       Score  : 51 points
       Date   : 2025-02-27 14:13 UTC (8 hours ago)
        
 (HTM) web link (fosdem.org)
 (TXT) w3m dump (fosdem.org)
        
       | pizlonator wrote:
       | To me, "memory safety" really means:
       | 
       | - There are clearly stated guarantees, like "a pointer cannot
       | access outside the bounds of its allocation" and "if you free an
       | object while there are still pointers to it then those pointers
       | cannot be dereferenced". These guarantees should be something you
       | can reason about formally, and they should be falsifiable. Not
       | sure this presentation really has that. It's not clear what they
       | prevent, and what they don't prevent.
       | 
       | - There is no way to break out of the clearly stated guarantees.
       | Totally unclear that whatever guarantees they have are actually
       | guarded against in all cases. For example, what if a tmp_alloc'd
       | object pointer escapes into another tmp_alloc'd object with
       | different lifetime - I get that they wouldn't write code that
       | does that intentionally, but "memory safety" to me means that if
       | you did write that code, you'd either get a compile error or a
       | runtime error.
       | 
       | It's possible to ascribe clearly stated guarantees to C and to
       | make it impossible to break out of them (Fil-C and CHERI both
       | achieve that).
        
         | johnnyjeans wrote:
         | > There is no way to break out of the clearly stated
         | guarantees.
         | 
         | I disagree on this, and having escape hatches is critically
         | important. Are we really going to call Rust or Haskell memory
         | unsafe because they offer ways to break their safety
         | guarantees?
        
           | pizlonator wrote:
           | I think that Rust's clearly stated guarantee holds if you
           | never say "unsafe".
           | 
           | That's still a clear statement, because it's trivial to tell
           | if you used "unsafe" or not.
        
             | jeffrallen wrote:
             | But the Rust ecosystem is littered with unsafe, so good
             | luck getting the actual benefits of Rust. :(
        
               | pizlonator wrote:
               | Great reason to use Fil-C
        
               | woodruffw wrote:
               | The actual benefits seem manifest: there aren't nearly as
               | many public reports of memory corruption (much less
               | exploitable corruption) in Rust components, even when
               | those components make extensive use of unsafety directly
               | or transitively.
               | 
               | (This seems like one of those "throw the baby out with
               | the bathwater" cases that people relitigate around Rust
               | -- there's ample empirical evidence that building safe
               | abstractions around unsafe primitives works well.)
        
               | pizlonator wrote:
               | > The actual benefits seem manifest: there aren't nearly
               | as many public reports of memory corruption (much less
               | exploitable corruption) in Rust components, even when
               | those components make extensive use of unsafety directly
               | or transitively.
               | 
               | Not sure the data is clean enough to draw meaningful
               | conclusions because of confounding factors.
               | 
               | The biggest confounding factor is that Rust is relatively
               | new, code written in it is even newer, and folks who
               | research vulns may not have applied the same level of
               | anger to Rust as to C.
               | 
               | That said, your point about "throwing the baby out with
               | the bathwater" is well taken. I would _expect_ that Rust
               | has _much fewer_ vulns than C /C++. My point is only that
               | it's an unproven expectation.
        
               | woodruffw wrote:
               | > Not sure the data is clean enough to draw meaningful
               | conclusions because of confounding factors.
               | 
               | I'm thinking of things like the Windows user- and kernel-
               | mode font parsers; these have a pretty long and steady
               | public history of exploitation that seems to have mostly
               | stopped with the Rust rewrite 1-2 years ago. I don't
               | think that's because vuln researches have stopped looking
               | at them!
               | 
               | But yeah, I would like it if Google and Microsoft (among
               | others) would put more hard data out there. I don't think
               | of the Windows kernel teams as typically suffering from
               | hype-driven development, so my abductive conclusion is
               | that they have strong supporting data internally.
               | 
               | Edit: here's a hard data source from Google, showing that
               | Rust has contributed to a marked decline in memory
               | unsafety in Android[1].
               | 
               | [1]: https://security.googleblog.com/2022/12/memory-safe-
               | language...
        
               | pizlonator wrote:
               | That's good data, but: is the reduction in vulns because
               | Rust is safer, or is it because vuln researchers assume
               | it's safer and so choose to look for vulns in C/C++ code
               | because it's what they're familiar with?
               | 
               | It's hard to say.
        
               | simonask wrote:
               | Wait, what? Unsafe in Rust _is_ the actual benefit. That
               | is to say, what `unsafe` does is that it allows you to
               | implement a fundamentally tricky thing and express its
               | safety invariants in the type system and lifetime system,
               | resulting in a safe API with no tricky parts.
               | 
               | That's the whole point.
               | 
               | There's tons of trivial unsafe in the Rust ecosystem, and
               | a little bit of nontrivial unsafe, because crates.io is
               | full of libraries doing interesting things (high-
               | performance data structures, synchronization primitives,
               | FFI bindings, etc.) while providing a safe API, so you
               | can do all of that without writing any unsafe yourself.
               | 
               | The point of Rust isn't that you can implement low-level
               | data structures in safe code, but that you can use them
               | without fear.
        
               | pizlonator wrote:
               | Saying that unsafe is a benefit is backwards, I think.
               | Unsafe is the compromise Rust made to achieve its other
               | goals, but of course Rust would be better if there was
               | some way of doing things without unsafe.
        
               | not2b wrote:
               | Consider the split_at_mut function. It takes one mutable
               | slice, and returns two mutable slices by chopping at a
               | caller-specified point. It's in the standard library.
               | 
               | The operation is completely safe, as after the call the
               | original mutable slice is no longer live, but the borrow
               | checker won't let you write such a function yourself
               | unless you tag it as unsafe, so that's what the
               | implementation must do.
               | 
               | The same thing happens in the implementation of Vec:
               | there is low-level code that is unsafe, used to provide
               | safe abstractions.
        
               | pizlonator wrote:
               | That's not a benefit of Rust. In other safe languages you
               | could implement those things without writing a single bit
               | of unsafe code. (This is true in Fil-C for example.)
        
               | amluto wrote:
               | Is that really fair? Neither Fil-C nor C have anything
               | that particularly resembles & or &mut. If Rust had an
               | &shared_mut style of reference, you could presumably
               | split it without unsafe. For that matter, Rust does have
               | various interior-mutable types, and you could have a
               | shared reference to a slice of AtomicBool or whatever,
               | and you can split that without any particular magic.
        
               | pizlonator wrote:
               | Fil-C doesn't have those things because it doesn't need
               | them to achieve safety.
        
               | jeffrallen wrote:
               | I don't see the interest in split_at_mut. I can get the
               | same thing by reslicing in Go. And also the GC will do
               | the job the borrow checker foists off onto the unlucky
               | Rust programmer.
               | 
               | Pfft, whatever. Rusters gonna rust, I guess.
        
               | not2b wrote:
               | I'm not a Ruster, I've spent my career writing a ton of
               | C++. But I'm interested in Rust as an alternative because
               | it doesn't need GC.
               | 
               | But in this case, it's not just the memory safety I'm
               | interested in, it's the data races. If we have multiple
               | threads but can guarantee that any object either has only
               | read-only references, or one mutable references and no
               | readers, we don't have data race issues.
        
             | the__alchemist wrote:
             | _Trivial_ is not a word I would use here! Rust 's `unsafe`
             | gets fuzzy as you transverse an operation's dependencies!
             | There are many applications where marking a function as
             | `unsafe` is subjective.
        
               | pizlonator wrote:
               | True.
               | 
               | I do think the ideal kind of memory safe language either
               | has no "unsafe", or has an "unsafe" feature that only
               | needs to be used in super rare an obscure cases (Java is
               | like that, sort of).
               | 
               | Fil-C has no "unsafe", so in that sense Fil-C is safer
               | than Rust. You don't need an escape hatch if the memory
               | safety guarantees are dialed in just right.
        
               | titzer wrote:
               | In Virgil, native targets have a couple of unsafe
               | operations available, one of which is to be able to forge
               | a closure from a pair of a code pointer and an object
               | reference. This is used, e.g. to implement Wizard's JIT
               | and fast interpreter, which generate new machine code at
               | runtime. I can't imagine the level of proof necessary to
               | make that safe--and not just the safety of the generated
               | machine code, but it often lacks bounds checks because it
               | relies on running verified Wasm bytecode, which cannot go
               | out of bounds. So the proof would have to include a
               | complete proof of correctness for the code validation
               | algorithm (i.e. part of Wizard itself).
        
               | pizlonator wrote:
               | I dream of a JIT API that lets you propose machine code
               | that is checked using an abstract interpreter to ensure
               | that you have adequate checks to stay within the host
               | language's type system.
               | 
               | Someday, man
        
               | porridgeraisin wrote:
               | I don't get this, can you explain?
        
               | pizlonator wrote:
               | Sure.
               | 
               | If I told you that I have a snippet of machine code that:
               | 
               | - obeys the ABI of your safe language (ie it has exactly
               | the calling convention that safe language uses)
               | 
               | - corresponds exactly to a function body whose signature
               | is T->U (or whatever, different safe languages have
               | different function type syntax)
               | 
               | - obeys the language's type system.
               | 
               | Then you could run an abstract interpreter to check that
               | the machine code follows that type system. Simple
               | example: given the above claims, if we further assume
               | that the host language impl puts argument one into
               | register 5, and the first argument's type is "pointer to
               | an array of bytes", and we know that arrays have a 64-bit
               | length prefixed to the start, then the abstract
               | interpreter would just need to check that any deref of
               | register 5 is preceded by a bounds check on whatever was
               | loaded at offset -8 from register 5. And so on, for every
               | possible thing you can do in the language.
               | 
               | Then the JIT would just have to make sure it puts checks
               | in all of the places that the absint expects them. If the
               | absint fails, then the machine code is rejected.
        
               | hotjump wrote:
               | The problem of C code migration to memory safe languages
               | is that legacy C projects aim for extremely high
               | performance. Garbage-collecting languages would also be
               | safe in any situation, but I want to note that the recent
               | tendency toward Rust derives from its type-system based
               | approach that imposes very few runtimes checks such as
               | bound checking. I myself hope something like F* gets more
               | attraction in the industry.
        
               | pizlonator wrote:
               | I hear the perf claim a lot and yet most of the time when
               | I write C/C++ code it's not because it's the most
               | performant. It's either because I'm editing a codebase
               | that's already in C/C++, or I'm using libraries whose
               | best (or only) bindings are C/C++, or because I want to
               | make syscalls (and safe languages don't expose those as
               | nicely as C).
        
               | rrrix1 wrote:
               | For desktop, server, web, mobile, etc. This holds true.
               | Not so much for embedded systems, anything with
               | unnatractive memory capacity or processor performance.
               | Rust is starting to make it's way in, but C and even
               | assembly is still king AFAIK.
        
               | pizlonator wrote:
               | And yet there are embedded systems running JavaScript and
               | Python.
        
               | amluto wrote:
               | I think there's a sense in which Rust is safer than
               | Fil-C, though: Rust allows abstractions with little to do
               | with memory safety but that still can't be broken without
               | 'unsafe'. So a struct called EvenNumber can fairly
               | strongly guarantee that it contains an even number.
               | 
               | But Fil-C objects (at least for now?) only seem to allow
               | one single capability type, and that capability grants
               | unrestricted read/write access to the object's bytes.
               | 
               | I wonder if one could build a handle system in Fil-C that
               | would allow this to be extended. Or if a different
               | variant of a Fil-C-like system could distinguish between
               | pointers with different access levels to an object and
               | could allow only the correct piece of trusted code to
               | increase the permission of a pointer.
        
               | pizlonator wrote:
               | I've thought about adding such things to Fil-C but have
               | held off on going there because it feels like a bridge
               | too far.
               | 
               | What I mean by that is: the memory safety issues of C are
               | a total dumpster fire, while whether a number is even or
               | not (and whether you can prove that) is maybe like icing
               | on the dumpster fire. It just doesn't matter by
               | comparison.
               | 
               | So I want to decisively fix the memory safety issues and
               | not lose focus.
        
             | johnnyjeans wrote:
             | Maybe I just misinterpret what you mean. When you say "no
             | way" and "all cases", I take your meaning literally. The
             | existence of pointers to bypass the borrow checker,
             | disabling runtime bounds checks and unsafe blocks are
             | exactly that: escape hatches to break Rust's safety, in the
             | same way type-casting is an escape hatch to break C's
             | (anemic) type safety, and unsafePerformIO in Haskell is an
             | escape hatch to break every bone in your body.
        
               | pizlonator wrote:
               | That's fair.
               | 
               | FWIW, Fil-C's guarantees are literally what you want.
               | There's no escape.
        
         | kstrauser wrote:
         | Thinking aloud, and this is probably a bad idea for reasons I
         | haven't thought of.
         | 
         | What if pointers were a combination of values, like a 32 bit
         | "zone" plus a 32 bit "offset" (where 32/32 is probably really
         | 28/36 or something that allows >4GB allocations, but let's
         | figure that out later). Then each malloc() could increment the
         | zone number, or pick an unused one randomly, so that there's
         | enormous space between consecutive allocs and an address
         | wouldn't be reissued quickly. A dangling pointer would the
         | point at an address that isn't mapped at all until possibly
         | 2^32 malloc()s later. It wouldn't help with long-lived dangling
         | pointers, but would catch accessing a pointer right after it
         | was freed.
         | 
         | I guess, more generally, why are addresses reused before they
         | absolutely must be?
        
           | pizlonator wrote:
           | That's a way of achieving safety that has so many costs:
           | 
           | - physical fragmentation (you won't be able to put two live
           | objects into the same page)
           | 
           | - virtual fragmentation (there's kernel memory cost to having
           | huge reservations)
           | 
           | - 32 bit size limit
           | 
           | Fil-C achieves safety without any of those compromises.
        
             | kstrauser wrote:
             | For sure. I'm under no illusion that it wouldn't be costly.
             | What I'm trying to suss out is whether libc could
             | hypothetically change to give _better_ safety to existing
             | compiled binaries.
        
               | pizlonator wrote:
               | Two things:
               | 
               | - The costs of your solution really are prohibitive. Lots
               | of stuff just won't run.
               | 
               | - "Better" isn't good enough because attackers are good
               | at finding the loopholes. You need a guarantee.
        
           | throwawaymaths wrote:
           | you can do this easily with virtual memory, and IIRC Zig's
           | general purpose allocator does under some circumstances
           | (don't remember if its default or if it needs a flag).
        
           | zyedidia wrote:
           | It sounds like what you're describing is one-time allocation,
           | and I think it's a good idea. There is some work on making
           | practical allocators that work this way [1]. For long-running
           | programs, the allocator will run out of virtual address space
           | and then you need something to resolve that -- either you do
           | some form of garbage collection or you compromise on safety
           | and just start reusing memory. This also doesn't address
           | spatial safety.
           | 
           | [1]:
           | https://www.usenix.org/system/files/sec21summer_wickman.pdf
        
             | kstrauser wrote:
             | Oh, nifty! I guarantee you anyone else discussing this has
             | put more than my 5 minutes' worth of thought into it.
             | 
             | Yeah, if you allow reuse then it wouldn't be a guarantee. I
             | think it'd be closer to the effects of ASLR, where it's
             | still possible to accidentally still break things, just
             | vastly less likely.
        
           | layer8 wrote:
           | This sounds similar to the 386 segmented memory model: https:
           | //en.wikipedia.org/wiki/X86_memory_segmentation#80386_...
           | 
           | However, it was limited to 8192 simultaneous "allocations"
           | (segments) per process (or per whatever unit the OS
           | associates the local descriptor tables with).
        
         | PaulDavisThe1st wrote:
         | > There are clearly stated guarantees, like "a pointer cannot
         | access outside the bounds of its allocation"
         | 
         | But that's not a pointer in anything like the sense of a C
         | pointer.
         | 
         | You'd need to reword that (as I know you've been doing with
         | FiL-C) to be something more like: no reference to a
         | (variable|allocation|object) may ever be used to access memory
         | that is not a part of the object.
         | 
         | Pointers are not that, and the work you've done in FiL-C to
         | make them closer to that makes them also be "not pointers" in a
         | classic sense.
         | 
         | I'm OK with that, it just needs to be more clear.
        
           | pizlonator wrote:
           | Semantics.
           | 
           | You can call Fil-C's pointers whatever you like. You can call
           | them capabilities if that works better for you.
           | 
           | The point of my post is to enumerate the set of things you'd
           | need to do to pointers to make them safe. If that then means
           | we've created something that you wouldn't call a pointer then
           | like whatever
        
             | tredre3 wrote:
             | > Semantics.
             | 
             | If your goal is just to redefine the word then by all
             | means, continue.
             | 
             | But semantics are very important if your goal is to drive
             | adoption of your ideas. You can't misuse a term and then
             | get pissy when people don't understand you.
        
               | pizlonator wrote:
               | Seems like bro understood me just fine.
               | 
               | In Fil-C, pointers are called "pointers".
        
             | PaulDavisThe1st wrote:
             | And my point is that you cannot make C pointers safe. You
             | can make something else that is safe, and you're clearly
             | hard at work on that, which is great.
        
               | pizlonator wrote:
               | Fil-C's pointers work more like C pointers than like any
               | other language construct I can think of, and are
               | compatible enough that lots of C/C++ code compiles and
               | runs with no changes.
               | 
               | So I think that Fil-C pointers are just pointers.
               | 
               | You could even get pedantic over what the spec says. If
               | you go there, you find that Fil-C's pointers work exactly
               | like how the spec promises pointers to work (and all of
               | the places that the spec doesn't define either have safe
               | semantics in Fil-C or lead to Fil-C safety errors).
        
               | pjc50 wrote:
               | What behavior _in the C standard_ requires unsafety from
               | pointers??
        
               | pizlonator wrote:
               | Not a big fan of this line of thinking since if you tried
               | to deploy a C implementation that just implements what's
               | in the C standard, then you'd quickly find that real
               | world C code expects more of pointers than the C standard
               | promises.
               | 
               | Fil-C supports a superset of the C standard but a subset
               | of what contemporary mainstream C compilers support (you
               | can't pass an integer around in memory that is really
               | being used to represent a pointer in Fil-C, but you can
               | in Yolo-C).
        
               | layer8 wrote:
               | Does Fil-C support uintptr_t? Because I've written
               | programs that I believe to be quite strictly conforming
               | C99 programs, that make use of uintptr_t.
        
         | blacksqr wrote:
         | There are and have been many techniques and projects for making
         | C more memory-safe. The crucial question it always comes down
         | to is what performance hit do you take using them?
         | 
         | That's why C has been on top for so long. Seat-of-the-pants
         | hand-crafted C has always been the fastest high-level language.
        
           | WalterBright wrote:
           | I found that C programs rarely evolve beyond their initial
           | design. The trouble is, it's hard to refactor C programs. For
           | example,                   struct S { int a; };
           | struct S s; s.a = 3;         struct S *p; p->a = 3;
           | 
           | I.e. a . is for direct access, -> for indirect access. Let's
           | say you want to change passing S by value to passing S by
           | pointer. Now you have to update every use, instead of just
           | the declaration.
           | 
           | This is how it would work in D:                   struct S {
           | int a; }         S s; s.a = 3;         S* p; p.a = 3;
           | ref S q; q.a = 3;
           | 
           | And so refactoring becomes much easier, and so happens more
           | often.
        
             | WalterBright wrote:
             | > C has always been the fastest high-level language.
             | 
             | C has another big speed problem. Strings are 0 terminated,
             | rather than length terminated. This means constant scanning
             | of strings to find their length. Even worse, the scanning
             | of the string reloads the cache with the string contents,
             | which is pretty bad for performance.
             | 
             | Of course, you could use `struct String { char *p; size_t
             | length; };` but since every library you want to connect to
             | uses 0 terminated strings, you're out on your island all
             | alone, so pragmatically it does not work.
             | 
             | Another speed-destroying problem with C strings is you
             | cannot take a substring that does not require allocating a
             | new string and then copying the data. (Unless the substring
             | is right-justified.) This is not fast in any universe.
             | 
             | D uses length-denoted strings as a basic data type, and
             | with string processing code, it is much faster than C.
             | Substrings are quick and easy. You can still interface with
             | C because D string literals implicitly convert to C string
             | literals, as the literals are 0 terminated. So this works
             | in D:                   printf("hello world!\n");
             | 
             | (People sometimes rag on me for still using printf, but
             | printf is the most optimized and debugged library function
             | in the world, so I take advantage!)
        
               | pizlonator wrote:
               | Yeah totally.
               | 
               | It's a perfect example of C being optimized for _simple_
               | mapping onto linear memory, rather than some kind of
               | performance optimum
        
           | pizlonator wrote:
           | > There are and have been many techniques and projects for
           | making C more memory-safe.
           | 
           | Sort of. None of them got all the way to safety, or they
           | never got all the way to compatibility with C.
           | 
           | Fil-C is novel in that it achieves both safety and
           | compatibility.
           | 
           | > The crucial question it always comes down to is what
           | performance hit do you take using them?
           | 
           | Is that really the crucial question?
           | 
           | I don't think you would have even gotten to asking that
           | question with most attempts to make C memory safe, because
           | they involved experimental academic compilers that could only
           | compile a subset of the language and only worked for a tiny
           | corpus of benchmarks.
           | 
           | Lots of C/C++ code is not written with a perf mindset. Most
           | of the UNIX utilities are like that, for example.
           | 
           | > That's why C has been on top for so long. Seat-of-the-pants
           | hand-crafted C has always been the fastest high-level
           | language.
           | 
           | I don't think that's the reason. C rose to where it is today
           | even when it was much slower than assembly. C was slower than
           | FORTRAN for a long time (maybe still is?) but people
           | preferred C over FORTRAN anyway.
           | 
           | C's biggest superpower is how easy it makes it to talk to
           | system ABI (syscalls, dynamic linking, etc).
        
             | blacksqr wrote:
             | >> There are and have been many techniques and projects for
             | making C more memory-safe.
             | 
             | > Sort of.
             | 
             | Yes. That's why I used the qualifier "more." Our statements
             | are not in conflict.
             | 
             | > Fil-C is novel in that it achieves both safety and
             | compatibility.
             | 
             | How does it affect performance?
             | 
             | >> The crucial question it always comes down to is what
             | performance hit do you take using them?
             | 
             | > Is that really the crucial question?
             | 
             | Yes, because it's the factor that industry leaders use to
             | decide on which language to use. For example, Apple
             | switching from Pascal to C way back in the Stone Age. The
             | fact that it's the crucial question doesn't mean that lots
             | of people don't consider other factors for their own
             | reasons.
             | 
             | > I don't think you would have even gotten to asking that
             | question with most attempts to make C memory safe.
             | 
             | Yes, most. But for example, Microsoft's Checked C comes
             | with a performance penalty of almost 10% for a partial
             | solution. Not academic. Very commercial.
             | 
             | > C rose to where it is today even when it was much slower
             | than assembly
             | 
             | Yes, that's why I said "high-level language." I don't
             | consider assembly high-level, do you?
             | 
             | > people preferred C over FORTRAN anyway
             | 
             | People preferred C in the 1970s/80s because at the time you
             | could allocate memory dynamically in C but not in FORTRAN.
             | FORTRAN fixed that in the 1990s, but by then there were too
             | few FORTRAN programmers to compete. Since then C has
             | serially defeated all newcomers. Maybe Go or Rust are
             | poised to take it on. When a major operating system
             | switches from C, we'll know.
        
               | pizlonator wrote:
               | > How does it affect performance?
               | 
               | Right now, 1.5x-5x, but considering how many
               | optimizations I know I can do but haven't done yet, I
               | think those numbers are an upper bound.
               | 
               | > Yes, because it's the factor that industry leaders use
               | to decide on which language to use. For example, Apple
               | switching from Pascal to C way back in the Stone Age. The
               | fact that it's the crucial question doesn't mean that
               | lots of people don't consider other factors for their own
               | reasons.
               | 
               | I don't think this is true at all, sorry. Top reason for
               | using C/C++ is inertia. If you've got a pile of C code,
               | then you'll keep writing C.
               | 
               | > Yes, most. But for example, Microsoft's Checked C comes
               | with a performance penalty of almost 10% for a partial
               | solution.
               | 
               | Checked C didn't make C memory safe, so I don't think
               | it's interesting.
               | 
               | > Yes, that's why I said "high-level language." I don't
               | consider assembly high-level, do you?
               | 
               | No, I don't consider assembly to be high-level. The point
               | is: serious engineers don't just blindly reach for the
               | fastest programming language. They'll take slow downs if
               | it makes them more productive. Happens all the time.
               | 
               | > People preferred C in the 1970s/80s because at the time
               | you could allocate memory dynamically in C but not in
               | FORTRAN. FORTRAN fixed that in the 1990s, but by then
               | there were too few FORTRAN programmers to compete. Since
               | then C has serially defeated all newcomers. Maybe Go or
               | Rust are poised to take it on. When a major operating
               | system switches from C, we'll know.
               | 
               | The last time I saw a benchmark of FORTRAN beating C was
               | the early 2000's. FORTRAN is much easier to optimize and
               | compile.
               | 
               | C is great for writing operating systems because C has
               | the right abstractions, such as the abstractions
               | necessary for doing dynamic linking and basically any
               | kind of ABI compatibility. Rust and Go don't have that
               | today. Even C++ is worse than C in this regard. Swift has
               | ABI, but it took heroic efforts to get there.
               | 
               | C didn't eat the world because of its stellar
               | performance. It's the other way around. C has stellar
               | performance because it ate the world, and then the
               | industry had no choice but to make it fast.
        
           | WalterBright wrote:
           | C's memory safety could be drastically improved with the
           | addition of bounds-checked arrays (which is an extension, and
           | does not change existing code):
           | 
           | https://www.digitalmars.com/articles/C-biggest-mistake.html
           | 
           | 25 years of experience with D has shown this to be a huge
           | improvement.
           | 
           | D also has references as an alternative to pointers.
           | References cannot have arithmetic done on them. Hence, by
           | replacing pointers with references, and with array bounds
           | checking, the incidence of memory corruption is hugely
           | reduced.
        
             | pizlonator wrote:
             | > C's memory safety could be drastically improved with the
             | addition of bounds-checked arrays (which is an extension,
             | and does not change existing code):
             | 
             | If you solved that problem then you'd still have a dumpster
             | fire of memory safety issues from bad casts, use after
             | free, etc
        
               | WalterBright wrote:
               | Array overflows is consistently the number one memory
               | safety bug in shipped code, by a wide margin.
        
               | pizlonator wrote:
               | Citation needed.
               | 
               | (I would have guessed similar to what you said, minus the
               | "by a wide margin" bit.)
        
               | WalterBright wrote:
               | A few minutes of googling:
               | 
               | https://cwe.mitre.org/top25/archive/2024/2024_cwe_top25.h
               | tml
               | 
               | https://runsafesecurity.com/blog/memory-safety-
               | vulnerabiliti...
        
               | pizlonator wrote:
               | Neither of those support your claim that buffer overflows
               | are the top issue _by a wide margin_.
               | 
               | If you are saying that "most memory safety issues are
               | bounds related" then I agree. I'm just disagreeing on the
               | wide margin bit.
        
       | SV_BubbleTime wrote:
       | I am in no way at all better than that guy. Not even sort of. I
       | appreciate his talk.
       | 
       | However, if I were to make a presentation based on my superior C
       | practices, it would have to be implementation and example heavy.
       | 
       | All of his rules sound great, except for when you have to break
       | them or you don't know how to do the things he's talking about in
       | _your_ code, because you need to get something done _today_.
       | 
       | It reads a little like "I've learned a lot of lessons over my
       | career, you should learn my lessons. You're welcome."
        
         | rrrix1 wrote:
         | The talk was obviously extremely time-limited, as demonstrated
         | when they basically skipped the last handful of slides and then
         | it abruptly ended. I think for the time allocated, it was just
         | right, and they did include a couple of examples where it made
         | sense.
        
       | beardyw wrote:
       | I remember chasing down a memory leak in my first commercial C
       | code. Took me a long while to discover that if you allocate zero
       | bytes you still have to free it! After that I took nothing for
       | granted.
        
         | weinzierl wrote:
         | It's not even guaranteed that it doesn't allocate, so a
         | malloc(0) could cause an out of memory.
        
           | ignoramous wrote:
           | > _malloc(0) could cause an out of memory_
           | 
           | tbh, 640K RAM ought to be enough for anybody.
        
             | weinzierl wrote:
             | For the last drop to make the cup run over it doesn't
             | matter how big the cup is.
        
       | debatem1 wrote:
       | I don't think anyone ever doubted that a C program could be
       | memory safe. The problem is knowing without exhaustive work
       | whether yours is one of them.
       | 
       | These aren't bad practices, but I don't think they satisfy that
       | desire either.
        
       | cryptonector wrote:
       | Nah. I use C a lot, but none of this is enough to make C safe.
       | You really need the language and the tools to enforce discipline.
       | Oh, and things like the cleanup attribute are not standard C
       | either, so this is not portable code.
        
         | throwawaymaths wrote:
         | usually portability in C includes the provision that you can
         | drop in whatever #includes you want?
        
           | cryptonector wrote:
           | No, it's really not that simple at all.
        
             | throwawaymaths wrote:
             | Probably depends on the macro, but ok.
        
       ___________________________________________________________________
       (page generated 2025-02-27 23:01 UTC)