3 ms·
You can write every program you want to without pointer arithmetic. If you mean that a dereference of a memory location involves the compiler emitting pointer
by mjburgess 2y ago
You can write every program you want to without pointer arithmetic.
If you mean that a dereference of a memory location involves the compiler emitting pointer arithmetic instructions -- that's true of all languages.
If you want your language to completely disguise the machine from you, and "abstract away" memory allocation, you're going to pay a high complexity cost to do so.
If you have never run C in a debugger, with the massive amount of highly sophisticated tooling available to C debuggers, then you're operating from a profoundly mistaken starting point for evaluating the viability of C for modern safe sofware development.
C debuggers and tooling are vastly more powerful than Rust's static type system, and catch a much wider array of memory problems (, and bugs) than the Rust compiler can catch. Static verification is far more limited than the dynamic verification a sophisticated debugger can perform.
People's undergrad C course is a terrible basis on which to evaluate what C is today. The reason C is associated with a lack of security is that almost all software is written in C, written in a time when either the internet didnt exist or didnt imply an adversarial local environment.
Running a network cable through every facet of our O/S and software breaks many assumptions about the entire history of programming -- which C predominated in. This is a very poor basis on which to generalize the capabilities of a well-specified C programming environment (which today, is much more powerful than Rust's compiler).
- mschuster91 2y ago> People's undergrad C course is a terrible basis on which to evaluate what C is today. The reason C is associated with a lack of security is that almost all software is written in C, written in a time when either the internet didnt exist or didnt imply an adversarial local environment. The problem is - undergrad C is about the lowest common denominator that all tooling understands and that all people understand. Of course you're probably not going to have to go as low as C89 like sqlite or, until 2022, the Linux kernel [1], but still, the long support cycles of many distributions make it challenging to move standards upgrades forward. [1] https://www.zdnet.com/article/linus-torvalds-prepares-to-move-the-linux-kernel-to-modern-c/ https://www.zdnet.com/article/linus-torvalds-prepares-to-mov...
- LegionMammal978 2y ago> C debuggers and tooling are vastly more powerful than Rust's static type system, and catch a much wider array of memory problems (, and bugs) than the Rust compiler can catch. Static verification is far more limited than the dynamic verification a sophisticated debugger can perform. Is there any dynamic verifier that fully validates all acesses w.r.t. the object trees specified by the C standard? Tools like ASan and UBSan won't detect a write to one field running into another field, only a write overrunning the complete object. (Compiler-level hardening might catch that to some extent, but it's limited to TU boundaries.) Not to mention things like 'misuse of restrict pointers' that I've never seen any verifiers for, except for special cases like overlapping memcpy() buffers. Meanwhile, Rust does have its own dynamic verifier, called Miri [0], which checks just about every language-level rule at runtime. The main drawbacks are that it's slow and doesn't support calling arbitrary C functions, but it would be hard to get that to work short of the Valgrind route of emulating the whole process on an instruction level. [0] https://github.com/rust-lang/miri https://github.com/rust-lang/miri
- mjburgess 2y agoIf you're redoing the c std, which was my initial point, you can introduce a debug allocator with metadata about object layout. Coupled with some debugger support, i'd imagine you can get there. And if the C std was willing to allow constexpr to do more at compile time, you wouldn't need explicit debugger support and could just use constexpr to modify the compiler. There's also nothing stopping debuggers reifying the C at debug-time into this metadata. My claim is 95% can be fixed by just normalizing what is current practice at the stdlib level and compiler level. By extending constexpr, i think you could get to 100%. Given that this is the case, why even both with the nightmare of Rust.
- LegionMammal978 2y agoOh, you sounded like you were talking about something that already exists today, rather than something that you'd like to exist. The main problem with taking dynamic verification all the way is making it work with ABI boundaries, which won't be going anywhere in at least the next decade. You'd need everyone to migrate to a universal ABI that can convey all needed metadata, and while I've seen a few proposals for that, none of them have gone anywhere.
- jcranmer 2y ago> C debuggers and tooling are vastly more powerful than Rust's static type system As someone who's worked on C debuggers and tooling... I really have no idea what you're talking about. C's core semantics are just so weak that it's not really possible to express a lot of the things you can express in the type system, and that's before we get to the necessary lossiness that debuggers and tooling have to work with (e.g., you can't just ascribe types to memory in C because C--in practice--is way too loose with types for that to be meaningful). For an example from something I've worked on, Linux manages to have two different arrays for the GPRs for a thread register context, one that's used for ptrace and one that's used for signal contexts. Helpfully, the header files give you macros to map register names to numbers so that you can say regs[RAX] instead of regs[0]. But the offsets are different, so you have to remember that you need to use regs[REG_RAX] instead of regs[RAX], and there is absolutely no tooling in the world that can tell you when you get it wrong because there is no expressible difference between the two scenarios in C. Meanwhile, in Rust, I can wrap the accessors in newtypes so that I can only use the correct set of constants to index into the array, which makes the error state literally impossible to construct. That's the real value of a static type system--you can use it to make errors literally impossible to specify in an API.
- mjburgess 2y agoI don't see the difference between having a typed access API (V inline reg_at(enum K k) {} ) and overloading the indexer. If your point is that historical C APIs have overused an untyped operation, that's part of my point about a new std lib. Rust APIs can still provide an untyped indexer, it's just bad API design. What I'm imagining a new std lib would be doing is having debug allocators, metadata against types, etc. Ie., a std library designed for the debugger along with a release version.
- jcranmer 2y agoUsing different enums doesn't help, because C will happily let you cast enum A to enum B implicity. In Rust, I can express an API which can't be used incorrectly. In C, I can't. Sometimes, in C, you can sometimes get to the point where you use conventions that means maybe static or dynamic analysis tools might be able to flag the misuse of the API, but very often, such tools have extremely poor tradeoffs between precision and accuracy, far worse than exists in Rust with just the vanilla compiler.