3 ms·
> I had to switch to a more powerful board though And therein lies the rub. If you can throw more computational power and resources at your problem, then you s
by okamiueru 5y ago
> I had to switch to a more powerful board though
And therein lies the rub. If you can throw more computational power and resources at your problem, then you should absolutely go for something with simpler abstractions that make better use of your time.
The problem is however not with C or C++, but rather inexperience in how to manage the control/flexibility these languages allow. You now have 50 ways to shoot yourself in the foot, instead of the otherwise handful. And, you also get the choice of a few bazookas.
> I have no idea how people write whole browsers and operating systems in this language.
They use best practices that avoid all of the aforementioned 50 ways.
- klohto 5y agoOh, look! Another comment about how C is great and it’s the user’s fault. Yea, no shit Sherlock. This is the reason why we have Rust now and what’s possible is migrated into it.
- nikitaga 5y agoCan confirm, foot gunned myself over and over even though I'm not doing anything complicated. Couldn't figure out the last memory corruption trap I fell into, had to abandon ship. I'm honestly surprised how well regarded Arduino software stack is given that its target audience is not exactly seasoned C++ developers. I guess most casuals have very simple programs, or just go straight for micropython / circuitpython.
- nyanpasu64 5y agoC++ memory management is hard, and best practices don't solve all the problems you encounter. (Note that I have more experience with desktop programs, like "browsers and operating systems", than embedded boards.) Managing tree-shaped allocation trees is possible but mistake-prone, and Rust includes a sound checker to catch mistakes. (Rust pushes users towards using "writable xor aliased" pointers which is integral to this sound checker. "Writable xor aliased" is one of Rust's biggest strengths and weaknesses, and differs from both C++ and GC'd languages.) Managing intrusive linked lists is common in C (I have less C experience than C++) and probably tractable as long as you don't carry around pointers which may dangle. Rust doesn't help write correct intrusive code, and worse yet Stacked Borrows (a proposed set of rules for pointer aliasing) interferes with attempts to write intrusive collections in unsafe code (https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a42462 https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a...). Maintaining intrusive sorted-tree maps and such is probably possible in C, but perhaps even trickier to get right (note that I've never built one myself). I try to avoid aliased object graphs (multiple objects hold pointers to another object) when architecting greenfield projects, and it's often (but not always) possible. When I encounter pointer aliasing anyway (often in existing codebases), I usually fall back to shared_ptr (reference counting) because manual memory management requires borderline-intractable global reasoning to make sure every possible codepath never uses an aliasing pointer after the object has been freed through another pointer. (This is especially difficult on code I'm not wholly familiar with, because I either didn't build it myself or forgot the details.) And if you have reference cycles, shared_ptr leaks memory, so you need to manually clear shared_ptr on destruction (error-prone and risks leaking), use weak_ptr (runtime overhead and boilerplate on every read, not just creation/destruction), or use raw pointers (error-prone and risks memory unsafety). Rust doesn't help you with reference cycles either, sadly (Rust makes mutating shared references painful, Rc leaks memory, and Weak has runtime overhead and boilerplate, but at least Rc/Weak aren't atomic like shared_ptr). A garbage collector makes your life a lot easier, at the cost of runtime pauses (often higher throughput than refcounting, but slowdown occurs in bursts). ---- C++ is intractable to write correct threaded code in. The language doesn't tell you whether a T& is visible from one or multiple threads (which determines whether you need to acquire a mutex to access it). And if 1 object has methods A and B both exposed through a public API (accessible from multiple threads), but method A (which acquires a mutex) calls method B, then method B must acquire the mutex when called from outside, but cannot acquire the mutex when called by method A unless it's a recursive mutex. The alternative is to make neither A nor B acquire the mutex, and require callers to acquire the mutex beforehand (which they may forget to do). I've written more about this at https://write.as/nyanpasu64/c-mutexes-are-easy-to-misuse-and-terrifying-to-properly-use https://write.as/nyanpasu64/c-mutexes-are-easy-to-misuse-and.... I honestly don't think Python and Java are that much more tractable to write correct threaded code (note that Python has a global interpreter lock, and I have limited experience with Java threading). Neither distinguishes references to an object visible to one thread, from references to an object visible to multiple threads. And neither can force users to acquire a mutex before accessing an object visible to multiple threads, though Java's synchronized methods use recursive locks which can avoid the method A/B problem above. Also the consequences for threading errors are slightly less severe (corrupted data and exceptions instead of segfaults). Rust is tractable to write correct threaded code. Its type system distinguishes &T (sometimes shared between threads, usually read-only), &Mutex<T> (cannot access T without taking a lock), and MutexGuard<T> and &mut T (obtained when you acquire a lock, provides unique access to T while you hold the mutex). This ensures you'll never forget to acquire a mutex, and have to go out of your way to try to acquire a second lock to a mutex you already own.