26

I noticed something strange after compiling this code on my machine:

#include <stdio.h>

int main()
{
    printf("Hello, World!\n");

    int a,b,c,d;

    int e,f,g;

    long int h;

    printf("The addresses are:\n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x",
        &a,&b,&c,&d,&e,&f,&g,&h);

    return 0;
}

The result is the following. Notice that between every int address there is a 4-byte difference. However between the last int and the long int there is a 12-byte difference:

 Hello, World!
 The addresses are:

 da54dcac 
 da54dca8 
 da54dca4 
 da54dca0 
 da54dc9c 
 da54dc98 
 da54dc94 
 da54dc88
9
  • 3
    Put another int after h in the source code. The compiler may put it in the gap, before h. Sep 12, 2017 at 12:48
  • 32
    Don't use the difference between memory addresses to determine size. There's a sizeof function for that. printf("size: %d ", sizeof(long)); Sep 12, 2017 at 19:46
  • 10
    You're only printing the low 4 bytes of your addresses with %x. Lucky for you, it happens to work correctly on your platform to pass pointer args with a format string expecting unsigned int, but pointers and ints are different sizes in many ABIs. Use %p to print pointers in portable code. (It's easy to imagine a system where your code would print upper/lower halves of the first 4 pointers, instead of lower half of all 8.) Sep 13, 2017 at 0:41
  • 5
    @ChrisSchneider to print size_t use %zu. @yoyo_fun to print addresses use %p. Using the wrong format specifier invokes undefined behavior
    – phuclv
    Sep 13, 2017 at 3:54
  • 2
    @luu don’t spread misinformation. No decent compiler cares about the order in which variables are declared in C. If it cares, there’s no reason why it would do it the way you describe.
    – gnasher729
    Jan 30, 2018 at 8:21

3 Answers 3

81

It didn't take 12 bytes, it only took 8. However, the default alignment for an 8 byte long int on this platform is 8 bytes. As such, the compiler needed to move the long int to an address that's divisible by 8. The "obvious" address, da54dc8c, isn't divisible by 8 hence the 12 byte gap.

You should be able to test this. If you add another int prior to the long, so there are 8 of them, you should find that the long int will be aligned ok without a move. Now it'll be only 8 bytes from the previous address.

It's probably worth pointing out that, although this test should work, you shouldn't rely on the variables being organised this way. A C compiler is allowed to do all sorts of funky stuff to try to make your program run quickly including re-ordering variables (with some caveats).

9
  • 3
    difference, not gap. Sep 12, 2017 at 12:09
  • 10
    "including re-ordering variables". If the compiler decides that you don't use two variables at the same time, it's free to partially overlap or completely overlay them as well... Sep 12, 2017 at 13:01
  • 8
    Or indeed, keep them in registers instead of on the stack.
    – OrangeDog
    Sep 12, 2017 at 13:30
  • 11
    @OrangeDog I don't think that would happen if the address is taken as in this case but, in general, you are of course correct.
    – Alex
    Sep 12, 2017 at 13:34
  • 5
    @Alex: You can get funny things with memory and registers when taking the address. Taking the address means it has to give it a memory location, but doesn't mean it has to actually use it. If you take the address, assign 3 to it and pass it to another function, it might just write 3 into RDI and call, never writing it to memory. Surprising in a debugger sometimes.
    – Zan Lynx
    Sep 12, 2017 at 16:31
9

This is because your compiler is generating extra padding between variables to ensure they are correctly aligned in memory.

On most modern processors, if a value has an address that is a multiple of its size, it is more efficient to access it. If it had put h at the first available spot, its address would have been 0xda54dc8c, which isn't a multiple of 8, so would have been less efficient to use. The compiler knows about this and is adding a bit of unused space between your last two variables in order to make sure it happens.

6
  • Thanks for the explanation. Can you point me to some materials regarding the reasons for which accessing variables which are multiple of their sizes is more efficient? i would like to know why is this happening?
    – yoyo_fun
    Sep 12, 2017 at 11:05
  • 4
    @yoyo_fun and if you really want to understand memory, then there is a famous paper on the subject futuretech.blinkenlights.nl/misc/cpumemory.pdf
    – Alex
    Sep 12, 2017 at 11:26
  • 1
    @yoyo_fun It's quite simple. Some memory controllers can only access multiples of the processor's bit width (e.g. a 32-bit processor can only directly request addresses 0-3, 4-7, 8-11, etc). If you ask for a non-aligned address, the processor has to make two memory requests then get the data into the register. So, back to 32-bit, if you wanted a value stored at address 1, the processor has to ask for addresses 0-3, 4-7, then get the bytes from 1, 2, 3, and 4. Four bytes of memory read wasted.
    – phyrfox
    Sep 12, 2017 at 14:10
  • 2
    Minor point, but misaligned memory access can be an unrecoverable fault instead of a performance hit. Architecture dependent. Sep 12, 2017 at 23:14
  • 1
    @JonChesterfield - Yes. That's why I commented that the description I gave applies to most modern architectures (by which I mostly meant x86 and ARM). There are others that behave in different ways, but they're substantially less common. (Interestingly: ARM used to be one of the architectures that required aligned access, but they added automatic handling of unaligned access in later revisions)
    – Jules
    Sep 13, 2017 at 2:06
2

Your test isn't necessarily testing what you think it is, because there is no requirement of the language to relate the address of any of these local variables to each other.

You would have to put these as fields in a struct in order to be able to infer something about storage allocation.

Local variables are not required to share storage next to each other in any particular manner. The compiler may insert a temporary variable anywhere within the stack, for example, which could be in between any two of these local variables.

By contrast, it wouldn't be allowed to insert a temporary variable into a struct, so if you printed the addresses of struct fields instead, you'd be comparing items intended allocated from the same logical chuck of memory (the struct).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.