2

I have the following program that I am compiling using Microsoft's Visual C++ command line compiler.

#include<stdio.h>

void foo(int, int);

void main(void) {

    foo(5,4);
}

void foo(int a, int b) 
{
    printf("%u\n", sizeof(int));
    printf("%u\n", &a);
    printf("%u\n", &b);
}

When I print out the addresses I get addresses like -:

3799448016
3799448024

The gap is always 8 byes between addresses, while sizeof(int) = 4

There is always an extra 4 byte gap between the parameters (The size of int on my machine is 4 bytes). Why ?

What is the extra 4 bytes of space for ?

I am on a 64-bit Windows 8.1 machine with VS 2013.

ng.newbie
  • 2,807
  • 3
  • 23
  • 57

3 Answers3

1

Well, I cannot check the code produced by VS 2013, but Godbolt does support some CL version; the assembly below is an excerpt of what it generated:

main    PROC
        sub      rsp, 40              ; 00000028H
        mov      edx, 4
        mov      ecx, 5
        call     void __cdecl foo(int,int)
        ...
main ENDP

a$ = 48
b$ = 56
foo PROC
        mov      DWORD PTR [rsp+16], edx
        mov      DWORD PTR [rsp+8], ecx
        sub      rsp, 40              ; 00000028H
        mov      edx, 4
        lea      rcx, OFFSET FLAT:$SG4875
        call     printf
        lea      rdx, QWORD PTR a$[rsp]
        lea      rcx, OFFSET FLAT:$SG4876
        call     printf
        lea      rdx, QWORD PTR b$[rsp]
        lea      rcx, OFFSET FLAT:$SG4877
        call     printf
        add      rsp, 40              ; 00000028H
        ret      0
foo ENDP

First of all, the parameters are not passed on stack, but in registers - first in *CX and second in *DX - as these are 32-bit, they're passed in EDX and ECX .

Thus, the parameters do not have addresses, and would not stored in memory, unless they have their address taken. Since the & operator is used within the function, these now have to be stored on stack. I don't have any good explanation though on why they're stored with a 4-byte gap ([rsp+16] and [rsp+8]) - but they are. **EDIT: Here's the relevant Visual Studio documentation - from Hans Passant's comment - it clearly shows that VS 2013 uses a fixed layout to store the parameters wherever needed - despite their types.

This is unlike my Linux GCC, which would generate code that stores them in adjacent 32-bit locations:

foo:
.LFB1:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR -4[rbp], edi
        mov     DWORD PTR -8[rbp], esi

...

