While coding in C, I came across the below situation.
int function ()
{
if (!somecondition) return false;
internalStructure *str1;
internalStructure *str
Whenever you allocate local variables in a C scope (such as a functions), they have no default initialization code (such as C++ constructors). And since they're not dynamically allocated (they're just uninitialized pointers), no additional (and potentially expensive) functions need to be invoked (e.g. malloc
) in order to prepare/allocate them.
Due to the way the stack works, allocating a stack variable simply means decrementing the stack pointer (i.e. increasing the stack size, because on most architectures, it grows downwards) in order to make room for it. From the CPU's perspective, this means executing a simple SUB instruction: SUB rsp, 4
(in case your variable is 4 bytes large--such as a regular 32-bit integer).
Moreover, when you declare multiple variables, your compiler is smart enough to actually group them together into one large SUB rsp, XX
instruction, where XX
is the total size of a scope's local variables. In theory. In practice, something a little different happens.
In situations like these, I find GCC explorer to be an invaluable tool when it comes to finding out (with tremendous ease) what happens "under the hood" of the compiler.
So let's take a look at what happens when you actually write a function like this: GCC explorer link.
int function(int a, int b) {
int x, y, z, t;
if(a == 2) { return 15; }
x = 1;
y = 2;
z = 3;
t = 4;
return x + y + z + t + a + b;
}
function(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-20], edi
mov DWORD PTR [rbp-24], esi
cmp DWORD PTR [rbp-20], 2
jne .L2
mov eax, 15
jmp .L3
.L2:
-- snip --
.L3:
pop rbp
ret
As it turns out, GCC is even smarter than that. It doesn't even perform the SUB instruction at all to allocate the local variables. It just (internally) assumes that the space is "occupied", but doesn't add any instructions to update the stack pointer (e.g. SUB rsp, XX
). This means that the stack pointer is not kept up to date but, since in this case no more PUSH
instructions are performed (and no rsp
-relative lookups) after the stack space is used, there's no issue.
Here's an example where no additional variables are declared: http://goo.gl/3TV4hE
int function(int a, int b) {
if(a == 2) { return 15; }
return a + b;
}
function(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
cmp DWORD PTR [rbp-4], 2
jne .L2
mov eax, 15
jmp .L3
.L2:
mov edx, DWORD PTR [rbp-4]
mov eax, DWORD PTR [rbp-8]
add eax, edx
.L3:
pop rbp
ret
If you take a look at the code before the premature return (jmp .L3
, which jumps to the cleanup and return code), no additional instructions are invoked to "prepare" the stack variables. The only difference is that the function parameters a and b, which are stored in the edi
and esi
registers, are loaded onto the stack at a higher address than in the first example ([rbp-4]
and [rbp - 8]
). This is because no additional space has been "allocated" for the local variables like in the first example. So, as you can see, the only "overhead" for adding those local variables is a change in a subtraction term (i.e. not even adding an additional subtraction operation).
So, in your case, there is virtually no cost for simply declaring stack variables.
If you declare variables after if statement and returned from the function immediately the compiler does not commitment memory in the stack.
It ultimately depends on the compiler but usually all locals are allocated at the beginning of the function.
However, the cost of allocating local variables is very small as they are put on the stack (or are put in a register after optimization).
I prefer keeping the "early out" condition at the top of the function, in addition to documenting why we are doing it. If we put it after a bunch of variable declarations, someone not familiar with the code could easily miss it, unless they know they have to look for it.
Documenting the "early out" condition alone is not always sufficient, it is better to make it clear in the code as well. Putting the early out condition at the top also makes it easier to keep the document in sync with the code, for instance, if we later decide to remove the early out condition, or to add more such conditions.
If it actually mattered the only way to avoid allocating the variables is likely to be:
int function_unchecked();
int function ()
{
if (!someGlobalValue) return false;
return function_unchecked();
}
int function_unchecked() {
internalStructure *str1;
internalStructure *str2;
char *dataPointer;
float xyz;
/* do something here with the above local variables */
}
But in practice I think you'll find no performance benefit. If anything a minuscule overhead.
Of course if you were coding C++ and some of those local variables had non-trivial constructors you would probably need to place them after the check. But even then I don't think it would help to split the function.
Do whatever makes sense, but current coding style recommends putting variable declarations as close to their usage as possible
In reality, variable declarations are free on virtually every compiler after the first one. This is because virtually all processors manage their stack with a stack pointer (and possibly a frame pointer). For example, consider two functions:
int foo() {
int x;
return 5; // aren't we a silly little function now
}
int bar() {
int x;
int y;
return 5; // still wasting our time...
}
If I were to compile these on a modern compiler (and tell it not to be smart and optimize out my unused local variables), I'd see this (x64 assembly example.. others are similar):
foo:
push ebp
mov ebp, esp
sub esp, 8 ; 1. this is the first line which is different between the two
mov eax, 5 ; this is how we return the value
add esp, 8 ; 2. this is the second line which is different between the two
ret
bar:
push ebp
mov ebp, esp
sub esp, 16 ; 1. this is the first line which is different between the two
mov eax, 5 ; this is how we return the value
add esp, 16 ; 2. this is the second line which is different between the two
ret
Note: both functions have the same number of opcodes!
This is because virtually all compilers will allocate all of the space they need up front (barring fancy things like alloca
which are handled separately). In fact, on x64, it is mandatory that they do so in this efficient manner.
(Edit: As Forss pointed out, the compiler may optimize some of the local variables into registers. More technically, I should be arguing that the first varaible to "spill over" into the stack costs 2 opcodes, and the rest are free)
For the same reasons, compilers will collect all of the local variable declarations, and allocate space for them right up front. C89 requires all declarations to be up-front because it was designed to be a 1 pass compiler. For the C89 compiler to know how much space to allocate, it needed to know all of the variables before emitting the rest of the code. In modern languages, like C99 and C++, compilers are expected to be much smarter than they were back in 1972, so this restriction is relaxed for developer convenience.
Modern coding practices suggest putting the variables close to their usage
This has nothing to do with compilers (which obviously could not care one way or another). It has been found that most human programmers read code better if the variables are put close to where they are used. This is just a style guide, so feel free to disagree with it, but there is a remarkable consensus amongst developers that this is the "right way."
Now for a few corner cases:
alloca
is handled on a layer above this. For those who are curious, alloca
implementations tend to have the effect of moving the stack pointer down some arbitrary amount. Functions using alloca
are required to keep track of this space in one way or another, and make sure the stack pointer gets re-adjusted upwards before leaving.alloca
.