CS50’s Introduction to Computer Memory Management in C

Sachin Bhutekar
7 min readJan 5, 2021

This is 5th blog in my ongoing CS50 Series. Readers are suggested to go through 0th, 1st ,2nd & 3rd article for a better understanding of subject matter. In this blog, we will learn about memory management in C.

Computer Memory (Credit: www.pandasecurity.com)

In the last blog, we studied different searching & sorting algorithms. In this blog, we will be studying

  • Hexadecimal Numbers
  • Pointers
  • Memory Layout
  • Dynamic Memory Allocation
  • Call Stacks
  • File Pointers

Hexadecimal Numbers

In our daily life, we use Decimal System (base 10) having 10 digits (0 to 9) to represent the data where each digit has place values like 1, 10, 100 and so on. The computer uses Binary System (base 10) having 2 digits (0 &1) to represent the data where each digit has place values like 1, 2, 4 and so on. Similarly, there is Hexadecimal System (base 16) having 16 digits (0 to F), and each digit has a place value of 1, 16, 256 and so on.

Decimal vs Binary vs Hexadecimal

Each hexadecimal number corresponds to a unique arrangement of 4 binary digits or bits and hence we can express very long, complex binary numbers in a very concise way without losing any data. We use the prefix “0x” before any hexadecimal to represent that it is a hexadecimal number. Each byte in the computer memory is identified by a unique hexadecimal address.

Computer Memory Addressing

Pointers

The pointer, as its name suggests, stores the address of another variable i.e. points toward the other variable. The pointer is nothing more than an address.
For any pointer,
• Its value is a memory address
• Its datatype describes the data located at that memory address.

There are two operators which have been used in the pointer syntax & example above

  • Extraction Operator (&)
    The extraction operator(&) extracts the address of a variable. We use the extraction operator to initialize the pointer. If var is a variable of int type then &var is a pointer to the int and its value is the address of variable var.
  • Dereference Operator (*)
    The dereference operator(*) goes to the reference and accesses data at that memory location allowing you to manipulate it at will. We use the dereferencing operator to declare or create the pointer. If ptr is a pointer of int type then *ptr is the data or value of a variable (here int) whose address is stored inside ptr.

Remember the following things about pointers.

  • The pointer must have the same data type as that of data at the memory address which is stored in a pointer.
  • The size of a pointer to any data type is 4 bytes for a 32-bit system & 8 bytes for a 64-bit system.
  • The pointer should be initialized with NULL if you don't initialize it with a meaningful value. Such a pointer is called Null Pointer which points toward nothing.
  • Pointers allow us to pass the data by reference. means it allows us to pass an actual variable itself. Thus change that is made in other functions will impact the actual data in the passed variable

Memory Layout

Computer memory consists of millions and billions of bytes and during a program when we request memory to the computer, the requested memory is allocated to you from these bytes. The computer (specifically OS) doesn't allocate the memory arbitrarily but follows a certain methodology. The computer uses different sections of memory for different types of data. If we imagine computer memory as a rectangle with three sections then the data will be stored as follows

Memory Layout
  1. Upper Section
    The compiled machine code is stored in the uppermost section of memory. Below the machine code, global variables are stored.
  2. Middle Section(Heap)
    The Heap section is a chunk of memory from which memory is allocated for dynamic memory allocation. When we function like malloc(), they are allocated memory in the heap section. The more memory we use, the computer will go on allocating memory on the heap section towards the lower side.
  3. Bottom Section(Stack)
    The bottom-most part of memory below the heap section is called Stack. Whenever you called any function in a program, the local variables and arguments of that function get memory from the stack section. The more memory we use, the computer will go on allocating memory on the stack section towards the upper side.

Dynamic Memory Allocation

Till this time in CS50, we were allocating memory statically where we would specify how much memory we need in a program. What if we don't know the amount of memory we need? This problem is solved by using Dynamic Memory Allocation.

We can use pointers to get access to a block of dynamically allocated memory at runtime. Dynamically allocated memory comes from the heap section whereas statically allocated memory comes from the stack section.

malloc( )

We can get dynamically allocated memory by making a call to the malloc() function, passing as it parameter the number of bytes required. If it can obtain memory for you, it will return a pointer to the obtained memory. if it can’t obtain memory for you, it will return a NULL pointer. Hence it is important to check whether the pointer returned by malloc() is NULL or not and if t is then you need to end the program. If you try to dereference the NULL pointer then you will suffer a segmentation error.

free( )

The problem with dynamically allocated memory is that the memory you are allocated is not automatically returned to the system for further use when a function in which it is created finishes execution. Thus it results in a memory leak which can compromise the performance of your system. Hence it is standard practice to free the dynamically allocated memory you have used using the free() function.

Three Golden Rules of Dynamic Memory Allocation:

  1. Every block of memory that you malloc() must subsequently be free()d.
  2. The only memory that you malloc() should be free()d.
  3. Do not free() a block of memory more than once.

You can understand the Dynamic Memory Allocation from the following visualization.

Credit: CS50

Call Stack

When you call a function, the system set aside a chunk of memory on the stack section for that function to do its necessary work. Such a chunk of memory is called Stack frames or function frames.

  • In the case of nested functions, more than one function stack frame can exist in memory at a given time, although only one stack frame can be active at a given time.
  • These frames are arranged in a stack. The frame for the most recently called function is always on the top of the stack.
  • When a new function is called, a new frame is pushed on the top of the stack and becomes the active frame.
  • When a function finishes its work, its frame is popped off of the stack and the frame immediately below it becomes a new active function on the top of the stack and picks up immediately from where it left off.

You can understand the above process from the following visualization.

Credit: CS50

File Pointers

Up to this point in C, we don't have any means by which we can store any persistent data, i.e. the information that exists even after the program has stopped running. Fortunately, C provides you with this ability using FILE. It is a data structure that represents a file. We will be working with FILE*, a pointer to files. All of the file manipulation functions are defined in the header file stdio.h and all of them accept FILE* as their parameter except fopen() function which is used to get file pointer in the first place. Following are some of the most common file input/output (I/O) functions.

  • fopen()
  • fclose()
  • fgetc()
  • fputc()
  • fread()
  • fwrite()
File I/O Functions

You should take care of the following things while using these functions

  • fopen(): Always check its return value to make sure that you don't get back NULL.
  • fgetc() / fread(): The operation of the file pointer passed in as a parameter must be “r” for read, or you will suffer an error.
  • fputc() / fwrite(): The operation of the file pointer passed in as a parameter must be “w” for write or “a” for append, or you will suffer an error

Conclusion

I hope this blog was helpful to you. This series of blogs on CS50 will continue. Thank you very much for your patient reading. Please write comments if you find anything incorrect, or if you want to share more information about the topic discussed above. You can connect with me on LinkedIn or by visiting my website on Medium. Happy Learning.

--

--