Memory

From this lecture onwards the cs50.h library is not going to be used anymore!

Binary system : 0 & 1

Hexa system: 0123456789ABCDEF (numbers 0-15)

Snippet of memory:
Attachments/Pasted image 20260615190639.png
IN computer the memory location are referred in Hexadecimal. So per convention, any number using Hexadecimal notation has a 0x.

Code starting from addresses.c
#include <stdio.h>
#include <cs50.h>
int main(void)
{
	int n = 50;
	printf("%i\n",n);
}

New terminology:

#include <stdio.h>
#include <cs50.h>
int main(void)
{
	int n = 50;
	printf("%p\n",&n);
}

Memory Segment in C:

Memory management is one of the core concept in C , and it is essential to understand it to the core.
Memory can be structure in this way:
Attachments/Pasted image 20260623150600.png109

#pointersC

Pointers:

Core topic in C!
A pointer is a variable that can store in address. (Address is referred as memory address)
A pointer is 8-byte by default

#include <stdio.h>
#include <cs50.h>
int main(void)
{
	int n = 5;
	int *p = &n;
	print(%p/n, p);
}

#memory
!Fact : Every time I run the program the memory address changes, this is because modern OS use Address Space Layout Randomization (ASLR), a security feature that randomizes the base address of the stack every time a program runs.  Since local variables like n are stored on the stack, their virtual memory addresses will differ between executions to prevent attackers from predicting memory layouts for exploits like buffer overflows.

Pointer Specs --> In C, the asterisk * sign means:

De-referencing:

Checking at the previous code

#include <stdio.h>
#include <cs50.h>
int main(void)
{
	int n = 5;
	int *p = &n;
	print(%i/n, *p); //Using * sign to dereferece
}

In de-referencing rather than printing the address, we ask to print what is contained in the specific address.

Memory with string type:
#include <stdio.h>
#include <cs50.h>
int main(void)
{
	string s = "Hi!";
   // string *p = &s;
    printf("%p\n", s);
    printf("%p\n", &s[0]);
    printf("%p\n", &s[1]);
    printf("%p\n", &s[2]);
}

Why using & sign for each character of the string s but not while printing the s varibale?

Proof Check here:

cs50/c/lec4memory/ $ make addresses2
cs50/c/lec4memory/ $ ./addresses2

0x590f114f4004
0x590f114f4004
0x590f114f4005
0x590f114f4006

Also, a string is just a collection of char.

string s = "Hi!";

is equal to:

char *s = "Hi!";

proof:
We explained the there is a datatype called typedef that can be used to create customised datatypes:

typedef  char* string;

This means the string data type is actually a collection of chars, check:
Attachments/Pasted image 20260623103724.png

We can also do arithmetic operations to print out addresses:

Pointer arithmetic:
#include <stdio.h>
#include <cs50.h>
int main(void)
{
	char *s = "HI!";
	// Print whole string
	printf("%s\n", s);
	// Print from index 1 onwards 
	printf("%s\n", s + 1);
	// print from index 2 onwards
	printf("%s\n", s + 2);
	// Print memory address
	printf("%p\n", s);
	printf("%p\n", s+1);
	printf("%p\n", s+2);
}

which gives:
Attachments/Pasted image 20260623112154.png

#coremalloc

Copy & malloc:

Note that in C, when there is a variable containing a string OR a set of characters, the first character contains the address of the memory.

#include <stdio.h>
#include <cs50.h>
#include <string.h>

main int(void)
{
    // User input to a string
    char *s = get_string("s: ");
    //Save the address of s in t
    char *t = s;
}

where:
Attachments/Pasted image 20260623115018.png
Both pointing at the same chunk of memory.

So if I was to change the character to uppercase in s, the same would happen to t because they are pointing at the same thing.

#include <stdio.h>
#include <cs50.h>
#include <string.h>

main int(void)
{
    // User input to a string
    char *s = get_string("s: ");
    //Save the address of s in t
    char *t = s;
	
	printf("%s\n", s);
	// switch to uppercase index 0
	s[0] = toupper(s[0]);
	printf("%s\n", s);
	printf("%c\n", s[0]);
	printf("%c\n", t[0]);
}

which is:
Attachments/Pasted image 20260623120236.png

malloc: Memory allocation

2 keywords:

