File Input/Output (I/O) is a fundamental aspect of C programming that enables applications to persist data, read configuration files, process large datasets, and interact with the operating system. Unlike higher-level languages that abstract away file handling complexities, C provides a powerful yet granular set of functions that give developers precise control over file operations. This guide explores everything from basic file operations to advanced techniques for robust file handling.
Understanding File I/O in C
C treats files as streams of bytes, whether they're text files, binary files, or even hardware devices. The standard I/O library (stdio.h) provides functions for file operations, building on the concept of file pointers (FILE*) that serve as handles to open files.
Key Concepts:
- Stream: A logical interface to a file or device
- File Pointer: A pointer to a
FILEstructure that contains stream information - Buffer: Temporary storage that improves I/O performance
- File Position: Current location within a file for read/write operations
Opening and Closing Files
1. The fopen() Function
The foundation of file I/O is opening a file with fopen():
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file;
// Open file for reading
file = fopen("example.txt", "r");
if (file == NULL) {
printf("Error: Could not open file\n");
return 1;
}
// File operations go here
// Always close the file
fclose(file);
return 0;
}
File Opening Modes:
| Mode | Description | File Exists | File Doesn't Exist |
|---|---|---|---|
"r" | Read | Opens existing file | Returns NULL |
"w" | Write | Truncates to zero length | Creates new file |
"a" | Append | Opens, positions at end | Creates new file |
"r+" | Read/Update | Opens existing file | Returns NULL |
"w+" | Write/Update | Truncates to zero length | Creates new file |
"a+" | Append/Update | Opens, positions at end | Creates new file |
"rb" | Binary read | Opens existing file | Returns NULL |
"wb" | Binary write | Truncates to zero length | Creates new file |
"ab" | Binary append | Opens, positions at end | Creates new file |
2. Error Handling with fopen()
FILE* safe_open_file(const char *filename, const char *mode) {
FILE *file = fopen(filename, mode);
if (file == NULL) {
fprintf(stderr, "Error opening file '%s': ", filename);
perror(""); // Prints system error message
return NULL;
}
return file;
}
int main() {
FILE *file = safe_open_file("config.txt", "r");
if (file == NULL) {
return EXIT_FAILURE;
}
// Process file...
fclose(file);
return EXIT_SUCCESS;
}
Reading from Files
1. Character-by-Character Reading with fgetc()
void read_file_char_by_char(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
perror("Error opening file");
return;
}
int ch;
while ((ch = fgetc(file)) != EOF) {
putchar(ch); // Print each character
}
if (feof(file)) {
printf("\nEnd of file reached.\n");
} else if (ferror(file)) {
printf("Error reading file.\n");
}
fclose(file);
}
2. Line-by-Line Reading with fgets()
void read_file_line_by_line(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
perror("Error opening file");
return;
}
char buffer[256];
int line_number = 1;
while (fgets(buffer, sizeof(buffer), file) != NULL) {
// Remove trailing newline if present
buffer[strcspn(buffer, "\n")] = 0;
printf("Line %d: %s\n", line_number++, buffer);
}
fclose(file);
}
3. Formatted Reading with fscanf()
typedef struct {
char name[50];
int age;
double salary;
} Employee;
void read_employee_data(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
perror("Error opening file");
return;
}
Employee emp;
int count = 0;
// Read formatted data
while (fscanf(file, "%49s %d %lf", emp.name, &emp.age, &emp.salary) == 3) {
printf("Employee %d: %s, %d years, $%.2f\n",
++count, emp.name, emp.age, emp.salary);
}
fclose(file);
}
4. Reading Binary Data with fread()
typedef struct {
int id;
char name[50];
double balance;
} Record;
void read_binary_records(const char *filename) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
perror("Error opening file");
return;
}
Record rec;
size_t bytes_read;
while ((bytes_read = fread(&rec, sizeof(Record), 1, file)) == 1) {
printf("ID: %d, Name: %s, Balance: %.2f\n",
rec.id, rec.name, rec.balance);
}
fclose(file);
}
Writing to Files
1. Character-by-Character Writing with fputc()
void write_characters_to_file(const char *filename) {
FILE *file = fopen(filename, "w");
if (file == NULL) {
perror("Error opening file for writing");
return;
}
const char *message = "Hello, World!\n";
for (int i = 0; message[i] != '\0'; i++) {
fputc(message[i], file);
}
printf("Successfully wrote to %s\n", filename);
fclose(file);
}
2. String Writing with fputs()
void write_lines_to_file(const char *filename) {
FILE *file = fopen(filename, "w");
if (file == NULL) {
perror("Error opening file for writing");
return;
}
const char *lines[] = {
"First line",
"Second line",
"Third line",
NULL
};
for (int i = 0; lines[i] != NULL; i++) {
if (fputs(lines[i], file) == EOF) {
printf("Error writing line %d\n", i);
break;
}
fputc('\n', file); // Add newline
}
printf("Successfully wrote %d lines\n", 3);
fclose(file);
}
3. Formatted Writing with fprintf()
void write_formatted_data(const char *filename) {
FILE *file = fopen(filename, "w");
if (file == NULL) {
perror("Error opening file for writing");
return;
}
// Write header
fprintf(file, "%-20s %10s %15s\n", "Name", "Age", "Salary");
fprintf(file, "%s\n", "----------------------------------------");
// Write data rows
fprintf(file, "%-20s %10d %15.2f\n", "John Doe", 30, 55000.50);
fprintf(file, "%-20s %10d %15.2f\n", "Jane Smith", 28, 62000.75);
fprintf(file, "%-20s %10d %15.2f\n", "Bob Johnson", 35, 48500.00);
fclose(file);
}
4. Writing Binary Data with fwrite()
void write_binary_records(const char *filename) {
FILE *file = fopen(filename, "wb");
if (file == NULL) {
perror("Error opening file for binary writing");
return;
}
Record records[] = {
{1, "Alice", 1000.50},
{2, "Bob", 2500.75},
{3, "Charlie", 3200.25},
{4, "Diana", 1800.00}
};
size_t num_records = sizeof(records) / sizeof(records[0]);
size_t written = fwrite(records, sizeof(Record), num_records, file);
if (written == num_records) {
printf("Successfully wrote %zu records\n", written);
} else {
printf("Error: Only wrote %zu of %zu records\n", written, num_records);
}
fclose(file);
}
File Positioning
1. Using ftell() and fseek()
void demonstrate_file_positioning(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
perror("Error opening file");
return;
}
// Get current position (should be 0)
long position = ftell(file);
printf("Initial position: %ld\n", position);
// Read first 10 characters
char buffer[11] = {0};
fread(buffer, 1, 10, file);
printf("Read: '%s'\n", buffer);
// Get new position
position = ftell(file);
printf("After reading 10 bytes: %ld\n", position);
// Seek back to beginning
fseek(file, 0, SEEK_SET);
position = ftell(file);
printf("After seeking to start: %ld\n", position);
// Seek to 20 bytes from end
fseek(file, -20, SEEK_END);
position = ftell(file);
printf("20 bytes from end: %ld\n", position);
fclose(file);
}
2. Using fgetpos() and fsetpos()
void demonstrate_fgetpos_fsetpos(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
perror("Error opening file");
return;
}
fpos_t pos;
// Save current position
fgetpos(file, &pos);
// Read some data
char buffer[50];
fgets(buffer, sizeof(buffer), file);
printf("Read: %s", buffer);
// Restore saved position
fsetpos(file, &pos);
// Read again - should read the same line
fgets(buffer, sizeof(buffer), file);
printf("Read again: %s", buffer);
fclose(file);
}
Advanced File Operations
1. Temporary Files
void demonstrate_temp_files() {
char temp_filename[] = "/tmp/tempfileXXXXXX";
int fd = mkstemp(temp_filename);
if (fd == -1) {
perror("Error creating temp file");
return;
}
// Convert file descriptor to FILE stream
FILE *temp_file = fdopen(fd, "w+");
if (temp_file == NULL) {
perror("Error opening temp file stream");
close(fd);
return;
}
// Write to temp file
fprintf(temp_file, "Temporary data");
rewind(temp_file);
// Read back
char buffer[100];
fgets(buffer, sizeof(buffer), temp_file);
printf("Temp file contains: %s\n", buffer);
fclose(temp_file);
unlink(temp_filename); // Delete the file
}
2. Memory-Mapped Files (Linux/Unix)
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
void demonstrate_mmap(const char *filename) {
int fd = open(filename, O_RDWR);
if (fd == -1) {
perror("Error opening file");
return;
}
// Get file size
struct stat st;
fstat(fd, &st);
size_t filesize = st.st_size;
// Map file into memory
char *data = mmap(NULL, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
perror("Error mapping file");
close(fd);
return;
}
// Access file as memory
printf("First 50 bytes: %.50s\n", data);
// Modify file (if writable)
if (filesize > 0) {
data[0] = toupper(data[0]);
}
// Clean up
munmap(data, filesize);
close(fd);
}
3. Directory Operations
#include <dirent.h>
#include <sys/stat.h>
void list_directory_contents(const char *path) {
DIR *dir = opendir(path);
if (dir == NULL) {
perror("Error opening directory");
return;
}
struct dirent *entry;
struct stat file_stat;
char full_path[1024];
printf("Contents of '%s':\n", path);
printf("%-30s %10s %s\n", "Name", "Size", "Type");
printf("%s\n", "----------------------------------------");
while ((entry = readdir(dir)) != NULL) {
// Skip . and ..
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0) {
continue;
}
// Build full path
snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
if (stat(full_path, &file_stat) == 0) {
const char *type = S_ISDIR(file_stat.st_mode) ? "DIR" : "FILE";
printf("%-30s %10ld %s\n",
entry->d_name, file_stat.st_size, type);
}
}
closedir(dir);
}
Error Handling and Best Practices
1. Comprehensive Error Handling
typedef enum {
FILE_SUCCESS = 0,
FILE_ERR_OPEN,
FILE_ERR_READ,
FILE_ERR_WRITE,
FILE_ERR_SEEK,
FILE_ERR_CLOSE
} FileErrorCode;
typedef struct {
FileErrorCode code;
char message[256];
} FileResult;
FileResult safe_file_copy(const char *src, const char *dest) {
FileResult result = {FILE_SUCCESS, ""};
FILE *source = fopen(src, "rb");
if (source == NULL) {
result.code = FILE_ERR_OPEN;
snprintf(result.message, sizeof(result.message),
"Cannot open source file: %s", src);
return result;
}
FILE *destination = fopen(dest, "wb");
if (destination == NULL) {
result.code = FILE_ERR_OPEN;
snprintf(result.message, sizeof(result.message),
"Cannot open destination file: %s", dest);
fclose(source);
return result;
}
char buffer[8192];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), source)) > 0) {
if (fwrite(buffer, 1, bytes_read, destination) != bytes_read) {
result.code = FILE_ERR_WRITE;
strcpy(result.message, "Error writing to destination");
break;
}
}
if (ferror(source)) {
result.code = FILE_ERR_READ;
strcpy(result.message, "Error reading from source");
}
fclose(source);
fclose(destination);
return result;
}
2. Buffering Control
void demonstrate_buffering() {
FILE *file = fopen("buffered.txt", "w");
// Set full buffering with custom buffer
char buffer[4096];
setvbuf(file, buffer, _IOFBF, sizeof(buffer));
// These writes will be buffered
for (int i = 0; i < 1000; i++) {
fprintf(file, "Line %d\n", i);
}
// Force write of buffer
fflush(file);
// Switch to line buffering
setvbuf(file, NULL, _IOLBF, 0);
// These will be written when newline is encountered
fprintf(file, "This will be written immediately\n");
fprintf(file, "This too\n");
fclose(file);
}
3. Atomic Operations with flock()
#include <sys/file.h>
void safe_write_with_lock(const char *filename, const char *data) {
FILE *file = fopen(filename, "a");
if (file == NULL) {
perror("Error opening file");
return;
}
int fd = fileno(file);
// Acquire exclusive lock
if (flock(fd, LOCK_EX) == -1) {
perror("Error locking file");
fclose(file);
return;
}
// Write data (now safe from concurrent access)
fprintf(file, "%s\n", data);
fflush(file); // Ensure data is written
// Release lock
flock(fd, LOCK_UN);
fclose(file);
}
Performance Optimization
1. Efficient Reading with Larger Buffers
void efficient_file_copy(const char *src, const char *dest) {
FILE *source = fopen(src, "rb");
FILE *destination = fopen(dest, "wb");
if (!source || !destination) {
perror("Error opening files");
return;
}
// Use large buffer for efficiency
char *buffer = malloc(65536); // 64KB buffer
if (buffer == NULL) {
perror("Memory allocation failed");
return;
}
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, 65536, source)) > 0) {
fwrite(buffer, 1, bytes_read, destination);
}
free(buffer);
fclose(source);
fclose(destination);
}
2. Reading Entire File into Memory
char* read_entire_file(const char *filename, long *file_size) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
return NULL;
}
// Get file size
fseek(file, 0, SEEK_END);
*file_size = ftell(file);
rewind(file);
// Allocate memory
char *buffer = malloc(*file_size + 1);
if (buffer == NULL) {
fclose(file);
return NULL;
}
// Read entire file
size_t bytes_read = fread(buffer, 1, *file_size, file);
if (bytes_read != *file_size) {
free(buffer);
fclose(file);
return NULL;
}
buffer[*file_size] = '\0'; // Null terminate
fclose(file);
return buffer;
}
Practical Examples
1. CSV File Parser
typedef struct {
char **fields;
int field_count;
} CSVRow;
typedef struct {
CSVRow *rows;
int row_count;
int capacity;
} CSVData;
CSVData* parse_csv(const char *filename, char delimiter) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
return NULL;
}
CSVData *data = malloc(sizeof(CSVData));
data->rows = NULL;
data->row_count = 0;
data->capacity = 0;
char line[4096];
while (fgets(line, sizeof(line), file)) {
// Remove trailing newline
line[strcspn(line, "\n")] = 0;
// Count fields
int field_count = 1;
for (char *p = line; *p; p++) {
if (*p == delimiter) field_count++;
}
// Allocate row
CSVRow row;
row.fields = malloc(field_count * sizeof(char*));
row.field_count = 0;
// Parse fields
char *token = strtok(line, &delimiter);
while (token != NULL) {
row.fields[row.field_count] = malloc(strlen(token) + 1);
strcpy(row.fields[row.field_count], token);
row.field_count++;
token = strtok(NULL, &delimiter);
}
// Add to data structure
if (data->row_count >= data->capacity) {
data->capacity = data->capacity == 0 ? 10 : data->capacity * 2;
data->rows = realloc(data->rows,
data->capacity * sizeof(CSVRow));
}
data->rows[data->row_count++] = row;
}
fclose(file);
return data;
}
void free_csv_data(CSVData *data) {
for (int i = 0; i < data->row_count; i++) {
for (int j = 0; j < data->rows[i].field_count; j++) {
free(data->rows[i].fields[j]);
}
free(data->rows[i].fields);
}
free(data->rows);
free(data);
}
2. Configuration File Reader
typedef struct {
char key[128];
char value[256];
} ConfigEntry;
typedef struct {
ConfigEntry *entries;
int count;
int capacity;
} Config;
Config* read_config(const char *filename) {
Config *config = malloc(sizeof(Config));
config->entries = NULL;
config->count = 0;
config->capacity = 0;
FILE *file = fopen(filename, "r");
if (file == NULL) {
free(config);
return NULL;
}
char line[512];
while (fgets(line, sizeof(line), file)) {
// Skip comments and empty lines
if (line[0] == '#' || line[0] == '\n') {
continue;
}
// Remove trailing newline
line[strcspn(line, "\n")] = 0;
// Split at '='
char *equals = strchr(line, '=');
if (equals == NULL) continue;
*equals = '\0';
char *key = line;
char *value = equals + 1;
// Trim whitespace
while (isspace(*key)) key++;
while (isspace(*value)) value++;
// Add to config
if (config->count >= config->capacity) {
config->capacity = config->capacity == 0 ? 10 : config->capacity * 2;
config->entries = realloc(config->entries,
config->capacity * sizeof(ConfigEntry));
}
strncpy(config->entries[config->count].key, key, 127);
strncpy(config->entries[config->count].value, value, 255);
config->count++;
}
fclose(file);
return config;
}
const char* get_config_value(Config *config, const char *key) {
for (int i = 0; i < config->count; i++) {
if (strcmp(config->entries[i].key, key) == 0) {
return config->entries[i].value;
}
}
return NULL;
}
Common Pitfalls and Solutions
1. Buffer Overflow Protection
// UNSAFE
char buffer[100];
fscanf(file, "%s", buffer); // No bounds checking!
// SAFE
char buffer[100];
fscanf(file, "%99s", buffer); // Limit input size
// SAFER
char buffer[100];
if (fgets(buffer, sizeof(buffer), file) == NULL) {
// Handle error
}
2. End-of-File and Error Distinction
// WRONG - can't distinguish EOF from error
while (!feof(file)) {
fgets(buffer, sizeof(buffer), file);
// Process buffer
}
// CORRECT
while (fgets(buffer, sizeof(buffer), file) != NULL) {
// Process buffer
}
// Check for errors after loop
if (ferror(file)) {
printf("Error reading file\n");
}
3. Portability Considerations
// NOT portable (Windows vs Unix line endings) fprintf(file, "Line 1\nLine 2\n"); // Portable fprintf(file, "Line 1" NEWLINE "Line 2" NEWLINE);
Conclusion
File I/O in C provides developers with powerful, low-level control over data persistence and retrieval. From basic text file operations to advanced memory-mapped I/O, the standard library offers a comprehensive set of tools for every file handling need. Understanding buffering, error handling, and performance optimization techniques is essential for writing robust, efficient file operations.
Key takeaways:
- Always check return values and handle errors gracefully
- Choose the right file opening mode for your needs
- Use appropriate buffer sizes for optimal performance
- Distinguish between text and binary modes when necessary
- Remember to close files to prevent resource leaks
- Consider portability when working across different platforms
Mastering file I/O in C is essential for systems programming, data processing, and building applications that interact persistently with the operating system. With the techniques covered in this guide, you'll be well-equipped to handle any file operation challenge in your C programs.
Complete C Programming Guide + Compilers Collection
1. C srand() Function â Understanding Seed Initialization
https://macronepal.com/understanding-the-c-srand-function
Explains how srand() initializes the pseudo-random number generator in C by setting a seed value. Using the same seed produces the same sequence, while time(NULL) gives different results each run.
2. C rand() Function Mechanics and Limitations
https://macronepal.com/c-rand-function-mechanics-and-limitations
Explains how rand() generates pseudo-random numbers between 0 and RAND_MAX, its deterministic nature, and limitations for security use cases.
3. C log() Function
https://macronepal.com/c-log-function-2
Covers natural logarithm calculation using <math.h> and its applications.
4. Mastering Date and Time in C
https://macronepal.com/mastering-date-and-time-in-c
Explains <time.h> functions like time(), clock(), difftime(), and struct tm.
5. Mastering time_t Type in C
https://macronepal.com/mastering-the-c-time_t-type-for-time-management
Explains time representation as seconds since Unix epoch and conversion functions.
6. C exp() Function
https://macronepal.com/c-exp-function-mechanics-and-implementation
Explains exponential function exp(x) and its scientific applications.
7. C log() Function (Alternate Guide)
https://macronepal.com/c-log-function
Comparison of log() and log10() with usage examples.
8. C log10() Function
https://macronepal.com/mastering-the-log10-function-in-c
Explains base-10 logarithm for engineering and scientific applications.
9. C tan() Function
https://macronepal.com/understanding-the-c-tan-function
Explains tangent function and radian-based calculations.
10. Random Numbers in C (Secure vs Predictable)
https://macronepal.com/mastering-c-random-numbers-for-secure-and-predictable-applications
Explains difference between rand() and secure randomness methods.
11. Free Online C Compiler
https://macronepal.com/free-online-c-code-compiler-2
Browser-based compiler for testing C programs instantly.
C Functions, Arguments, Parameters & Flow
Mastering Functions in C â Complete Guide
https://macronepal.com/c/mastering-functions-in-c-a-complete-guide/
Covers function structure, modular programming, and real-world usage.
Function Arguments in C
https://macronepal.com/c-function-arguments/
Explains how arguments are passed and used in function calls.
Function Parameters in C
https://macronepal.com/c-function-parameters/
Explains defining inputs for functions and matching them with arguments.
Function Declarations in C
https://macronepal.com/c-function-declarations-syntax-rules-and-best-practices/
Covers prototypes, syntax rules, and best practices.
Function Calls in C
https://macronepal.com/understanding-function-calls-in-c-syntax-mechanics-and-best-practices/
Explains execution flow and parameter handling during function calls.
Void Functions in C
https://macronepal.com/understanding-void-functions-in-c-syntax-patterns-and-best-practices/
Explains functions that do not return values.
Return Values in C
https://macronepal.com/c-return-values-mechanics-types-and-best-practices/
Explains different return types and how functions return results.
Pass-by-Value in C
https://macronepal.com/aws/understanding-pass-by-value-in-c-mechanics-implications-and-best-practices/
Explains how copies of variables are passed into functions.
Pass-by-Reference in C
https://macronepal.com/c/understanding-pass-by-reference-in-c-pointers-semantics-and-safe-practices/
Explains using pointers to modify original variables.
C strstr() Function
https://macronepal.com/aws/c-strstr-function/
Explains substring search inside strings in C.
C Preprocessor & Macros
https://macronepal.com/mastering-c-variadic-macros-for-flexible-debugging/
https://macronepal.com/mastering-the-stdc-macro-in-c/
https://macronepal.com/c-time-macro-mechanics-and-usage/
https://macronepal.com/understanding-the-c-date-macro/
https://macronepal.com/c-file-type/
https://macronepal.com/mastering-c-line-macro-for-debugging-and-diagnostics/
https://macronepal.com/mastering-predefined-macros-in-c/
https://macronepal.com/c-error-directive-mechanics-and-usage/
https://macronepal.com/understanding-the-c-pragma-directive/
https://macronepal.com/c-include-directive/
C Structures, Memory, Scope & Linkage
https://macronepal.com/mastering-structures-in-c/
https://macronepal.com/c-structure-declaration-mechanics-and-usage/
https://macronepal.com/c-structure-initialization-mechanics-and-best-practices/
https://macronepal.com/mastering-c-structure-member-access-for-reliable-data-handling/
https://macronepal.com/c-nested-structures/
https://macronepal.com/mastering-arrays-of-structures-in-c/
https://macronepal.com/c-structure-pointers-mechanics-and-implementation/
https://macronepal.com/understanding-c-structure-parameter-passing-mechanics/
https://macronepal.com/mastering-c-returning-structures-for-efficient-data-flow/
https://macronepal.com/c-self-referential-structures/
https://macronepal.com/mastering-structure-alignment-in-c/
https://macronepal.com/c-structure-padding-mechanics-and-optimization/
https://macronepal.com/understanding-c-flexible-array-members-mechanics-and-usage/
https://macronepal.com/mastering-c-anonymous-structures-for-flattened-data-layouts/
https://macronepal.com/c-unions/
https://macronepal.com/mastering-c-name-mangling-and-symbol-decoration/
https://macronepal.com/c-no-linkage-mechanics-and-scope-isolation/
https://macronepal.com/understanding-c-internal-linkage-mechanics-and-architecture/
C Scope, Storage Classes & Typedef
https://macronepal.com/mastering-function-prototype-scope-in-c/
https://macronepal.com/c-function-scope-mechanics-and-visibility/
https://macronepal.com/understanding-c-file-scope-mechanics-and-architecture/
https://macronepal.com/mastering-c-scope-rules-for-predictable-name-resolution/
https://macronepal.com/c-scope-rules/
https://macronepal.com/mastering-c-register-storage-class-for-historical-context-and-modern-alternatives/
https://macronepal.com/mastering-_thread_local-in-c/
https://macronepal.com/c-extern-storage-class-mechanics-and-usage/
https://macronepal.com/understanding-the-c-static-storage-class-mechanics-and-usage/
https://macronepal.com/c-auto-storage-class/
https://macronepal.com/c-typedef-with-pointers/
Extra Articles
https://macronepal.com/13757-2/
https://macronepal.com/13748-2/
https://macronepal.com/13747-2/
https://macronepal.com/13746-2/
https://macronepal.com/13745-2/
https://macronepal.com/13708-2/
https://macronepal.com/13707-2/
https://macronepal.com/13702-2/
Online Compilers
https://macronepal.com/free-html-online-code-compiler/
https://macronepal.com/free-online-python-code-compiler/
https://macronepal.com/free-online-python2-code-compiler/
https://macronepal.com/free-online-java-code-compiler/
https://macronepal.com/free-online-javascript-code-compiler/
https://macronepal.com/free-online-node-js-code-compiler/
https://macronepal.com/free-online-c-code-compiler/
https://macronepal.com/free-online-c-code-compiler-2/
https://macronepal.com/free-online-c-code-compiler-3/
https://macronepal.com/free-online-php-code-compiler/
https://macronepal.com/free-online-ruby-code-compiler/
https://macronepal.com/free-online-perl-code-compiler/
https://macronepal.com/free-online-lua-code-compiler/
https://macronepal.com/free-online-tcl-code-compiler/
https://macronepal.com/free-online-groovy-code-compiler/
https://macronepal.com/free-online-j-shell-code-compiler/
https://macronepal.com/free-online-haskell-code-compiler/
https://macronepal.com/free-online-scala-code-compiler/
https://macronepal.com/free-online-common-lisp-code-compiler/
https://macronepal.com/free-online-d-code-compiler/
https://macronepal.com/free-online-ada-code-compiler/
https://macronepal.com/free-erlang-code-compiler/
https://macronepal.com/free-online-assembly-code-compiler/
