C Programming
C Programming
Introduction
- C is often called the “mother of all programming languages” because of its influence on modern languages like C++, Java, and Python. Developed by Dennis Ritchie in the early 1970s at Bell Labs, C remains a cornerstone for system programming, embedded systems, and performance-critical applications.
Why Learn C?
- Foundation for other languages: Many popular languages borrow syntax and concepts from C.
- Efficiency: C gives direct access to memory and hardware, making it ideal for embedded systems.
- Portability: Programs written in C can run on different machines with minimal changes.
- Control: Unlike high-level languages, C allows fine-grained control over system resources.
Key Features of C
- Procedural language: Focuses on functions and structured programming.
- Low-level access: Through pointers and direct memory manipulation.
- Rich library: Standard functions for input/output, string handling, and math.
- Fast compilation & execution: Suitable for real-time applications.
Basic Structure of a C Program
#include <stdio.h> // Preprocessor directive
int main()
{
printf("Hello, World!\n"); // Output statement
return 0; // Exit status
}
Structure Breakdown
- #include<stdio.h> : Includes standard input/output library.
- int main() : Entry point of the program.
- printf() : Prints text to the console.
- return 0; : Ends the program successfully.
Applications of C
• Operating systems (UNIX, Linux kernel).
• Embedded systems (microcontrollers, IoT devices).
• Compilers and interpreters.
• Game development and graphics engines.
A program is a set of instructions, given to the computer by user in user friendly format.
Program are written in human understandable format, that format is called as a programming language.
Then this source code will be converted to machine understandable code by the programming compiler.
First C Program:
// single line comment is written by using "//"
/*
Multi-line comment is written in this format
*/
#include<stdio.h> // header file which includes standard input/output functions
int main() // main function is the first function to be called in the program
{
printf("Hello World\n"); // function to display the output
return 0; // return 0 value on successful execution of function statements
}
Compiler used for C program compilation : GCC
Terminal Commands to compile a C program:
>gcc program.c //terminal command to compile the program
>./a.out //terminal command to execute or run the program
// Program to declare the variable using extern keyword
//<-------- extern keyword Use -------->//
#include<stdio.h>
#include<stdbool.h>
//#include<conio.h> //not available for vscode
extern int x;
int main()
{
int age = 30;
char name[] = "Embedded";
float average = 66.78;
char gender = 'M';
bool result = true;
printf("%d\n",x);
printf("%d\n", age);
printf("%s\n", name);
printf("%f\n", average);
printf("%c\n", gender);
printf("%d\n", result);
printf("size of int is = %u\n", sizeof(int));
printf("size of char is=%u\n", sizeof(char));
printf("size of float is=%u\n", sizeof(float));
printf("size of bool is=%u\n", sizeof(bool));
return 0;
// getch(); //only used with conio.h header file to hold value on console
// The getch() is a predefined non-standard function that is defined in conio. h header file.
// It is mostly used by the Dev C/C++, MS- DOS's compilers like Turbo C to hold the screen
// until the user passes a single value to exit from the console screen.
}
int x=5; //extern variable is declared here in
Compile Process in C:
1. Preprocessing:
- The preprocessor scans the source code and handles preprocessor directives that begin with a '#' symbol, such as '#include', '#define', '#ifdef', etc.
- The '#include' directive is used to insert the contents of header files into the source code.
- Macro expansions occur when the preprocessor replaces macro names with their corresponding definitions.
- Conditional compilation directives, like '#ifdef' and '#ifndef', control the inclusion or exclusion of certain code based on conditions.
2. Compilation:
- The preprocessed code is passed to the compiler.
- The compiler performs lexical analysis, syntax analysis, and semantic analysis of the code.
- Lexical analysis involves breaking the code into tokens (keywords, identifiers, operators, etc.) and removing comments.
- Syntax analysis checks the grammar of the code to ensure it follows the language rules.
- Semantic analysis verifies the correctness of the code in terms of types, scope, and usage of variables and functions.
- If there are no syntax or semantic errors, the compiler generates assembly language or machine code instructions.
3. Assembly:
- If the compiler generates assembly code, the assembly step converts the assembly code into machine code.
- The assembly code is transformed into binary instructions that the processor can understand.
- The output of this step is an object file, which contains the machine code instructions and some metadata.
4. Linking:
- If your program consists of multiple source files or uses external libraries, the linker combines the object files generated in the previous step.
- The linker resolves references to functions and variables across different object files by matching symbols.
- It also resolves references to external libraries by linking them to the program.
- The linker performs memory layout, determining the location of functions and variables in memory.
- It generates the final executable file, which can be run by the operating system.
5. Loading and Execution:
- The operating system loads the executable file into memory.
- It allocates memory for global and static variables.
- Registers and stacks are set up, and any necessary system resources are allocated.
- The program's entry point (usually the 'main' function in C) is located.
- Execution begins, and the processor executes the instructions in the loaded executable file.
During the compilation process, various optimizations may be performed by the compiler and linker to improve the efficiency and performance of the resulting executable code. These optimizations can include dead code elimination, constant folding, loop unrolling, and many more.
- The preprocessor scans the source code and handles preprocessor directives that begin with a '#' symbol, such as '#include', '#define', '#ifdef', etc.
- The '#include' directive is used to insert the contents of header files into the source code.
- Macro expansions occur when the preprocessor replaces macro names with their corresponding definitions.
- Conditional compilation directives, like '#ifdef' and '#ifndef', control the inclusion or exclusion of certain code based on conditions.
2. Compilation:
- The preprocessed code is passed to the compiler.
- The compiler performs lexical analysis, syntax analysis, and semantic analysis of the code.
- Lexical analysis involves breaking the code into tokens (keywords, identifiers, operators, etc.) and removing comments.
- Syntax analysis checks the grammar of the code to ensure it follows the language rules.
- Semantic analysis verifies the correctness of the code in terms of types, scope, and usage of variables and functions.
- If there are no syntax or semantic errors, the compiler generates assembly language or machine code instructions.
3. Assembly:
- If the compiler generates assembly code, the assembly step converts the assembly code into machine code.
- The assembly code is transformed into binary instructions that the processor can understand.
- The output of this step is an object file, which contains the machine code instructions and some metadata.
4. Linking:
- If your program consists of multiple source files or uses external libraries, the linker combines the object files generated in the previous step.
- The linker resolves references to functions and variables across different object files by matching symbols.
- It also resolves references to external libraries by linking them to the program.
- The linker performs memory layout, determining the location of functions and variables in memory.
- It generates the final executable file, which can be run by the operating system.
5. Loading and Execution:
- The operating system loads the executable file into memory.
- It allocates memory for global and static variables.
- Registers and stacks are set up, and any necessary system resources are allocated.
- The program's entry point (usually the 'main' function in C) is located.
- Execution begins, and the processor executes the instructions in the loaded executable file.
During the compilation process, various optimizations may be performed by the compiler and linker to improve the efficiency and performance of the resulting executable code. These optimizations can include dead code elimination, constant folding, loop unrolling, and many more.
Memory Layout in C Programs
When a C program runs, its memory is divided into distinct segments:
- Text
(or Code) : Stores compiled instructions (functions, logic). It is
Read-Only to prevent modifications.
- Data : Hold global and static variables. It will retain values throughout execution.
- Heap : Used for dynamic memory allocation (using malloc, calloc, realloc). Its size can grow/shrink at run time.
- Stack : Stores local variables and function call information. It is managed automatically using push/pop operations.
Types of Memory Allocation
1. Static Memory Allocation
- Done
at compile time.
- Variables
like int x; are allocated fixed memory.
- Memory
size cannot change during runtime.
- Managed
by the compiler.
2. Dynamic Memory Allocation
- Done
at runtime using functions from <stdlib.h>.
- Provides
flexibility to allocate memory as needed.
Common functions:
- malloc(size)
→ Allocates memory block of given size.
- calloc(num,
size) → Allocates memory for an array, initializes to zero.
- realloc(ptr,
new_size) → Resizes previously allocated memory.
- free(ptr) → Deallocates memory to avoid leaks.
Example :
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int*) malloc(5 * sizeof(int)); // allocate 5 integers
#include <stdlib.h>
int main() {
int *arr = (int*) malloc(5 * sizeof(int)); // allocate 5 integers
if (arr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++)
arr[i] = i + 1;
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++)
arr[i] = i + 1;
for (int i = 0; i < 5; i++)
printf("%d ", arr[i]);
free(arr); // release memory
return 0;
}
Common Issues in Memory Management
- Memory
Leaks: Forgetting to free() allocated memory keeps it reserved
unnecessarily.
- Dangling
Pointers: Using a pointer after freeing its memory.
- Double
Free: Calling free() twice on the same pointer.
- Buffer Overflow: Writing beyond allocated memory size.
- Fragmentation: Frequent allocations/deallocations can leave scattered free blocks, reducing efficiency.
Best Practices
- Always
pair malloc/calloc/realloc with free.
- Initialize
pointers to NULL after freeing.
- Use
tools like Valgrind to detect leaks and invalid memory access.
- Prefer stack allocation for small, short-lived variables.
- Be cautious with pointer arithmetic.
Memory Leak in C:
In C, a memory leak refers to a situation where dynamically allocated memory is not properly deallocated or freed when it is no longer needed. As a result, the allocated memory remains in use even though it cannot be accessed or used by the program anymore. Memory leaks can lead to a gradual depletion of available memory, which can eventually cause performance issues or program crashes.
There are several causes of memory leaks in C programs:
1. Forgetting to deallocate memory: If you allocate memory dynamically using functions like malloc(), calloc(), or realloc(), it is important to free that memory using the free() function when it is no longer needed. Forgetting to deallocate memory can result in a memory leak.
2. Incorrect deallocation: It is essential to ensure that memory is deallocated properly and at the right time. If you deallocate memory too early or too late, it can cause memory leaks. For example, deallocating memory inside a loop without considering all iterations can lead to memory leaks.
3. Overwriting memory pointers: If you overwrite a pointer to dynamically allocated memory without freeing it first, you lose the reference to that memory and cannot deallocate it. This can happen, for instance, when assigning a new memory address to a pointer without freeing the previous memory.
4. Global variables: If you allocate memory to a global variable and forget to deallocate it before the program terminates, the memory will not be released, resulting in a memory leak. Global variables persist throughout the entire program's execution.
5. Circular references: In situations where data structures or objects reference each other in a circular manner, it can prevent memory from being deallocated even if it is no longer in use. This is commonly found in situations where garbage collection is not available, as in C.
6. Exception handling: If an exception occurs and memory allocated in the corresponding try block is not freed in the catch block, it can lead to memory leaks.
To avoid memory leaks, it is important to follow good memory management practices. Always free dynamically allocated memory when it is no longer needed and ensure that deallocation is performed correctly and at the appropriate time. Additionally, using tools like memory profilers or leak detection tools can help identify and locate memory leaks in C programs.
There are several causes of memory leaks in C programs:
1. Forgetting to deallocate memory: If you allocate memory dynamically using functions like malloc(), calloc(), or realloc(), it is important to free that memory using the free() function when it is no longer needed. Forgetting to deallocate memory can result in a memory leak.
2. Incorrect deallocation: It is essential to ensure that memory is deallocated properly and at the right time. If you deallocate memory too early or too late, it can cause memory leaks. For example, deallocating memory inside a loop without considering all iterations can lead to memory leaks.
3. Overwriting memory pointers: If you overwrite a pointer to dynamically allocated memory without freeing it first, you lose the reference to that memory and cannot deallocate it. This can happen, for instance, when assigning a new memory address to a pointer without freeing the previous memory.
4. Global variables: If you allocate memory to a global variable and forget to deallocate it before the program terminates, the memory will not be released, resulting in a memory leak. Global variables persist throughout the entire program's execution.
5. Circular references: In situations where data structures or objects reference each other in a circular manner, it can prevent memory from being deallocated even if it is no longer in use. This is commonly found in situations where garbage collection is not available, as in C.
6. Exception handling: If an exception occurs and memory allocated in the corresponding try block is not freed in the catch block, it can lead to memory leaks.
To avoid memory leaks, it is important to follow good memory management practices. Always free dynamically allocated memory when it is no longer needed and ensure that deallocation is performed correctly and at the appropriate time. Additionally, using tools like memory profilers or leak detection tools can help identify and locate memory leaks in C programs.
Comments
Post a Comment