And the conversion specification for printing pointers is %p and for size_t you should use %zu. But since VS 2013 does not have a standards-compliant C compiler, you cannot use z - therefore cast it to unsigned long long and print with %llu or get a C compiler.

  • The reason why I asked this question is because I wanted to know how the `stdarg.h` macros like `va_start` worked. So is there no rhyme or reason how the spacing is done ? – ng.newbie Mar 18 '18 at 17:24
  • @ng.newbie no, and you really *must not* know how the `va_start` worked - they're magic. [And if you really insist - see this QA.](https://stackoverflow.com/questions/12263745/in-c-given-a-variable-list-of-arguments-how-to-build-a-function-call-using-the) – Antti Haapala -- Слава Україні Mar 18 '18 at 17:26
  • @ng.newbie: The facilities in `stdarg.h` do not have to work by addressing arguments where they are actually passed. As the disassembly shows in this case, arguments are normally passed in registers (for the first few arguments, after which the stack must be used). When `stdarg.h` is used, the compiler may move arguments from registers to stack to make it easier to handle them algorithmically as required for `stdarg.h`. So what you see when you take the addresses of arguments or when you use `stdarg.h` is not what happens normally. – Eric Postpischil Mar 18 '18 at 17:27
  • 1
    @AnttiHaapala: `va_start` is not magic, and we ought to promote curiosity and learning, not discourage it or tell people it is unknowable. Somebody has to write `va_start`, somebody has to design ABIs so that `va_start` can work, all programmers have to have at last rudimentary knowledge of how things work so they can recognize then they go wrong and use the clues to deduce what has gone wrong. Please stop promoting ignorance. – Eric Postpischil Mar 18 '18 at 17:29
  • 1
    @EricPostpischil I am not promoting ignorance. If you need to write one, please do inspect it. I am promoting *portability* and part of it is *not* knowing how `va_start` is implemented because you *cannot know* how it is done for the implementation that exists in 10 years from now. – Antti Haapala -- Слава Україні Mar 18 '18 at 17:34
  • I have asked a question about va_start [here](https://stackoverflow.com/questions/49351257/why-does-gccs-va-start-add-instead-of-subtracting) – ng.newbie Mar 18 '18 at 18:09
  • @EricPostpischil see what you did here^. – Antti Haapala -- Слава Україні Mar 18 '18 at 18:11
  • @AnttiHaapala: Promoting portability is saying “This is implementation-dependent” not “they’re magic” and “you cannot know how it will be done in the future” not “you *must not* know.” The former guide a person to goals, the latter turn a person away. – Eric Postpischil Mar 18 '18 at 18:54
0

Because your OS is 64-bit, so addresses are too.

Parameters are passed with push instruction. This instruction pushes operand into stack and decreases esp based on stack frame size. In 64-bit OS, esp is decreased by 8 for next address. In 32-bit OS, you will see 4 byte difference only.

If it was not function argument, you would not have seen this behavior. For example, in the following code you should see 4 byte difference in addresses:

void foo() 
{
    int a = 0;
    int b = 0;
    printf("%u\n", sizeof(int));
    printf("%u\n", &a);
    printf("%u\n", &b);
}

EDIT: When you pass parameters, they are allocated on stack, so this following example:

struct _asd
{
    int a;
    int b;
    char c[6];
};
void foo2(struct _asd a, int b)
{
    printf("%u\n", sizeof(struct _asd));
    printf("%u\n", &a);
    printf("%u\n", &b);
}

In this case, sizeof(struct _asd)==16, so you see a result like this:

16
3754948864
3754948880

and as you see difference is 16. Just remember that you need to test this when you build Release mode, in Debug mode you see something like this:

16
3754948864
3754948884

a 20 byte difference, Because in Debug mode, compiler allocates more memory for structures. Now if you write your code like this:

struct _asd
{
    int a;
    int b;
    char c[6];
};
void foo2(struct _asd *a, int b)
{
    printf("%u\n", sizeof(struct _asd));
    printf("%u\n", a);
    printf("%u\n", &b);
}

Result will be like this:

16
827849528
827849520

Only 8 bytes difference, because now only addresses is sent to function which is only 8 bytes long.

As a side note, in 64-bit OS, arguments are mostly passed by registers. For example first 4 parameters are normally passed by registers when you compile your code in MSVC. But in current topic, parameters are accessed by address and this will be meaningless for registers. So I think compiler automatically passed such parameters by pushing them into stack.

Afshin
  • 8,839
  • 1
  • 18
  • 53
  • So what happened that my OS is 64-bit ? Why can't the addresses start right after one another ? Like 16, 20, 24 ? Why the extra 4 bytes ?. Please explain. – ng.newbie Mar 18 '18 at 16:49
  • Okay that's great. But imagine if the argument that is being pushed on to the stack in greater than 8 bytes in size ? What would happen then ? – ng.newbie Mar 18 '18 at 16:52
  • @ng.newbie In that case (for example `xmm` instruction for 128-bit operation), it will fill up more slots in stack and `esp` decreased more. – Afshin Mar 18 '18 at 16:54
  • So lets say I have struct function `parameter` that is of size 14 bytes. So in that case will the `esp` be decremented by 16 ? Since it is always a multiple of 8 ? – ng.newbie Mar 18 '18 at 16:57
  • @Afshin Saying that addresses are 64 bits doesn't quite tell the whole story. I think the other part of the story is that stack frame entries are always 64 bits / 8 bytes, too -- even if they hold 32-bit plain ints -- because those entries *might* have to contain pointers, i.e. addresses, too. – Steve Summit Mar 18 '18 at 16:58
  • parameters are not usually passed on stack in 64-bit mode. In 64-bit mode stack pointer is RSP. – Antti Haapala -- Слава Україні Mar 18 '18 at 17:00
  • @SteveSummit I meant that. But I don't think any 64-bit OS use 32-bit stack frames (or at least I don't know) anyway, so my answer is not incorrect, but I add this in replay. – Afshin Mar 18 '18 at 17:01
  • @SteveSummit When I use a struct variable in place of an int variable. The resultant decrements is 24 bytes. Why is that ? If it is always done on the multiple of 8 the decrements should be 16 bytes, not 24. – ng.newbie Mar 18 '18 at 17:05
  • @AnttiHaapala yea, in 64-bit, normally first 4 argument will be passed through registers. But I guess because he has accessed argument address, this optimization is ignored. – Afshin Mar 18 '18 at 17:06
  • @ng.newbie because you passed it by I guess. if you pass something between 17-24 bytes, address will be decreased by 24. If you pass it by address, only 8 will be decreased. – Afshin Mar 18 '18 at 17:10
  • @Afshin Where are getting the rules for this ? Now my question is what happens if I pass something that is 62 byes in size ? What happens then is it a 24 byte decrease or a 8 byte decrease or is it something else ? – ng.newbie Mar 18 '18 at 17:12
  • @Afshin Please include that in your answer. – ng.newbie Mar 18 '18 at 17:14
  • @ng.newbie "Where are getting the rules for this?" These are called *calling conventions*, and they're carefully documented somewhere -- although I don't know where. – Steve Summit Mar 18 '18 at 17:17
