JS
C

JavaScript to C

10 lessons

Progress0%
1Variables & Types2Functions3Arrays & Pointers4Objects → Structs5Memory Management6Preprocessor & Headers7String Handling in Depth8Enums and Bitwise Operations9Bit-Fields and Unions10Multi-File Projects and Linking
All Mirror Courses
JS
C
Multi-File Projects and Linking
MirrorLesson 10 of 10
Lesson 10

Multi-File Projects and Linking

C programs split across multiple .c files with shared declarations in .h header files; the linker combines compiled object files into one executable.

Introduction

In this lesson, you'll learn about multi-file projects and linking in C. Coming from JavaScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.

Mirror Card
JS
From JavaScript:

In JavaScript, you're familiar with c programs split across multiple .c files with shared declarations in .h header files; the linker combines compiled object files into one executable..

C
In C:

C has its own approach to c programs split across multiple .c files with shared declarations in .h header files; the linker combines compiled object files into one executable., which we'll explore step by step.

The C Way

Let's see how C handles this concept. Here's a typical example:

C
C Example
/* math.h — declarations (header guard prevents double inclusion) */
#ifndef MATH_H
#define MATH_H

#define PI 3.14159

int add(int a, int b);        /* function prototype */
extern const char *VERSION;   /* extern: defined elsewhere */

#endif /* MATH_H */

/* math.c — definitions */
#include "math.h"

const char *VERSION = "1.0";

int add(int a, int b) {
    return a + b;
}

/* main.c — consumer */
#include <stdio.h>
#include "math.h"

int main(void) {
    printf("%d\n", add(2, 3));   /* 5  */
    printf("%.5f\n", PI);         /* 3.14159 */
    printf("%s\n", VERSION);      /* 1.0 */
    return 0;
}

/* Makefile (tab-indented) */
/*
all: program

program: main.o math.o
	gcc -o program main.o math.o

main.o: main.c math.h
	gcc -c main.c

math.o: math.c math.h
	gcc -c math.c

clean:
	rm -f *.o program
*/

Comparing to JavaScript

Here's how you might have written similar code in JavaScript:

JS
JavaScript (What you know)
// math.js — module
export function add(a, b) { return a + b; }
export const PI = 3.14159;

// main.js
import { add, PI } from "./math.js";
console.log(add(2, 3));   // 5
console.log(PI);           // 3.14159
Mirror Card
JS
From JavaScript:

You may be used to different syntax or behavior.

C
In C:

JS modules use import/export; C uses #include to paste header declarations, then the linker resolves references

Mirror Card
JS
From JavaScript:

You may be used to different syntax or behavior.

C
In C:

Header (.h) files contain declarations (prototypes, extern, #define, typedefs); source (.c) files contain definitions

Mirror Card
JS
From JavaScript:

You may be used to different syntax or behavior.

C
In C:

Include guards (#ifndef MATH_H … #define MATH_H … #endif) prevent a header from being included twice in one translation unit

Mirror Card
JS
From JavaScript:

You may be used to different syntax or behavior.

C
In C:

extern tells the compiler a variable/function is defined in another file — the linker will find it

Mirror Card
JS
From JavaScript:

You may be used to different syntax or behavior.

C
In C:

Makefile: each target has dependencies and a tab-indented shell command; make only rebuilds what changed

Step-by-Step Breakdown

1. Create the Header File

Put declarations in math.h: function prototypes, extern variable declarations, macros, and typedefs. Always wrap with include guards.

JS
JavaScript
// math.js: export function add(a, b) { return a + b; }
C
C
/* math.h */
#ifndef MATH_H
#define MATH_H

int add(int a, int b);  /* prototype only */
extern const int MAX;   /* defined in math.c */

#endif

2. Implement in the Source File

math.c #includes its own header (so the compiler checks that the prototype matches the definition) and provides the actual implementations.

C
C
/* math.c */
#include "math.h"

const int MAX = 1000;

int add(int a, int b) {
    return a + b;
}

3. Use in main.c

#include the header in every file that uses the declarations. The compiler produces .o object files; the linker combines them.

JS
JavaScript
// main.js
import { add } from './math.js';
C
C
/* main.c */
#include <stdio.h>
#include "math.h"

int main(void) {
    printf("%d\n", add(2, 3));
    return 0;
}

4. Build with gcc or make

Compile each .c file to an object file, then link. A Makefile automates this and only recompiles files whose source or headers changed.

C
C
# Manual:
gcc -c math.c -o math.o
gcc -c main.c -o main.o
gcc -o program main.o math.o

# Or with make:
make        # builds 'all' target
make clean  # removes *.o and program
Rule of Thumb
One .h per .c module. Never #include a .c file. Always use include guards.

Common Mistakes

When coming from JavaScript, developers often make these mistakes:

  • JS modules use import/export; C uses #include to paste header declarations, then the linker resolves references
  • Header (.h) files contain declarations (prototypes, extern, #define, typedefs); source (.c) files contain definitions
  • Include guards (#ifndef MATH_H … #define MATH_H … #endif) prevent a header from being included twice in one translation unit
Common Pitfall
Don't assume C works exactly like JavaScript. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • Header files (.h) hold declarations; source files (.c) hold definitions — #include pastes the header text
  • Include guards (#ifndef / #define / #endif) prevent duplicate declarations when a header is included from multiple files
  • extern declares a variable defined in another translation unit — the linker resolves it at link time
  • Compile each .c to .o with gcc -c, then link all .o files together; make automates incremental builds
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your JavaScript code in C to practice these concepts.
PreviousFinish