Hacker News new | ask | show | jobs
by atilaneves 2212 days ago
> Source code is for humans

The reason why making the scope as local as possible is important is for humans, not compilers.

> Put all the vars at the top to tell the humans "here's all the scratch space I'll be needing in this block"

Why would humans care about how much scratch space is needed? That's for the compiler to know.

2 comments

We're not talking about scope. The scope is already decided. "Vars at the top of the block" doesn't mean "take those extra vars out of the while{} scope and elevate them to the next enclosing scope."

You've taken my "scratch space" too literally. Very few people need to count bytes for local vars. I'm talking about future maintainers reading and understanding code. Grouping the current block's variables at the top says nothing about how the compiler might organize the resulting code and storage. But it does inform future readers of the code.

The scope of a variable is from where it is declared to the end of the block. Moving a variable to the top of the enclosing block means that it can be referenced from more places in the code, which increases its scope.

Warnings about uninitialized variables help, but don't catch everything. For example, you don't usually get a warning for passing the address of an uninitialized variable to an external function (since it might be an output parameter), but that would be undefined behavior if the function expects the variable to be initialized. Initializing variables at the point where they are declared ensures that they can't be referenced at all in an uninitialized state.

Rust has a slightly more nuanced (and IMHO superior) system: non-mutable ("const") variables can be assigned exactly once, possibly but not necessarily at the point where they are declared, and all variables must be initialized before use, including passing references to other functions. This permits more flexibility in how the code is arranged while simultaneously offering stronger guarantees against undefined or otherwise erroneous behavior.

> Why would humans care about how much scratch space is needed?

In some contexts, it's important. For instance, each thread within the Linux kernel has a very limited fixed-size stack space (used to be 4K bytes, IIRC it's been increased to 8K and then 16K), which resides in physical memory (cannot be swapped out or lazily allocated). Avoiding large stack frames is necessary.

1. That's a rare use case 2. There are ways of measuring that without hampering readability. I mean, are programmers supposed to add all the variables' sizes up in their head??
That wouldn't work anyway. You could rewrite code to use fewer variables without changing the resulting assembly.

    i = get_thing();
    j = do_stuff(i);
vs

    j = do_stuff(get_thing());
Also, unless your thread has only a single function that has local variables or arguments and that function doesn’t contain sub blocks that declare variables (say inside a while block), All variable declarations at top of block doesn’t help much in gauging stack space usage of a thread.

Actually, not even that helps, you would also have to know how much stack a function call takes (might be non-trivial in the presence of stack alignment rules), and which functions get inlined. Edit: if you declare all your locals at the start of a function, chances are the compiler will check whether it can make some of them share memory, so you’d have to take that into account, too.

If you’re concerned about stack overflows in your threading code, it is tooling is what you need, not manually counting stack usage.

> if you declare all your locals at the start of a function, chances are the compiler will check whether it can make some of them share memory, so you’d have to take that into account, too.

And that's ignoring registers. Not every local ever needs to reside in memory.