3

I have this very simple piece of code:

// test.c
int main(){
  int a = 0;
  char b[10];
  int c = 0;

  return 0;
}

Compiled with gcc (6.2.1):

$ gcc -g -o test test.c

And analysed with gdb:

$ gdb -q test
(gdb) break 6                (the return statement)
(gdb) run
(gdb) x &a
0x7fffffffecb8: 0x00000000
(gdb) x b
0x7fffffffecc0: 0x00000001
(gdb) x &c
0x7fffffffecbc: 0x00000000

So you can clearly see that in memory, the variables are defined in this order: a, c, b.

Is there a reason for that?

  • Can you compile with -O0 (disable optimization)? – Holmes.Sherlock Jan 20 '17 at 6:08
  • Same result unfortunately. – nobe4 Jan 20 '17 at 11:46
5

My best guess : memory alignment.

An integer in C is 4 bytes and a char 1 byte. Therefore, your declarations go like this : 4B & 1x10=10B & 4B. This order means that the 10B won't be aligned on a power of two memory boundary without wasting space. Data accesses are faster on x86 machines when arrays are aligned on 16B/32B/64B - BTW 64B is the size of cacheline).

Therefore, the compiler finds it more optimal to align the first two 4B variables 0x---ECCB8 & 0x---ECBC (the difference is 4bytes). Then choose the closest aligned memory location 0x---ECC0 for the array (0x---ECC0 - 0x---ECBC = 4B); the zero at the end implies the address is dividable by a power of two. If you forget about the 7F... and convert ECC0 to base 10 you get 60608 which is dividable by 64, 32, 16, ... We can assume safely then that the compiler optimized the array placement to match a 64B memory alignment.

You should read Ulrich Drepper's document on memory and check Agner Fog's Software Optimization Manuals. Gold mines!

Addendum :

If you wish to play around with memory alignment I suggest that you check out how compiler perform data layout restructuring and padding of structures in C. Padding means that compilers add sometimes extra bytes to reach a power of two boundary. For example, imagine you have a code that contains the following

    typedef struct { int x; int y; int z; } point3D;

This declaration contains three 4B variables = 12B, not a power of 2. The compiler will most likely add another 4B to align it with the closest power of 2 boundary : 16B. Therefore, after compilation your declaration will look like this :

    typedef struct {int x; int y; int z; char[4] padding; } point3D;

Now with regard to your comment, I'd suggest trying the following :

       #include <stdio.h>
       #include <emmintrin.h> //Required for mm_malloc
       #define ALIGN 64
       int main(){
       int *a = _mm_malloc(sizeof(int), ALIGN);
       char b[10];
       int *c = _mm_malloc(sizeof(int), ALIGN);

       printf("a: %p\nb: %p\nc: %p, a, b, c);

       return 0; }

There are many variants of malloc (and tricks with the normal malloc) which let you align your memory on the boundary you seem fit for your code. Keep in mind that you have two types of memory for a binary program : the heap which is manipulated using malloc type functions, and the stack, which is left to the compiler to handle (function parameters, register saving, local variables, ...). The only way you can control the stack usage and data alignment is by doing it yourself using assembly code or, if the compiler handles it, compiler directives and parameters.

Your code allocates three local variables to main. Thefore, they'll be allocated in the stack, and given that your code goes through a compiler it will perform all necessary actions to align these variables using a heuristic analysis (optimistic predictive memory placement). If you compile the code provided above, you'll notice that the addresses aren't similar (heap vs. stack). 0x7FF---? for stack variables and 0x000---? for heap variables. You can play around with these functions and figure a lot of things for yourself. In doubt, refer to the documentation : Intel Software Optimization manual, Agner's, ... Next step if you want to understand things clearly is to delve into the OS memory allocation and paging scheme and the x86 architecture in order to understand better the requirement for alignment : cache line size, memory segmentation & BSI (Base + Scale * Index; scale can only take the following values : 1, 2, 4, and 8).

I hope this addendum makes things clearer for you.

| improve this answer | |
  • Interesting, I've tried to change the array size to make its size a power of two. But the result remains unchanged. (thanks for the links) – nobe4 Jan 20 '17 at 11:50
  • 1
    Well, the array size isn't the key here, rather alignment. Check the update. – yaspr Jan 20 '17 at 13:08
  • 1
    "An integer in C is 4 bytes and a char 8 bytes." This is incorrect. The number of bits of type char is given in <limits.h> to be 8, not 64. The number of bytes in a char will never exceed the number of bytes in an int – julian Jan 22 '17 at 16:29
  • 1
    b is a constant pointer to a char array, so b is 8 bytes on x86-64, b[0] is 1 byte. – julian Jan 22 '17 at 16:43
  • 1
    I was mistaken. I wanted to say 8 bits = 1 byte. Thanks for pointing it out. Fixed! – yaspr Jan 22 '17 at 23:11

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.