3

I've been doing some pointers testing in C, and I was just curious if the addresses of a function's parameters are always in a difference of 4 bytes from one another.

I've tries to run the following code:

#include <stdio.h>

void func(long a, long b);

int main(void)
{
    func(1, 2);

    getchar();
    return 0;
}

void func(long a, long b)
{
    printf("%d\n", (int)&b - (int)&a);
}

This code seems to always print 4, no matter what is the type of func's parameters. I was just wondering if it's ALWAYS 4, because if so it can be useful for something I'm trying to do (but if it isn't necessarily 4 I guess I could just use va_list for my function or something). So: Is it necessarily 4 bytes?

aviad1
  • 354
  • 1
  • 10

3 Answers3

6

Absolutely not, in so many ways that it would be hard to count them all.

First and foremost, the memory layout of arguments is simply not specified by the C language. Full stop. It is not specified. Thus the answer is "no" immediately.

va_list exists because there was a need to be able to navigate a list of varadic arguments because it wasn't specified other than that. va_list is intentionally very limited, so that it works on platforms where the shape of the stack does not match your intuition.

Other reasons it can't always be 4:

  • What if you pass an object of length 8?
  • What if the compiler optimizes a reference to actually point at the object in another frame?
  • What if the compiler adds padding, perhaps to align a 64-bit number on a 64-bit boundary?
  • What if the stack is built in the opposite direction (such that the difference would be -4 instead of +4)

The list goes on and on. C does not specify the relative addresses between arguments.

Cort Ammon
  • 10,221
  • 31
  • 45
2

As the other answers correctly say:

No.

Furthermore, even trying to determine whether the addresses differ by 4 bytes, depending on how you do it, probably has undefined behavior, which means the C standard says nothing about what your program does.

void func(long a, long b)
{
    printf("%d\n", (int)&b - (int)&a);
}

&a and &b are expression of type long*. Converting a pointer to int is legal, but the result is implementation-defined, and "If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type."

It's very likely that pointers are 64 bits and int is 32 bits, so the conversions could lose information.

Most likely the conversions will give you values of type int, but they don't necessarily have any meaning, nor does their difference.

Now you can subtract pointer values directly, with a result of the signed integer type ptrdiff_t (which, unlike int, is probably big enough to hold the result).

printf("%td\n", &b - &a);

But "When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object; the result is the difference of the subscripts of the two array elements." Pointers to distinct object cannot be meaningfully compared or subtracted.

Having said all that, it's likely that the implementation you're using has a memory model that's reasonably straightforward, and that pointer values are in effect represented as indices into a monolithic memory space. Comparing &b vs. &a is not permitted by the C language, but examining the values can provide some insight about what's going on behind the curtain -- which can be especially useful if you're tracking down a bug.

Here's something you can do portably to examine the addresses:

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

The result you're seeing for the subtraction (4) suggests that type long is probably 4 bytes (32 bits) on your system. I'd guess you're on Windows. It also suggests something about the way function parameters are allocated -- something that you as a programmer should almost never have to care about, but is worth understanding anyway.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
0

[...] I was just curious if the addresses of a function's parameters are always in a difference of 4 bytes from one another."

The greatest error in your reasoning is to think that the parameters exist in memory at all.

I am running this program on x86-64:

#include <stdio.h>
#include <stdint.h>

void func(long a, long b)
{
    printf("%d\n", (int)((intptr_t)&b - (intptr_t)&a));
}

int main(void)
{
    func(1, 2);
}

and compile it with gcc -O3 it prints 8, proving that your guess is absolutely wrong. Except... when I compile it without optimization it prints out -8.

X86-64 SYSV calling convention says that the arguments are passed in registers instead of being passed in memory. a and b do not have an address until you take their address with & - then the compiler is caught with its pants down from cheating the as-if rule and it quickly pulls up its pants and stuffs them into some memory location so that they can have their address taken, but it is in no way consistent in where they're stored.