Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

return a auto value in if block?

Tags:

c

#include <stdio.h>

static int test(int val)
{
    int *ptr;

    if(val == 0)
    {
        int val = 4;
        ptr = &val;
    }

    return (*ptr + 1);
}

int main(void)
{
    int i = test(0);
    printf("%d\n", i);
    return 0;
}

In the above code, the variable val in if block is destroyed, so in return (*ptr + 1) , the value *ptr should be undefined, but result of this program is 5.

I know this is a undefined program, but it seems that it produces a expected value, why?

like image 973
Charles0429 Avatar asked Nov 23 '22 05:11

Charles0429


1 Answers

As already stated in the comments, it is undefined behaviour - so anything can happen.

However, technically, the reason is that the stack frame is not changed after leaving the if block, and that the compiler allocates all required local variables for the whole function at the beginning of the function, instead of creating a new stack frame for each scope. You can see this in the assembly code which is produced by your function:

ZL4testi:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp          ; set up base pointer for stack frame
                                    ; NOTE: The stack pointer is not modified here
                                    ; since you are not calling any other function
                                    ; from within `test`
        .cfi_def_cfa_register 6
        movl    %edi, -20(%rbp)     ; parameter was passed in %edi, store it in the frame

; if (parameter val == 0)
        cmpl    $0, -20(%rbp)       
        jne     .L2

; Here the scope of the `if` block starts - no changes to the stack frame setup!
; {
;    int val = 4
        movl    $4, -4(%rbp)        ; val is at -4(%rbp)

;    ptr = &val;
        leaq    -4(%rbp), %rax      ; get address of val into %eax
        movq    %rax, -16(%rbp)     ; store address of val into ptr
; }
.L2:
        movq    -16(%rbp), %rax     ; Here, ptr is still containing the address 
                                    ; of val within the stack frame
        movl    (%rax), %eax        ; load `val` from the stack even though it is out of scope
        addl    $1, %eax

        popq    %rbp
        .cfi_def_cfa 7, 8
        ret

Throughout the whole function, the stack frame layout is

-20(%rbp)  => parameter val
-16(%rbp)  => ptr
 -4(%rbp)  => variable val inside the `if` block

Note that nothing prevents the compiler from reusing -4(%rbp) if you declare a new variable inside another scope at a later point inside your function:

static int test(int val) {
    int *ptr;

    if(val == 0) {
        int val = 4;
        ptr = &val;
    }
    if(val == 0) {
        int otherval = 6;
        ptr = &otherval;
    }
    return (*ptr + 1);
}

If you compare the previous assembly output with the one which is generated with the additional block in place, the only difference are these additional lines:

    cmpl    $0, -20(%rbp)
    jne .L3

    movl    $6, -4(%rbp)      ; here, -4(%rbp) is reused for otherval
    leaq    -4(%rbp), %rax
    movq    %rax, -16(%rbp)
.L3:
like image 192
Andreas Fester Avatar answered Jan 25 '23 23:01

Andreas Fester