#include <stdio.h>
#include <cs50.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

//Declare main function
int main(void)
{
    //Get user input
    char *s = get_string("s: ");

    //Allocate memory
    char *t = malloc(strlen(s) + 1);

    // copy every character from s to t
    for (int i = 0, n = strlen(s); i <= n; i++)
    {
        t[i] = s[i];
    }

    printf("%s\n", s);
    printf("%s\n", t);

}

The alternative to hardcoding this is, using the strcpy function, where:

#include <stdio.h>
#include <cs50.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

//Declare main function
int main(void)
{
    //Get user input
    char *s = get_string("s: ");

    //Allocate memory
    char *t = malloc(strlen(s) + 1);

    // strcpy function
    strcpy(t,s);
    
    printf("%s\n", s);
    printf("%s\n", t);
}

Whenever we use malloc, we should free the memory that we have assigned to a variable:

#include <stdio.h>
#include <cs50.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

//Declare main function
int main(void)
{
    //Get user input
    char *s = get_string("s: ");

    //Allocate memory
    char *t = malloc(strlen(s) + 1);

    // strcpy function
    strcpy(t,s);
    
    printf("%s\n", s);
    printf("%s\n", t);
    
    //free the memory
    free(t);
}

A good practice is to always check with conditional statements if there are NULL values while using malloc, because of:

#include <stdio.h>
#include <cs50.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

//Declare main function
int main(void)
{
    //Get user input
    char *s = get_string("s: ");
    if (s == NULL)
    {
        return 1;
    }
    
    //Allocate memory
    char *t = malloc(strlen(s) + 1);
    if (t == NULL)
    {
        return 1;
    }
    // use string copy function
    strcpy(t,s);
    printf("%s\n", s);
    printf("%s\n", t);

    //free memory
    if (strlen(t) > 0)
    {
        printf("Variable has memory, clearing up..\n");
        free(t);
    }
    else
    printf("No memory leaks!");
}

#sizeof
Assign memory to integers


int main(void)
{
	int *x = malloc(3*sizeof(int));
	
	x[0] = 10;
	x[1] = 11;
	x[2] = 12;
}
Variable scope:

consider the code and how the stack is used:

void swap(int a, intb)
{
	int tmp = a;
	a = b;
	b = tmp;
}

#scanf

User input , scanf() :

In this block of code we stop using the CS50.h library and start addressing classic C syntax.
- To take as an input an integer:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    // Scanner to take user input as an integer
    int n;
    printf("n: ");
    scanf("%i", &n);
    printf("%i\n",n);
}

!IMPO:
scanf does not take a variable name and prints its content. It points at the address there the value is stored and returns it's content.

- Take a string as an input

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    // Scanner to take user input as an integer
    char *s = malloc(1);
    // NOTE: char *s; would not work
    printf("s: ");
    scanf("%s", s);
    printf("%s\n",s);
}

Here in line 9, I don't need the &s because by definition the first char of a string points to its address. So in this case it's automatically retrieved.

!proof:
#valgrind
Using Valgrind to check how the binary code is running in the system.
valgrind ./getString

Check here the code and errors:

#bufferoverflow
What happened in memory: (Buffer Overflow problem)

This is called a Buffer Overflow. If those overwritten bytes belong to something critical, the operating system will step in and immediately crash my program with a Segmentation fault.

CORRECT USAGE:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    // Scanner to take user input as an integer
    char *s = malloc(4);
    // NOTE: char *s; would not work
    printf("s: ");
    scanf("%s", s);
    printf("%s\n",s);
    
    free(s);
}

execution:
Attachments/Pasted image 20260623161244.png

#filei/o #i/o

File I/O:

Different file handling options:
Attachments/Pasted image 20260623170615.png

There are various modes in which a file can be opened. The following are the different file opening modes:

Mode Description
r Opens an existing text file for reading purposes.
w Opens a text file for writing. If it does not exist, then a new file is created. Here your program will start writing content from the beginning of the file.
a Opens a text file for writing in appending mode. If it does not exist, then a new file is created. Here your program will start appending content in the existing file content.
r+ Opens a text file for both reading and writing.
Opens a text file for both reading and writing. It first truncates the file to zero length if it exists, otherwise creates a file if it does not exist.
a+ Opens a text file for both reading and writing. It creates the file if it does not exist. The reading will start from the beginning but writing can only be appended.
code:
#include <stdio.h>
#include <cs50.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    // open a new file
    FILE *file = fopen("random.txt", "w");
    
}

