4 ms·
We are talking about new engines written in these languages, though, not multi-decade old codebases still using inline assembler, goto, and pointer arithmetic.
by zanny 13y ago
We are talking about new engines written in these languages, though, not multi-decade old codebases still using inline assembler, goto, and pointer arithmetic.
Modern C++ is really safe if you use the subset that involves automatic storage duration, well bounded arrays, etc and use all the warning flags of your compiler, run static analysis, have a robust test framework, etc.
- pcwalton 13y agoNo, modern C++ is not even close to memory safe. This is my favorite meme to destroy over and over on HN. :) Consider iterator invalidation, null pointer dereference (which is undefined behavior, not a segfault -- and you can't get away from pointers because of "this" and move semantics), dangling references, destruction of the unique owner of the "this" pointer, use after move, etc. etc.
- steveklabnik 13y agoYou should write something up with actual code samples. I would also love to actually demonstrate this to people.
- JoeAltmaier 13y agoExtraordinary claim- please elaborate. I'm working on 100's of thousands of lines of C++ code with a medium-sized team; memory issues are almost non-existent because of disciplines described above.
- pcwalton 13y agoI've described this many times in the past, but here are a few things that modern C++ does nothing to protect against: * Iterator invalidation: if you destroy the contents of a container that you're iterating over, undefined behavior. This has resulted in actual security bugs in Firefox. std::vector v; v.push_back(MyObject); for (auto x : v) { v.clear(); x->whatever(); // UB } * "this" pointer invalidation: if you call a method on an object that is a unique_ptr or shared_ptr holds the only reference to, there are ways for the object to cause the smart pointer holding onto it to let go of it, causing the "this" pointer to go dangling. The simplest way is to have the object be stored in a global variable and to have the method overwrite the contents of that global. std::enable_shared_from_this can fix it, but only if you use it everywhere and use shared_ptr for all your objects that you plan to call methods on. (Nobody does this in practice because the overhead, both syntactic and at runtime, is far too high, and it doesn't help for the STL classes, which don't do this.) class Foo; unique_ptr<Foo> inst; class Foo { public: virtual void f(); void kaboom() { inst = NULL; f(); // UB if this == inst } }; * Dangling references: similar to the above, but with arbitrary references. (To see this, refactor the code above into a static method with an explicit reference parameter: observe that the problem remains.) No references in C++ are actually safe. * Use after move: obvious. Undefined behavior. * Null pointer dereference: contrary to popular belief, null pointer dereference is undefined behavior, not a segfault. This means that the compiler is free to, for example, make you fall off the end of the function if you dereference a null pointer. In practice compilers don't do this, because people dereference null pointers all the time, but they do assume that pointers that have been successfully dereferenced once cannot be null and remove those null checks. The latter optimization has caused at least one vulnerability in the Linux kernel. Why does use after free matter? See the page here: https://www.owasp.org/index.php/Using_freed_memory https://www.owasp.org/index.php/Using_freed_memory In particular, note this: "If the newly allocated data chances to hold a class, in C++ for example, various function pointers may be scattered within the heap data. If one of these function pointers is overwritten with an address to valid shellcode, execution of arbitrary code can be achieved." This happens a lot—not all use-after-free is exploitable, of course, but it happened often enough that all browsers had to start hacking in special allocators to try to reduce the possibility of exploitation of use-after-frees (search for "frame poisoning"). Obligatory disclaimer: these are small code samples. Of course nobody would write exactly these code examples in practice. But we do see these issues in practice a lot when the programs get big and the call chains get deep and suddenly you discover that it's possible to call function foo() in one module from function bar() in another module and foo() stomps all over the container that bar() was iterating over. At this point claiming that C++ is memory safe is the extraordinary claim; C++ is neither memory safe in theory (as these examples show) nor in practice (as the litany of memory safety problems in C++ apps shows).
- JoeAltmaier 13y agoRight; stl sucks. So its work, but you can make ref-safe containers, even thread-safe ones. We do that; we do audio rendering with audio-chain editing on the fly, with no memory issues. It takes care, more care than other languages. But its far from unsolvable.
- dbaupp 13y agoOf course it's possible to write correct C++ code, just like it's possible to write correct assembly code. The point is the extra care required: every piece of code needs to be very carefully authored to ensure it's correct, to avoid the myriad pitfalls.
- JoeAltmaier 13y agoOr you can just trust the language. And if its not right, or not the way you plan to use it, what then? You're stuck unless the language also permits you to roll your own.
- dbaupp 13y agoRust does allow you to implement low-level things in itself, by giving an escape hatch into C/C++-like unsafe code (i.e. risk-of-incorrectness is purely opt-in, rather than always-there). Examples of things efficiently implemented entirely in the standard library in pure Rust (well, with some calls into the operating system/libc): Vec, the std::vector equivalent. Rc, reference counted pointers (statically restricted to a single thread). Arc, thread-safe reference counted pointers. Mutex. Concurrent queues. Hashmap.
- scott_s 13y agoAnd the philosophy of Rust is, what if we encoded that "care" into the language itself? That, to me, is a clear win. It is, to me, good systems language design: codifying decades of hard earned "best practices" into the language semantics itself.
- 13y ago
- bjz_ 13y agoI agree with Steve here - in this instance a catalogue of code examples would be much a great deal compelling than natural language explanations.