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
  • 3
    Put another int after h in the source code. The compiler may put it in the gap, before h. – ctrl-alt-delor Sep 12 '17 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)); – Chris Schneider Sep 12 '17 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.) – Peter Cordes Sep 13 '17 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 '17 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 '18 at 8:21
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).

  • 3
    difference, not gap. – Deduplicator Sep 12 '17 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... – Roger Lipscombe Sep 12 '17 at 13:01
  • 8
    Or indeed, keep them in registers instead of on the stack. – Stop Harming Monica Sep 12 '17 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 '17 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 '17 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.

  • 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 '17 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 '17 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 '17 at 14:10
  • 2
    Minor point, but misaligned memory access can be an unrecoverable fault instead of a performance hit. Architecture dependent. – Jon Chesterfield Sep 12 '17 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 '17 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.