While working with file handling, we need a file pointer to store the reference of the FILE structure returned by the fopen() function. The file pointer is required for all file-handling operations.
The fopen() function and contains attributes such as the file descriptor, size, and position, etc.

Declare a file pointer:
FILE *file_name;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    // open a new file
    FILE *file = fopen("random.txt", "w");

    //Create a variable with 50bytes
    char *s = malloc(50);

    //Ask user to add a line to the file
    printf("Text: ");
    scanf("%s", s);

    // save to the file (; is the separator)
    fprintf(file, "%s;\n", s);
	
	//close file
    fclose(file);

	//free up memory
    free(s);
}

IN write mode, the file gets re-written every time, so it's not persistent.

We append:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    // open a new file
    FILE *file = fopen("random.txt", "a");

    //Create a variable with 50bytes
    char *s = malloc(50);

    //Ask user to add a line to the file
    printf("Text: ");
    scanf("%s", s);

    // save to the file (; is the separator)
    fprintf(file, "%s;\n", s);
	
	//close file
    fclose(file);

	//free up memory
    free(s);
}

This is incomplete! The program appends only one word at a time, and whenever there is a space character, it stops writing.
Attachments/Pasted image 20260623172102.png


Technical review: fopen vs open

The key difference between fopen() and open() in Linux is that open() is a low-level system call that returns a file descriptor (integer), while fopen() is a higher-level C library function that internally calls open() and returns a FILE pointer with additional buffering capabilities.
#systemcall
A system call requires the CPU to switch from user mode to kernel mode, perform the operation, and switch back.
In C fopen is a library function call , which means fopen has been defined in a header file , you can simply write it in your program by taking it's header file and it is dependent on library function .
open is also used by giving it's appropriate header file but,it is totally system dependent ,as it has been defined for the use of Unix based system , and it directly interacts with the kernel.

Example code:
fd = Open("file",Mode);

If you want to read a file by single character. The syntax for reading like above your system call will increase according to file's content.

fd = open("file", O_RDONLY);
read(fd, buffer, 1);    

If file contains 10,000 characters, this approach forces the operating system to perform 10,000 separate system calls.  The time spent switching modes often exceeds the time spent actually reading the data, causing the program to run significantly slower as the file size increases.
(The solution would be to hardcode a bigger buffer_size)

#define BUFFER_SIZE 4096 
int main()
{ 
	int fd = open("file.txt", O_RDONLY);
	if (fd == -1) return 1; 
	char buffer[BUFFER_SIZE]; 
	ssize_t bytes_read;

But using fopen then we have only one system call:
When you open a file using fopen(), the C standard library allocates a memory buffer (typically 4KB to 8KB, depending on the system) associated with that FILE pointer.

*Portability
fopen is library function call and it has been defined in appropriate headers file so it is present in every system whether it is Unix or other OS having c installation.
But open is a system call and it is only for Unix based Operating systems.
So if you have written any code using fopen then you can easily run it either on Unix based os or on other OS of c platforms.


Raw implementation of the Linux cp copy command:
include <stdio.h>

//Data type to read raw bytes
typedef unsigned char BYTE;

// Main function takes arguments
int main(int agrc , char *argv[])
{
    FILE *src = fopen(argv[1], "r");
    FILE *dst = fopen(argv[2], "w");

    BYTE b;

    while(fread(&b, sizeof(b), 1, src) != 0)
    {
        fwrite(&b, sizeof(b), 1, dst);
    }

    fclose(src);
    fclose(dst);
}

Breakdown:

int main(int argc, int argv[]
{
    FILE *src = fopen(argv[1], "r");
    FILE *dst = fopen(argv[2], "w");
}

The main function takes 2 command line arguments, which could be source of the file to copy and destination of the file to be written.

typedef unsigned char BYTE;

Create a datatype called BYTE , where:

    BYTE b;

    while(fread(&b, sizeof(b), 1, src) != 0)
    {
        fwrite(&b, sizeof(b), 1, dst);
    }

    fclose(src);
    fclose(dst);
}
Powered by Forestry.md