Skip to main content
Compilation Toolchain
Compilation Toolchain
IOT
Rx 2h Practical #2: Libraries

Practical #3: Advanced Git & Code Quality

This session covers Git workflows for collaborative development and a set of automated tools for maintaining code quality: formatting, static analysis, and runtime bug detection.


Part A — Advanced Git (60 min)

Exercise 1 — Branching and History

Difficulty: Easy

Use your tp_makefile/ project from TP1.

  1. Initialize a Git repository and make an initial commit:

    cd tp_makefile
    git init
    git add .
    git commit -m "Initial commit: data stats project"
  2. Create and switch to a feature branch:

    git checkout -b feature/add-median
  3. Add a stats_median function to stats.c and stats.h (you can use a simple implementation — sort then pick the middle value). Commit your changes.

  4. Switch back to main and modify something else (e.g. update a printf format in main.c). Commit.

  5. Visualize the history:

    git log --oneline --graph --all

You should see two branches diverging from the initial commit.


Exercise 2 — Merge and Conflict Resolution

Difficulty: Rx
  1. From the main branch, merge the feature branch:

    git merge feature/add-median
  2. If there is no conflict, create one deliberately: before merging, modify the same line in stats.h on both branches (e.g. add a different comment on the same line), then attempt the merge.

  3. When the conflict appears, open the conflicted file. You will see markers like:

    <<<<<<< HEAD
    // current branch version
    =======
    // incoming branch version
    >>>>>>> feature/add-median
  4. Resolve the conflict by editing the file to keep the desired content. Remove all conflict markers.

  5. Stage and commit:

    git add stats.h
    git commit -m "Merge feature/add-median with conflict resolution"
  6. Verify the merge:

    git log --oneline --graph --all
Info

Use git diff to review changes before committing. Use git status to see which files are conflicted.


Exercise 3 — Stash and Rebase

Difficulty: Rx

Git Stash

Sometimes you are in the middle of work and need to switch context without committing incomplete changes. git stash saves your working directory and index to a stack.

  1. Make some changes to main.c (do not commit them):

    # add a printf or change something in main.c
    git status   # should show modified: main.c
  2. Stash the changes:

    git stash push -m "WIP: new output format"
    git status   # working tree should be clean
  3. Switch to a different branch, do some work, then come back:

    git checkout -b hotfix/typo
    # fix something in stats.h
    git add stats.h && git commit -m "fix: correct typo in header"
    git checkout main
  4. Restore your stashed changes:

    git stash list          # list saved stashes
    git stash pop           # restore the most recent stash
    git status              # your WIP changes are back
Info

Use git stash drop to discard a stash without applying it, or git stash apply stash@{N} to apply a specific entry without removing it from the stack.

Git Rebase

Rebase rewrites your branch history on top of another branch, producing a linear history.

  1. Create a new feature branch from main:

    git checkout -b feature/variance
  2. Add a stats_variance function in stats.c/stats.h. Commit.

  3. Go back to main and make another commit (e.g. update the README or Makefile):

    git checkout main
    # make a small change and commit
  4. Rebase your feature branch on top of the updated main:

    git checkout feature/variance
    git rebase main
    git log --oneline --graph --all

You should now see a linear history where feature/variance starts from the tip of main.

Questions

  1. What is the key difference between git merge and git rebase?
  2. Why is it generally unsafe to rebase a branch that has already been pushed and shared with others?
  3. When would you prefer git stash over creating a temporary commit?

Exercise 4 — Remote Collaboration

Difficulty: Rx
  1. Create a new project on GitLab (or use the one provided by your instructor).

  2. Add the remote and push:

    git remote add origin <your-gitlab-url>
    git push -u origin main
  3. Create a new branch, make a small change, push it:

    git checkout -b feature/improve-output
    # ... make changes and commit ...
    git push -u origin feature/improve-output
  4. On GitLab, open a Merge Request from feature/improve-output into main.

  5. Review the diff in the GitLab UI: look at the changed files, add a comment on a specific line.

  6. Approve and merge the MR via the UI, then pull locally:

    git checkout main
    git pull

Part B — Code Quality (60 min)

Exercise 5 — Code Formatting with clang-format

Difficulty: Easy
  1. Take the following deliberately ugly file and save it as ugly.c:
