4 ms·
This is more like "a comparison of terrible C to middling Rust." Rust is generally a superior language, but this sort of comparing the worst kind of C to middli
by sidlls 9y ago
This is more like "a comparison of terrible C to middling Rust." Rust is generally a superior language, but this sort of comparing the worst kind of C to middling (or, worse, well-written) Rust doesn't seem useful to me.
I suppose it could be worse: these sorts of examples could always use obfuscated C as their source for comparison.
- klodolph 9y agoYeah, sure. Personally, as much as I dislike the way the Rust community evangelizes the way Rust works (like the Haskell community writing another monad tutorial), there are some pretty common categories of errors demonstrated in the C code: - Return pointer to stack object (more common than you might think!) - Incorrect size passed to malloc() - Pointer to array outliving invalidation of array Some of the remaining C code appears to be garbage intended to distract you from the errors people actually make, but there is some good stuff in there and I pull out similar examples when I'm trying to convince people not to learn C or C++ just to improve the quality of their greenfield projects.
- kbwt 9y ago> Return pointer to stack object (more common than you might think!) Maybe when you're still learning how to program. A combination of valgrind/sanitizers will catch all of these mistakes. The same class of mistakes can also be made in "memory-safe" languages, just replace pointer with index and memory with array.
- danieldk 9y agoValgrind will only catch such indexing errors when you read/write out of bounds when running Valgrind. There is a huge difference between out-of-bounds indexing leading to undefined behavior or an exception/panic.
- klodolph 9y agoValgrind won't catch certain indexing errors even if you exercise them. Valgrind only checks that the address is valid, not that the address was derived from a pointer to the object you are accessing. int main() { int x[16]; int y[16]; int z[16]; x[0] = 0; y[0] = 0; z[0] = 0; y[18] = 3; // Valgrind thinks this is OK. return 0; }
- jcelerier 9y agohttps://i.imgur.com/fgufyE1.png https://i.imgur.com/fgufyE1.png
- klodolph 9y agoI think you may have missed the part of the conversation where we were talking about Valgrind, specifically, and not talking about the address sanitizer.
- 2trill2spill 9y agoBut clang complains, it's not about just one tool. test.c:8:9: warning: array index 18 is past the end of the array (which contains 16 elements) [-Warray-bounds] y[18] = 3; // Valgrind thinks this is OK. ^ ~~
- klodolph 9y agoThe snippet is a demonstration of the limitations of Valgrind, specifically. It would be trivial to change the code so it has the same behavior but doesn't trigger the Clang warning.
- jcranmer 9y agoPointers to stack objects escaping the function (not necessarily returning) is actually surprisingly easy to do. Sure, the example "Foo f; return &f;" is the sort of situation that isn't going to happen if you're at all experienced, but it's not hard to build cases. I recently had to debug a crash which turned out to be a stack object unexpectedly escaping. Effectively, the flow is this: void foo(Foo *v) { Operand blah; /* Turns out that there's a use-list on some operands, and this adds &blah to that list in that case. */ copy_operand(&blah, &v->operands[1]); free_foo(v); } > A combination of valgrind/sanitizers will catch all of these mistakes. They will catch only those mistakes that occur when you run them in tests. Plenty of memory safety CVEs still show up in programs that do aggressive fuzzing under valgrind/sanitizer testing. Or, as one aphorism has it, "testing cannot prove the absence of bugs, only their presence."
- klodolph 9y ago> Maybe when you're still learning how to program. I used to think so, too. From John Carmack (https://twitter.com/ID_AA_Carmack/status/587077680652230656 https://twitter.com/ID_AA_Carmack/status/587077680652230656) > Found two pointer-to-out-of-scope-stack bugs today. I like tight native code, but C/C++ still makes me worry a lot. And then there's this dubious claim, > A combination of valgrind/sanitizers will catch all of these mistakes. Nope! 1) You have to execute the right code paths before Valgrind or any of the sanitizers will catch your use of a pointer to stack. In relatively simple cases, you might not catch the error even if you have 100% code coverage. 2) Not all platforms have Valgrind or sanitizers working on them, in fact, most don't. > The same class of mistakes can also be made in "memory-safe" languages, just replace pointer with index and memory with array. Sure, you could write an x86 interpreter in Java, and I'm sure that somebody has already done this. But these errors have much more severe consequences in C.
- Jweb_Guru 9y agoDon't use string_view then. Seriously, if people actually programmed C and C++ the way people claim they do, it wouldn't be any more efficient than Java most of the time.
- jstimpfle 9y ago> - Pointer to array outliving invalidation of array Another problem that can be largely avoided with a clear architecture. 1. The vast majority of buffers should be allocated at program startup, stored in a global variable, and freed at the end. 2. In almost all other cases, pointer ownership should just not be moved across functions (mostly "initializer functions" which return allocated memory). I think the idea that a function takes a data pointer without any idea how it was created, and then stores the pointer somewhere in its own structures, comes from garbage-collected languages and OOP culture which has an (I think) irrational aversion to globally architected dataflow. The messy object graphs that are created in this culture are just not manageable without a GC or another mechanically enforced discipline. I'm not a fan of neither C++, nor Rust, nor garbage collected languages, for complex tasks. One can get away with less expertly architected programs in these languages. But, IMHO, they don't support, or do even impede, clear architecture, which ultimately leads to complexity. And this does mean hard to maintain and buggy software - it's just more memory-safe.
- klodolph 9y agoNow the only problem is in communicating and enforcing that architecture, hiring people who understand how important this architecture is or training them, adding these architectural checks to your code review process, having discussions about which cases are exceptions, and figuring out what to do with legacy code.
- jstimpfle 9y agoLegacy code is reduced to a fraction with good architecture, though. Well architected programs are a lot shorter and a lot more straightforward. And I don't really think you can make clear rules or exceptions - so no general discussions needed. Just don't admit data flow designs that aren't completely thought through (and really simple). Yes, it takes experienced developers.
- klodolph 9y agoYou can point out that the ancient Egyptians built the pyramids just fine, but I'm still going to argue in favor of mechanization. In my experience, good architecture does not materialize often enough to make this a viable strategy except for certain specialized teams. Consider writing code in C when you don't completely understand the problem and haven't devised a solution yet, or when you know it's going to get assigned to a junior developer and you don't have spare time for extra oversight. Or consider that you might ship poorly architected code because it's functional and shipping it will earn you money, or you might inherit large tracts of legacy code that are too expensive to refactor. We all love ourselves some good architecture in our programs, but I'd rather have the penalty for bad architecture be "fails to compile" or "assertion error at runtime" rather than "our product has a security vulnerability". Just because the problem can be solved by throwing better, more experienced developers at it doesn't mean that we should. Let's throw the experienced developers at the more exciting problems and let our tools figure out memory safety for us... at least, most of the time. > Legacy code is reduced to a fraction with good architecture, though. I honestly don't understand this claim at all. I've only worked at one company where I didn't have to work with legacy code. The amount of legacy code I've been personally responsible for has varied from ~20 kloc (fairly manageable) to ~1 Mloc. I don't know what kind of good architecture could reduce that 1 Mloc. I ended up just throwing the address sanitizer at it, working on the test suite, and prioritizing refactors based on expected cost and payoff.