Is declaration of variables expensive?

前端 未结 12 964
长情又很酷
长情又很酷 2021-02-01 00:39

While coding in C, I came across the below situation.

int function ()
{
  if (!somecondition) return false;

  internalStructure  *str1;
  internalStructure *str         


        
12条回答
  •  执笔经年
    2021-02-01 00:56

    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.

    C code

    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;
    }
    

    Resulting assembly

    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

    C code

    int function(int a, int b) {
      if(a == 2) { return 15; }
      return a + b;
    }
    

    Resulting assembly

    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.

提交回复
热议问题