Hacker News new | ask | show | jobs
by optymizer1 4558 days ago
I'm not completely on board with #4 (initialization of variables). I agree that 'declaring and then assigning a value' _within the same block_ vs 'initialized declaration' has no speed gains, only downsides.

However, if the assignment can happen in a _different block_ (maybe inside an 'if' block) you could save 1 memory write, depending on how many times the if condition is satisfied.

This obviously optimizes for speed at the expense of maintainability, and it's for the programmer to make intelligent trade offs, but the fact is that one method is faster than the other.

Silly example that decrements 'a' repeatedly if condition is non-zero:

   //temp = 0 inside if block
   int dec(int a, int condition) {
       int temp;

       if (condition) {
           temp = 0;  //<-- memory write depends on condition

           //compute something here in a loop
           while (temp < a) {
               a -= (temp++);
           }
       }

       return a;
   }

   //temp = 0 at declaration
   int dec2(int a, int condition) {
       int temp = 0; // <-- memory write always executed

       if (condition) {
           //compute something here in a loop
           while (temp < a) {
               a -= (temp++);
           }
       }

       return a;
   }

We can disassemble the output to verify that dec() will only write to memory if the condition was satisfied (see 400458), while dec2() will always write into temp (see 400482):

    # <dec>
    400448:  push   %rbp
    400449:  mov    %rsp,%rbp
    40044c:  mov    %edi,0xffec(%rbp)
    40044f:  mov    %esi,0xffe8(%rbp)
    400452:  cmpl   $0x0,0xffe8(%rbp)  # if (condition)
    400456:  je     400473 <dec+0x2b>
    400458:  movl   $0x0,0xfffc(%rbp)  # temp = 0 <-- depends on condition
    40045f:  jmp    40046b <dec+0x23>  # while
    400461:  mov    0xfffc(%rbp),%eax
    400464:  sub    %eax,0xffec(%rbp)
    400467:  addl   $0x1,0xfffc(%rbp)
    40046b:  mov    0xfffc(%rbp),%eax
    40046e:  cmp    0xffec(%rbp),%eax
    400471:  jl     400461 <dec+0x19>  # loop
    400473:  mov    0xffec(%rbp),%eax  # return a
    400476:  leaveq
    400477:  retq

    # <dec2>
    400478:  push   %rbp
    400479:  mov    %rsp,%rbp
    40047c:  mov    %edi,0xffec(%rbp)   
    40047f:  mov    %esi,0xffe8(%rbp)
    400482:  movl   $0x0,0xfffc(%rbp)   # temp = 0 <-- always executed
    400489:  cmpl   $0x0,0xffe8(%rbp)   # if (condition)
    40048d:  je     4004a3 <dec2+0x2b>
    40048f:  jmp    40049b <dec2+0x23>  # while
    400491:  mov    0xfffc(%rbp),%eax
    400494:  sub    %eax,0xffec(%rbp)
    400497:  addl   $0x1,0xfffc(%rbp)
    40049b:  mov    0xfffc(%rbp),%eax
    40049e:  cmp    0xffec(%rbp),%eax
    4004a1:  jl     400491 <dec2+0x19>  # loop
    4004a3:  mov    0xffec(%rbp),%eax   # return a
    4004a6:  leaveq 
    4004a7:  retq
The above code was compiled with gcc 4.1.2 on amd64/linux. gcc -O2 and gcc -O3 completely do away with the 'temp' variable and generate fewer instructions.
2 comments

I'd suggest that, if a variable with an undefined value is a valid state for the program, the declaration is in the wrong place. How about this alternative?

   int dec(int a, int condition) {
       if (condition) {
           int temp = 0;

           //compute something here in a loop
           while (temp < a) {
               a -= (temp++);
           }
       }

       return a;
   }
That's the obvious next step, but it's not valid C89 and that may or may not be relevant for people doing embedded programming. Personally, I'm all for using newer standards and declaring variables locally when it makes sense. Sometimes I like to see all variables at the top of the function though.
I'm not particularly familiar with x86-64 calling conventions. Where is the code that sets up the stack frame? Would this code look different if temp were created inside the if() block, not just initialized there?
In C, variables have function-level storage. I don't recall the actual term from the C standard, but the storage for _all_ variables declared in any blocks inside a function is allocated at the beginning of the function, when the stack frame is set up. So there is 'no way' to create 'temp' only inside the if block.
Okay. I knew variables had block-level scope. I've read some of the ISO C standard, but hadn't caught the part where variables have function-level storage. I was wondering because, years ago, I read some disassembled code that did lots of stack allocations in the middle of functions, and not just for the purpose of calling another function.

Can you point to a reference showing that variables have function-level storage? The closest I can find in the C standard draft I'm looking at is 6.2.4.6, which suggests block-level lifetimes for variables: "For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way."