zell
zell

Reputation: 10204

Understanding an unexpected result due to an unmatched prototype (C89)

I have a program goo.c

void foo(double);

#include <stdio.h>
void foo(int x){
  printf ("in foo.c:: x= %d\n",x);
}

which is called by foo.c

int main(){
  double x=3.0;
  foo(x);
}

I compile and run

 gcc foo.c goo.c 
 ./a.out

Guess what? I get "x= 1" as result. Then I find the signature of 'foo' should have been void foo(int). Apparently, my double input value 3.0 has to be downcast to an int. But, if I try to see the value of (int) 3.0 with the test program:

int main(){
  double x=3.0;
  printf ("%d", ((int) x));
} 

I get 3 as output, which makes the earlier ` x= 1' even more hard to understand. Any idea? For information, my gcc is run with ANSI C standard. Thanks.

[EDIT] If I use gcc -S as suggested by JS1,

I get goo.s

.file   "goo.c"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $32, %rsp
    movabsq $4613937818241073152, %rax
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    %rax, -24(%rbp)
    movsd   -24(%rbp), %xmm0
    call    foo
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits

and foo.s

    .file   "foo.c"
    .section    .rodata
.LC0:
    .string "in foo.c:: x= %d\n"
    .text
    .globl  foo
    .type   foo, @function
foo:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movl    -4(%rbp), %eax
    movl    %eax, %esi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   foo, .-foo
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits

Anyone who knows how to read Assembly can help figure out the source problem?

Upvotes: 0

Views: 58

Answers (1)

Frederik Deweerdt
Frederik Deweerdt

Reputation: 5271

Understanding why you get '1' requires a bit of ASM and x86-64 ABI knowledge. First of all, goo.c and foo.c are two separate compilation units. The only thing that foo.c knows about the foo function is the bogus prototype.

The bogus prototype is as follows: void foo(double);. It's a function that takes only a single double argument. The x86-64 ABI mandates that the doubles are passed through the xmm registers (The exact phrasing is 'If the class is SSE, the next available vector register is used, the registers are taken in the order from %xmm0 to %xmm7.'.

That means that when the compiler sets up the arguments to call the foo() function, it's going to pass the argument via %xmm0. In simplified asm what happens is:

mov 3.0, %xmm0
call foo

Now, foo(), on it's side, believes it's going to recieve an int. The x86-64 ABI says: 'If the class is INTEGER, the next available register of the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9 is used.'. The first argument is supposed to be passed via %rdi. That means that foo() will do something like:

mov %rdi, %rsi
mov 0xabcd, %rdi // 0xabcd being the address of the "%d" string
call printf

So you're going to end up printing whatever was in %rsi, and not %xmm0.

But why 1? You'll get an idea by issuing the following commands: ./a.out a ./a.out a b ./a.out a b c

See a pattern? Let's go back to the simplified assembly:

main:
    mov 3.0, %xmm0
    call foo
    ret

foo:
    mov %rdi, %rsi
    mov 0xabcd, %rdi // 0xabcd being the address of the "%d" string
    call printf
    ret

As you can see, nothing is setting %rdi until it reaches foo(), where it's passed on to printf. Which means 1 was passed to main in the first place. Now, in the question, main is given the following prototype: int main(). But the compiler actually setup the function to have the following prototype instead: int main (int argc, char *argv[], char *envp[]). The first argument, thus stored in %rdi, is actually argc. That's why the program was printing 1.

Upvotes: 2

Related Questions