![]() |
![]() |
|
In the course of writing our videogame, we (hopefully) learned:
Compare move-truck with an imperative version that just re-assigns to a field. This requires much more memory (allocate a whole new truck), and more time (we have to copy all the fields over, rather than just assign to one field). For a struct with 20 fields, this is a slowdown of perhaps 20x, plus more time for memory allocation and garbage collection.
Certainly, this seems expensive, but a we can also make the following observations:
You can take your video-game assignment and scale up the number of objects, and see how it plays. (Any lagginess in the assignment is usually based on only moving 1 pixel per keypress, not actual CPU run-time! That said, the image-library is not designed for time-efficiency.)
In real life, rather than fear or suspect that an approach won’t scale, I encourage you to implement something both ways and actually measure the difference. The slower approach might end up being too slow, but then again maybe it’s not a bottleneck, and you have saved dozens of development & debugging hours by avoiding useless optimization. (And even if not useless: how many times must your program run milliseconds faster, just to recoup the extra hours and hours of development time and cost?)
to do in class:: Draw memory-diagram of sharing in lists, and trees.
Though this also is precisely why such code is harder to reason about — it’s unclear what changes in distant code might change this procedure’s behavior. If you hear people talk about “dependency injection”, they mean “pass in extra arguments (like database-connections), rather than using global state”; people feel this is a win even in imperative programming, which suggests this negative is not so big after all.
One small example:
int a = foo(); // this might be a long/complicated expression. if (someCondition) return a; else return -1; |
No == vs. .equals subtleties to worry about — in absence of mutation, you tend to only care about objects having equal fields. (Think Java’s String — equals is almost certainly the comparison you care about.)
see also: Oracle's Secure Coding guidelines have a large section on pitfalls of mutation:Mutability, whilst appearing innocuous, can cause a surprising variety of security problems.
Mutability can become a security hole. (We'll assume we're using Java, for the moment.) Imagine the following:
In an operating system, you certainly want to know who all your users are — String[] usernames = { ... };. And of course you wouldn't make this a public-field/variable, because you don't want other code to assign to it! But you may want to let other people know all the users, so it might be plausible that you'd include a getter, String[] getUsernames() { return usernames; }.
What's the problem? Aliasing + Mutability! An attacker can go ahead:
String[] myCopyOfUsers = getUsernames(); myCopyOfUsers[2] = "hax0r"; |
Secure Programming principle: Don't give references to your own mutable data-structures, to untrusted code! Instead, make a copy.So we'll change our function to be String[] getUsernames() { return Arrays.copy(usernames); }. Now an attacker can get a copy of the array, and if they assign into the array that's fine, they're only assigning to their own copy.
But our problems may not be over yet! If we are in a language like C (or many others), strings are mutable. So an attacker can still weasel their way into the system's trust:
String[] theUsers = getUsernames(); getUserNames[2][0] = 'h'; getUserNames[2][1] = 'a'; getUserNames[2][2] = 'x'; getUserNames[2][3] = '0'; getUserNames[2][4] = 'r'; getUserNames[2][5] = '\0'; |
Secure Programming principle, cont.: Remember to make a deep copy of all mutable fields!Note that Java is not susceptible to this latter attack. Why not? Because Java's Strings are immutable (yay!).
This page licensed CC-BY 4.0 Ian Barland Page last generated | Please mail any suggestions (incl. typos, broken links) to ibarland ![]() |