C Memory

Lecture Video

Lets take a look at how C works with memory.

C Memory Structure

C Memory structure

Memory is C is segmented in a similar way to memory in Java, there are areas for code, static data, the stack and the heap.

Text Segment

  • The Text segment contains machine code of the compiled program.
  • Usually, the text segment is sharable so that only a single copy needs to be in memory for frequently executed programs, such as text editors, the C compiler, the shells, and so on.
  • The text segment of an executable object file is often read-only segment that prevents a program from being accidentally modified.
C Memory

Initialized Data Segment

  • Initialized data stores all global, static, constant, and external variables ( declared with extern keyword ) that are initialized beforehand.
  • Data segment is not read-only, since the values of the variables can be altered at run time.
  • This segment can be further classified into initialized read-only area and initialized read-write area. Read-only uses const
C Memory
char c[]="Some text";             /* global variable stored in Initialized Data Segment in read/write area */
const char d[]="Some more text";  /* global variable stored in Initialized Data Segment in read-only area */

int main()
{
  static int i=11;                /* static variable stored in Initialized Data Segment */
  return 0;
}

Uninitialized Data Segment (bss)

  • Data in this segment is initialized to arithmetic 0 before the program starts executing.
  • Uninitialized data starts at the end of the data segment and contains all global variables and static variables that are initialized to 0 or do not have explicit initialization in source code
C Memory
char c[]="Some text";             /* global variable stored in Initialized Data Segment in read/write area */
const char d[]="Some more text";  /* global variable stored in Initialized Data Segment in read-only area */
<b>char e;                        /* Uninitialised variable stored in bss*/</b>

int main()
{
  static int i=11;                /* static variable stored in Initialized Data Segment
  <b>static int j;                /* Uninitialised variable stored in bss*/</b>
  return 0;
}

The Heap

  • The Heap is the segment where dynamic memory allocation usually takes place.
  • When some more memory need to be allocated using malloc* and calloc* function, heap grows upward.
  • The Heap area is shared by all shared libraries and dynamically loaded modules in a process.
C Memory
#include <stdlib.h>
int main()
{
  char *p=(char*)malloc(sizeof(char));                /* memory allocation in the heap
  return 0;
}

* we’ll come on to the meaning of malloc and calloc soon

The Stack

  • Stack segment is used to store all local variables and is used for passing arguments to the functions along with the return address of the instruction which is to be executed after the function call is over.
  • Local variables have a scope to the block which they are defined in, they are created when control enters into the block.
  • All recursive function calls are added to stack.
  • The stack and heap are traditionally located at opposite ends of the process's virtual address space.
  • The stack grows downward, starting at a high memory address
  • But individual variables are still stored upwards!
C Memory

Frame Structure

  • A frame fills down from a starting memory location
  • Added to the frame, in order, are
    • Return Address - the address the function was called from
    • Parameters - the variables passed to the function
    • Local Variables
C Memory- Frame structure

While the frame is filled downwards from a high memory address individual variables fill upwards.

#include <stdio.h>
#include <stdalign.h>

int main(void) {

int integer1=1;
int integer2=2;
int integer3=3;

printf("integer1 = %d Memory address %p\n",integer1,&integer1);
printf("integer2 = %d Memory address %p\n",integer2,&integer2);
printf("integer3 = %d Memory address %p\n",integer3,&integer3);


return 0;
}
integer1 = 1 Memory address 0x7ffe196e4ad8
integer2 = 2 Memory address 0x7ffe196e4ad4
integer3 = 3 Memory address 0x7ffe196e4ad0

An integer takes up 4 bytes in memory. In the example above integer1 is stored “at” address 0x7ffe196e4ad8, but it takes up four bytes of memory and the address 0x7ffe196e4ad8 only stores 1 byte. Where are the other three bytes stored? They are stored in the next three memory addresses going upwards. integer1 is stored in addresses 0x7ffe196e4ad8, 0x7ffe196e4ad9, 0x7ffe196e4ada and 0x7ffe196e4adb. (The addresses are in hexadecimal format - base 16).

When a variable is added to the stack the size of the variable is subtracted from the last used address to determine the first memory location the variable will be stored in and that gap gets filled up with the variable.

So before integer1 is created the last used address is 0x7ffe196e4adc. We want to create an integer. An integer takes up 4 bytes so we subtract 4 from the address to get 0x7ffe196e4ad8. So integer1 is stored in four bytes starting at 0x7ffe196e4ad8 and using the next three bytes up from there.

Its a little more complex than that. Computers have a memory alignment. This alignment is based on the processor. For a 64bit machine the alignment is 8 bytes. Memory is aligned in 8 byte sections that can be loaded in to the processor faster.

An 8 byte double can be stored in any contiguous 8 bytes of memory but for speed of access it is better if it is aligned to the 8 byte memory alignment.

In the example below the aligned variable will be read in to the processor in a single read from memory but the unaligned variable will require two reads.

C Memory- Frame structure C Memory- Frame structure

When variables are added to the stack they are added so that they are not split across the alignment boundaries.

The machine used by replit.com appears to have 4 byte boundaries.

#include <stdio.h>
#include <stdalign.h>

int main(void) {

int integer1=1;
short short2=2;
int integer3=3;

printf("integer1 =%d Memory address%p\n",integer1,&integer1);
printf("short2 =%d Memory address%p\n",short2,&short2);
printf("integer3 =%d Memory address%p\n",integer3,&integer3);


return 0;
}
integer1 =1 Memory address0x7ffe5d4f6428
short2 =2 Memory address0x7ffe5d4f6426
integer3 =3 Memory address0x7ffe5d4f6420

A short only takes up two bytes of space, as can be seen above. After that we create another 4 byte integer. Looking at the above we can see its starting address is 6 bytes below the short, why 6 rather than 4?

M+3 M+2 M+1 M+0
i1+3 i1+2 i1+1 i1+0
s2+1 s2+0    
i3+3 i3+2 i3+1 i3+0

Including the extra 2 bytes to offset the location of the second integer keeps it aligned. Without it the variable would take longer to read from memory.

M+3 M+2 M+1 M+0
i1+3 i1+2 i1+1 i1+0
s2+1 s2+0 i3+3 i3+2
i3+1 i3+0    

C Variable Sizes

C variables have a fixed size in memory.

Type Stores Size
int integer number 4-bytes
short integer number 2-bytes
long integer number 8-bytes
char character 1-byte
float floating point number 4-bytes
double floating point number 8-bytes
void * pointers 8-bytes on (64 bit machines)

You can use the sizeof() function to find the size of a variable in bytes.

#include <stdio.h> 
int main() 
{ 
    printf("%lu\n", sizeof(char)); 
    printf("%lu\n", sizeof(int)); 
    printf("%lu\n", sizeof(float)); 
    printf("%lu", sizeof(double)); 
    return 0; 
}