6 ms·
Those things help but it's still possible to return a pointer to an inner part of an allocation(say an element in a vector) and then have the vector go out of s
by vvanders 10y ago
Those things help but it's still possible to return a pointer to an inner part of an allocation(say an element in a vector) and then have the vector go out of scope.
Rust is much more robust about catching those cases and verifying that you don't have objects outliving their references.
- geezerjay 10y ago> Those things help but it's still possible to return a pointer to an inner part of an allocation(say an element in a vector) and then have the vector go out of scope. That's something that the programmer needs to specifically ask for, and I'm not aware of any C++ compiler which doesn't throw a bunch of warnings regarding an object's scope and lifetime. That means your point boils down to arguing that if a programmer really wants to, he can still shoot himself in the proverbial foot by writing C++ code, but in order for that to happen he needs to have a death wish and intentionally avoid all warnings and safe practices to begin with.
- catnaroek 10y agoI don't see any C++ compilers raising warnings because you use shared mutable objects without grabbing the right lock first.
- MaulingMonkey 10y agoCheck out https://clang.llvm.org/docs/ThreadSafetyAnalysis.html https://clang.llvm.org/docs/ThreadSafetyAnalysis.html Requires manual annotation and isn't nearly as systematic and comprehensive as the Rust compiler, but I've found threading bugs with this in existing code that I was about to modify.
- catnaroek 10y agoAh, this is very nice.
- vvanders 10y agoI've seen variations of the below in just about every codebase I've worked in. Just ran it through GCC, Clang and MSCV(W4) with no warnings ;). #include <vector> int main() { struct Foo { int bar; }; Foo* bar; { std::vector<Foo> foos; foos.emplace_back(Foo{1}); foos.emplace_back(Foo{2}); bar = &foos[1]; } bar->bar = 2; }
- Const-me 10y agoTrue, found similar bugs in my own C++ code, and I read and fix all warnings. However, I never spent more than 10 minutes fixing that. Debug version of MS C runtime fills freed heap memory with a magic number 0xFEEEFEEE. You’ll get exception in runtime, and you’ll immediately have the idea what’s wrong with the code.
- vvanders 10y agoYeah, this is a trivial example. It becomes trickier when you start dealing with things across library boundaries where you might not have source code. I've seen 5 minute fixes and things that took weeks to track down(yay for non deterministic repros).
- pjmlp 10y agoBack when I was doing C coding in the early 2000's I have spent around one week tracking down a server crash caused by a bad pointer. Technical support guys were not that happy with the customer regularly calling them to know when the fix would be delivered. Have other war stories about tracking down leaks as well.
- Const-me 10y agoIt’s hard to debug a library without the source code. This is irrelevant to what language was that source code in, C++ or Rust.
- 10y ago
- tomjakubowski 10y agoLet me share a sanitized C++ memory corruption story. We were testing our application the night before a big demo for our client. Ours was a C++ OpenGL application with several "phases" the user could enter from a "main menu" type screen. Our demo explored these phases in a pre-planned order. All went well, until somebody decided to try doing the demo in a slightly different order. Once the tester visited a certain phase of the application, the thing crashed and burned. The stack trace was littered with some incomprehensible container-implementation-detail junk. Debuggers weren't pointing at an obvious sign, other than that something was dereferencing a garbage pointer. We noticed that if we went back to the original canned order of operations (this was a demo, after all), the application didn't crash. But this still made us nervous. We found that we could sometimes swap a few phases early in the application's run with no ill effect. Here's part of the class responsible for some of the higher layers of the stack trace, from one of the lower level libraries we build all of our applications on (this is important because, for hysterical raisins, we can't source-step debug those libraries in production). Can you spot the bug? #include <map> #include <string> #include <vector> struct Message; class MessageCentral { public: void HandleMessages() { // messages were collected somewhere and organized by their queue. const std::vector<std::string> queues_with_new_messages; for (const std::string &queue : queues_with_new_messages) { const std::vector<Message> msgs; // = messages_for_queue(queue); const auto &handlers = handlers_[queue]; for (auto &h : handlers) { for (const auto &msg : msgs) { h(msg); } } } } void RegisterMessageHandler(const std::string &queue_name, std::function<void(Message)> handler) { handlers_[queue_name].push_back(handler); } private: std::map<std::string, std::vector<std::function<void(Message)>>> handlers_; }; (Note: this is probably way easier to figure out with all of the other code needed to implement MessageCentral taken out! It took us three engineers working for about 2.5-3 hours each, with lots and lots of gdb and printf debugging, to nail this one down. This particular crash would have been impossible in Rust, because the compiler would have rejected the Rust equivalent of the above C++ code.) (oh, by the way, we couldn't use runtime memory sanitizer tools like ASan or valgrind because the application linked to CEF. In retrospect, we could have dropped in a fake CEF that didn't interfere with ASan or valgrind, but that would have required build resources we didn't have available in the frenzied night before a big demo.)
- pcwalton 10y ago> That's something that the programmer needs to specifically ask for No, it's not. Use-after-free is very easy. Just push onto a vector while you're iterating over it, to name one obvious example that comes up a lot.