0

The gap is always 8 byes between addresses, while sizeof(int) = 4

1) This is compiler and implementation depended. The C standard does not specify memory arrangement for the individual integers.

2) To print addresses use "%p" format after casting it to (void *). The size_t should be print with "%zu" format.

For example:

#include<stdio.h>

struct my_struct
{
    int a;
    int b;
    int c;
    int d;
};

void foo(int a, int b, struct my_struct f, struct my_struct g ) 
{
    printf("%zu\n", sizeof(int));

    printf("&a= %p\n", (void *) &a);
    printf("&b= %p\n", (void *) &b);

    printf("&f= %p\n", (void *) &f);
    printf("&g= %p\n", (void *) &g);
}

int main(void) 
{
    int c=0;
    int d=1;

    struct my_struct e,f;

    foo(5, 4, e, f);

    printf("&c= %p\n", (void *) &c);
    printf("&d= %p\n", (void *) &d);

    return 0;
}

Output:

4
&a= 0x7fff88da09fc
&b= 0x7fff88da09f8
&f= 0x7fff88da09e0
&g= 0x7fff88da09d0
&c= 0x7fff88da0a18
&d= 0x7fff88da0a1c
sg7
  • 6,108
  • 2
  • 32
  • 40
  • Yeah but I am passing the `int` params by value and not by address. Why is it then 8 bytes ? – ng.newbie Mar 18 '18 at 17:19
  • Also what happens if I pass a value that is larger than 8 bytes say ? What is the decrements then ? Is it always 24 bytes ? – ng.newbie Mar 18 '18 at 17:20
  • @ng.newbie I have added structures to the function call. It my case it is `4x4 bytes = 16` – sg7 Mar 18 '18 at 17:30
  • I will be very much obligated if Downvoter could explain me what is a problem with my answer. Thank you! – sg7 Mar 18 '18 at 17:32
  • What answer? I see a bunch of text entered in the Answer field that does not answer the question. The question explicitly asks about Microsoft Visual C++ in both the title and the body, so of course it is implementation dependent. The question has already told us it is not asking about C or C++ generally; it is asking about Microsoft Visual C++. Saying it is implementation dependent does not provide any new information. – Eric Postpischil Mar 18 '18 at 18:56
  • @EricPostpischil Thank you very much for your comment. I do appreciate it. I do not know why Microsoft designers decided to put the gap. 1) This it is implementation depended , it is not specified by C standard and Microsoft can do whatever it likes . 2) Printing address should be done via "%p" format. That is useful info in my opinion. Eric, thank you again! I will try do do better next time. – sg7 Mar 18 '18 at 19:18