My Learning Journey: Understanding C

Community Article Published March 9, 2025

Hey everyone! I'm just starting with C programming, and I wanted to document my learning journey by breaking down this program. This code is from alex the dev im just adding comments on the code so i understand what's going on. Currently im on chapter 5 of c&c if you want to compare. formatting thanks to copilot


1. Including the Necessary Libraries

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

What I Learned:

  • stdlib.h:
    Provides functions for dynamic memory allocation (malloc, realloc, free).
  • stdio.h:
    Contains functions for input/output operations such as printf, file operations (fopen, fread, fclose).
  • string.h:
    Offers string handling functions like strcmp (to compare strings) and memcpy (to copy memory).

2. Defining a Structure to Hold Arguments

typedef struct arguments {
    char **files;
    unsigned int files_count;
} arguments;
  • Structures (struct) are used in C to group related variables together.
  • Members of the Structure:
    • char **files:
      This is a double pointer.
      • A char * represents a single string (an array of characters).
      • A char ** represents an array of strings, meaning it can hold multiple file names.
    • unsigned int files_count:
      Keeps track of how many file names have been provided. Using unsigned int ensures the number is never negative.

3. Parsing Command-Line Arguments

void parse_arguments(int argc, char **argv, arguments *args) {
    args->files = malloc(argc * sizeof(char *));
    int index = 0;

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--help") == 0) {
            printf("Usage: ./main [file1] or [--help]
");
            exit(0);
        } else {
            args->files[index] = argv[i];
            index++;
        }
    }

    args->files_count = index;
}

What I Learned:

  • Function Parameters:
    • int argc: Number of command-line arguments.
    • char **argv: Array of strings, where each string is one command-line argument.
    • arguments *args: A pointer to our arguments structure so the function can modify it.
  • Dynamic Memory Allocation:
    • args->files = malloc(argc * sizeof(char *));
      Here we allocate memory for an array of pointers. The -> operator is used because args is a pointer to a structure. It’s a shorthand for (*args).files.
  • Looping Through Arguments:
    • We start at index 1 (since argv[0] is the program name).
    • If an argument is "--help", the program prints a help message and exits.
    • Other arguments are assumed to be file names and stored in the files array.
  • Storing the File Count:
    • After the loop, the number of file names stored is saved in args->files_count.

4. Reading a File into Memory

#define MAX_LEN 128

int read_file(char *path, char **buffer) {
    int tmp_capacity = MAX_LEN;
    char *tmp = malloc(tmp_capacity * sizeof(char));
    int tmp_size = 0;
    
    if (tmp == NULL) {
        perror("Memory allocation error");
        exit(1);
    }
    
    FILE *f = fopen(path, "r");
    if (f == NULL) {
        perror("File opening error");
        exit(1);
    }
    
    int size = 0;
    do {
        if (tmp_size + MAX_LEN > tmp_capacity) {
            tmp_capacity *= 2;
            tmp = realloc(tmp, tmp_capacity * sizeof(char));
            if (tmp == NULL) {
                perror("Memory allocation error");
                exit(1);
            }
        }
        size = fread(tmp + tmp_size, sizeof(char), MAX_LEN, f);
        tmp_size += size;
    } while (size > 0);
    
    fclose(f);
    tmp[tmp_size] = '\0';
    *buffer = tmp;
    
    return tmp_size;
}

What I Learned:

  • Defining Constants:
    • #define MAX_LEN 128 sets the size of chunks to read from the file.
  • Memory Allocation for File Content:
    • Initially, a buffer tmp of size MAX_LEN is allocated.
    • If more space is needed as we read the file, realloc is used to double the buffer size.
  • File Operations:
    • The file is opened with fopen. If opening fails, an error is printed.
    • The file is read in chunks using fread, and the pointer arithmetic (tmp + tmp_size) makes sure new data is appended correctly.
  • Buffer Finalization:
    • After reading, the file is closed and a null terminator ('\0') is added to mark the end of the string.
    • The pointer to the allocated buffer is returned through *buffer.

5. The Main Function: Putting Everything Together

int main(int argc, char **argv) {
    arguments args = {0};
    parse_arguments(argc, argv, &args);

    char *buffer = NULL;
    int buffer_size = 0;
    
    for (int i = 0; i < args.files_count; i++) {
        char *content = NULL;
        int size = read_file(args.files[i], &content);

        buffer = realloc(buffer, buffer_size + size + 1);
        memcpy(buffer + buffer_size, content, size);
        buffer_size += size;

        free(content);
    }

    printf("%s\n", buffer);
    free(buffer);
    free(args.files);   
    
    return 0;
}

πŸš€ Final Thoughts

I really learned a lot while documenting this program!
If you’re also learning C, I hope this helps. If you have any questions (or if I explained something wrong πŸ˜…), let me know!

Happy coding! πŸŽ‰

Community

Sign up or log in to comment