My understanding of the history of it is that it's based on two main points...
Firstly, the language authors preferred to make the syntax variable-centric rather than type-centric. That is, they wanted a programmer to look at the declaration and think "if I write the expression *func(arg)
, that'll result in an int
; if I write *arg[N]
I'll have a float" rather than "func
must be a pointer to a function taking this and returning that".
The C entry on Wikipedia claims that:
Ritchie's idea was to declare identifiers in contexts resembling their use: "declaration reflects use".
...citing p122 of K&R2 which, alas, I don't have to hand to find the extended quote for you.
Secondly it is actually really, really difficult to come up with a syntax for declaration that is consistent when you're dealing with arbitrary levels of indirection. Your example might work well for expressing the type you thought up off-the-bat there, but does it scale to a function taking a pointer to an array of those types, and returning some other hideous mess? (Maybe it does, but did you check? Can you prove it?).
Remember, part of C's success is due to the fact that compilers were written for many different platforms, and so it might have been better to ignore some degree of readability for the sake of making compilers easier to write.
Having said that, I'm not an expert in language grammar or compiler writing. But I know enough to know there's a lot to know ;)
Who said the compiler will reserve any space (could be register only).
This is completely undefined.
All that you can say is that it (x
) can only be accessed from inside the inner block.
How the compiler allocates memory (on a stack if it even exists) is completely upto the compiler (as the memory region may be re-used for multiple objects (if the compiler can prove that their lifespans do not overlap)).
Is the space for x reserved on the stack immediately when func is entered
Undetermined.
or only if the block is actually executed?
Undetermined.
But if x
was a class object then the constructor will only be run if the block is entered.
Or is it the compiler's choice?
The compiler may not even allocate memory.
Do C and C++ behave the same about this?
Yes
Best Answer
TL;DR: It's not an issue of block scoping, but rather a misunderstanding of how pointers should be handled.
Courtesy of the vulnerability listing that you posted, let's take a look at the code in question.
And if we delve into the declaration of
BN_bn2dec
we see a comment of:What you see as a block scoping issue, I see as a poor understanding of pointer operations as the root issue.
This diff revision shows when the
*bndec
was moved from inside the while loop to outside the while loop. The error was also introduced at the same time when thefree(bndec)
statement was moved outside the loop.Where
*bndec
is declared for this function is a bit irrelevant. It can safely be defined either outside or inside the while loop. And if memory serves correctly, the actual declaration will be moved to the top of the function by the compiler anyway.Declaring
*bndec
inside the loop merely would have forced an error if thefree()
call remained outside of the loop. It remains to be seen whether that error would have been sufficient to alert the programmer to the fallacy behind their refactoring logic.Being a bit of a pessimist when it comes to most programmers and pointers, I'm going to posit that the
free
accessing an out-of-scope variable error wouldn't have helped them discover the error of their ways. Instead, they would have taken the naive (and incorrect) solution of resolving the error by merely moving the variable declaration outside of the block. And that's why I disagree with the contention that the root cause of this is a block scoping issue.If C happened to have garbage collection, then block scoping would have been of more value in this case. But it doesn't, and that line of thought becomes pointless speculation.
Unfortunately, we don't really know why the refactoring programmer chose to move that
free(bndec)
statement. All that they left via check-in comments was this:And while I respect their dedication to clean code, I'm not certain I fully agree with their removing of various checks from the code. Having written a fair amount of C code with a fairly large team of developers, my experience is that defensive coding practices are almost always warranted. And while a developer may not be able to justify the time expense to add those defensive practices, I can't say that I have ever found valid grounds to remove those defenses.
To delve into an equal amount of hyperbole as the refactoring programmer, I find their check-in comment to smack a little bit too much of hubris and not enough of humility. That hubris is likely what led them to assume they "knew what they were doing" (TM) in moving the
free(bndec)
statement, and they didn't mentally walk through what that was going to do to the code.This error was introduced with the refactoring not because of the block scoping, but because of the failure to understand how pointers operate and the failure to understand how pointers relate to memory.
While I think I have established why block scoping is irrelevant to this issue, I want to address what you see as
"great resistance to block scoped variables within old-school projects"
. And to my knowledge, ANSI C has always had block scoping available for variables. Going out on a limb, I suspect K&R C also had it.Keep in mind that C is not a gentle language for the naive or the initiate. It was created in order to provide an additional layer of abstraction over
B
and assembly level programming. Programmers writing in C were still expected to have an understanding of the underlying assembler that was being generated. For example, variables are not automatically initialized, and many a programmer has been burned by that lack of initialization. C expects the programmer to understand what that variable declaration maps to in memory. C also has more than its fair share of unwritten style rules that are considered to be "good form."Moving all variable declarations is one of those unwritten style rules, and the pragmatic reason goes back to the lack of automatic initialization. Disciplined C programmers declared all of their variables at the beginning of the function so they could make a quick visual scan to verify that all declared variables had been initialized. It's an easy way to make sure that a common mistake (e.g. lack of initialization) has been avoided.