Skip to the content.

Embedded Systems Study Group

Dynamic Memory

Use

When a variable is defined in the source program, the type of the variable determines how much memory the compiler allocates. When the program executes, the variable consumes this amount of memory regardless of whether the program actually uses the memory allocated. This is particularly true for arrays. However, in many situations, it is not clear how much memory the program will actually need. For example, we may have declared arrays to be large enough to hold the maximum number of elements we expect our application to handle. If too much memory is allocated and then not used, there is a waste of memory. If not enough memory is allocated, the program is not able to fully handle the input data. We can make our program more flexible if, during execution, it could allocate initial and additional memory when needed and free up the memory when it is no more needed. Allocation of memory during execution is called dynamic memory allocation. C provides library functions to allocate and free up memory dynamically during program execution. Dynamic memory is allocated on the heap by the system.

Accessing

There is a family of four functions which allow programs to dynamically allocate memory on the heap. In order to use these functions you have to include the stdlib.h header file in your program.

        void * malloc (size_t nbytes);

nbytes is the number of bytes that to be assigned to the pointer. The function returns a pointer of type void*. When allocating memory, malloc() returns a pointer which is just a byte address. Thus, it does not point to an object of a specific type. A pointer type that does not point to a specific data type is said to point tovoid type, that is why we have to type cast the value to the type of the destination pointer, for example:

        char* test;
        test = (char*) malloc(10);
        void* calloc (size_t nelements, size_t size);

calloc() is very similar to malloc() in its operation except its prototype have two parameters. These two parameters are multiplied to obtain the total size of the memory block to be assigned. Usually the first parameter (nelements) is the number of elements and the second one (size) serves to specify the size of each element. For example, we could define test with calloc():

        int* test;
        
        test = (int *) calloc(5, sizeof(int));

NOTE:- calloc() initializes all its elements to 0

Precautions

Memory Leak

It is easy to introduce memory leaks into application code implemented using malloc() and free(). This is caused by memory being allocated and never being deallocated. Such errors tend to cause a gradual performance degradation and eventual failure. This type of bug can be very hard to locate.

Memory Fragmentation

The best way to understand memory fragmentation is to look at an example. For this example, it is assumed hat there is a 10K heap. First, an area of 3K is requested, thus:

           #define K (1024)
           char *p1;
           p1 = malloc(3*K);

Then, a further 4K is requested:

          p2 = malloc(4*K);

3K of memory is now free.

Some time later, the first memory allocation, pointed to by p1, is de-allocated:

          free(p1);

This leaves 6K of memory free in two 3K chunks. A further request for a 4K allocation is issued:

         p1 = malloc(4*K);

This results in a failure – NULL is returned into p1 – because, even though 6K of memory is available, there is not a 4K contiguous block available. This is memory fragmentation.

Storage Classes in C

A storage class represents the visibility and a location of a variable. It tells from what part of code we can access a variable.

A storage class is used to describe the following things:

Thus a storage class is used to represent the information about a variable. A variable is not only associated with a data type, its value but also a storage class.

auto

#include <stdio.h>

int main( )
{
  auto int j = 1;
  {
    auto int j = 2;
    {
      auto int j = 3;
      printf(" %d ", j);
    }
    printf("\t %d ",j);
  }
  printf("%d\n", j);

  return 0;
}

Output: 3 2 1

extern

In some applications it may be useful to have data which is accessible from within any block and/or which remains in existence for the entire execution of the program. Such variables are called global variables, and the C language provides storage classes which can meet these requirements; namely, the external (extern) and static (static) classes. Declaration for external variable is as follows: extern int var;

External variables may be declared outside any function block in a source code file the same way any other variable is declared; by specifying its type and name (extern keyword may be omitted). But say an variable is defined in file1.c and used in file2.c and file3.c then the extern keyword must be used in file2.c and file3.c.

The scope of external variables is global, i.e. the entire source code in the file following the declarations. All functions following the declaration may access the external variable by using its name. However, if a local variable having the same name is declared within a function, references to the name will access the local variable cell.

External variables may be initialized in declarations just as automatic variables; however, the initializers must be constant expressions. The initialization is done only once at compile time, i.e. when memory is allocated for the variables. In general, it is a good programming practice to avoid using external variables as they destroy the concept of a function as a ‘black box’ or independent module.

Output: value of the external integer is = 48

static

Static variables were completed in earlier lecture, followup question for you guys, tell me if this code is correct and will it work ?

command: gcc main.c other.c -o main

Answer
It won't compile as we can't extern a static variable, as the scope of a static variable is limited only to its translation unit, i.e if defined in a function, it can't be seen by other functions, similarly if defined globally in a source file other source files can't access it. If you recall, we get undefined reference error when we didnot include a object file of a function and used it, same happens here, as extern expects a variable defined in some other source file, but it cannot find the variable, as it is static, thus it's visibility is restricted only to the source file where it is defined, `other.c` and not `main.c`

register

You can use the register storage class when you want to store local variables within functions or blocks in CPU registers instead of RAM to have quick access to these variables. For example, “counters” are a good candidate to be stored in the register. The variables declared using register storage class has lifespan throughout the program.

The register storage class specifier indicates to the compiler that the object should be stored in a machine register. The register storage class specifier is typically specified for heavily used variables, such as a loop control variable, in the hopes of enhancing performance by minimizing access time. However, the compiler is not required to honor this request. Because of the limited size and number of registers available on most systems, few variables can actually be put in registers. If the compiler does not allocate a machine register for a register object, the object is treated as having the storage class specifier auto.

The following restrictions apply to the register storage class specifier:

#include <stdio.h>

int main() 
{
    register int weight;
    int *ptr = &weight;
    
    return 0;
}

Since, register doesn’t have an address we cannot request it’s address using & operator.

Byte ordering

Little and big endian are two ways of storing multibyte data-types ( int, float, etc). In little endian machines, last byte of binary representation of the multibyte data-type is stored first. On the other hand, in big endian machines, first byte of binary representation of the multibyte data-type is stored first.

Let’s see an example:

int *i; // pointer to an int (4 bytes on 32-bit machine)
i = 0;  // points to location zero, so *i is the value there

What is the value of i?

Big endian machine: An int is 4 bytes, and the first is the largest. I read 4 bytes (W X Y Z) and W is the largest. The number is 0x12345678. Little endian machine: Sure, an int is 4 bytes, but the first is smallest. I also read W X Y Z, but W belongs way in the back – it’s the littlest. The number is 0x78563412.

Use this code to check endianness on your computer

#include <stdio.h> 
int main()  
{ 
   unsigned int i = 1; 
   char *c = (char*)&i; 
   if (*c)     
       printf("Little endian"); 
   else
       printf("Big endian"); 
   getchar(); 
   return 0; 
} 

Note: There is no difference between performance in little endian and big endian machines, it’s just a matter of convention.