#include<stdio.h>
#include"stats.h"
int main( void ){
int arr[]={10,20,30 ,40,50};
    int n=5;
printf("Sum=%d\n",stats_sum(arr,n));
        printf( "Mean=%d\n",stats_mean(arr ,n ));
if(n>0){printf("Min=%d Max=%d\n", stats_min(arr,n),stats_max(arr,n));}
return 0;}
  1. Run clang-format on it:

    clang-format ugly.c
  2. Create a .clang-format configuration file in your project root:

    clang-format -style=llvm -dump-config > .clang-format
  3. Apply formatting in-place:

    clang-format -i ugly.c
  4. Inspect the result. Try changing the style (e.g. BasedOnStyle: Google or BasedOnStyle: Mozilla) and reformat.


Exercise 6 — Static Analysis with clang-tidy

Difficulty: Rx
  1. Run clang-tidy on one of your source files:

    clang-tidy stats.c -- -Wall -Wextra
  2. If any warnings are reported, fix them.

  3. Try running it on ugly.c (before and after formatting) — clang-tidy may report different issues than clang-format.

  4. Add Makefile targets for both tools:

    format:
    	clang-format -i $(SRCS)
    
    lint:
    	clang-tidy $(SRCS) -- $(CFLAGS)
    
    .PHONY: format lint
Info

Combining tools

In real projects, clang-format handles style (whitespace, braces) while clang-tidy catches bugs and bad practices (unused variables, unsafe functions, etc.). They complement each other.


Exercise 7 — Runtime Bug Detection with AddressSanitizer

Difficulty: Hard
  1. Save the following program as buggy.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    // Bug 1: heap buffer overflow
    char *buf = malloc(8);
    strcpy(buf, "This string is way too long for the buffer");
    printf("%s\n", buf);

    // Bug 2: use after free
    free(buf);
    printf("After free: %c\n", buf[0]);

    return 0;
}
  1. Compile with AddressSanitizer enabled:

    gcc -Wall -Wextra -g -fsanitize=address -o buggy buggy.c
  2. Run the program:

    ./buggy

    AddressSanitizer will report the first memory error it detects and abort. Read the output carefully — it tells you the exact file and line number.

  3. Fix both bugs:

    • Allocate enough memory for the string (don’t forget the null terminator)
    • Remove the use-after-free
  4. Recompile with -fsanitize=address and verify the program runs cleanly.

Warning

AddressSanitizer in practice

ASan adds ~2x memory overhead and slows execution. Use it during development and testing, not in production builds. This is why we defined a separate debug target in TP1.


Exercise 8 — Memory Leak Detection with Valgrind

Difficulty: Hard

AddressSanitizer detects invalid memory accesses. Valgrind (specifically its memcheck tool) goes further: it also reports memory leaks — allocations that were never freed.

  1. Save the following program as leaky.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *duplicate(const char *s) {
    char *copy = malloc(strlen(s) + 1);
    if (copy) strcpy(copy, s);
    return copy;
}

int main(void) {
    char *a = duplicate("hello");
    char *b = duplicate("world");

    printf("%s %s\n", a, b);

    free(a);
    // Bug: b is never freed

    return 0;
}
  1. Compile with debug symbols (no sanitizers):

    gcc -Wall -Wextra -g -o leaky leaky.c
  2. Run under Valgrind:

    valgrind --leak-check=full --track-origins=yes ./leaky
  3. Read the Valgrind report:

    • Look for LEAK SUMMARY — it distinguishes between definitely lost, indirectly lost, and still reachable memory.
    • The stack trace points to where the leaked memory was allocated.
  4. Fix the leak and re-run Valgrind. The report should end with All heap blocks were freed -- no leaks are possible.

  5. Now try running leaky (the unfixed version) under ASan:

    gcc -Wall -Wextra -g -fsanitize=address -o leaky_asan leaky.c
    ./leaky_asan

    Does ASan report the leak? Compare how the two tools handle this case.

Questions

  1. Why doesn’t AddressSanitizer always report memory leaks, while Valgrind does?
  2. What does --track-origins=yes add to the Valgrind report?
  3. When would you prefer Valgrind over ASan, and vice versa?

Summary

TopicKey commands
Git branchinggit checkout -b, git merge, git log --graph
Stashgit stash push -m "msg", git stash pop, git stash list
Rebasegit rebase <branch>
Merge conflictsEdit markers <<</===/>>>, then git add + git commit
Remote workflowgit push -u, Merge Request on GitLab
Formattingclang-format -i file.c
Static analysisclang-tidy file.c -- -Wall
Memory errorsgcc -fsanitize=address
Memory leaksvalgrind --leak-check=full ./prog