JV
C

Java to C

10 lessons

Progress0%
1Introduction to C2Data Types3Pointers4Strings5Memory Management6Structs vs Classes7Preprocessor and Header Files8Multi-File Programs9Input / Output and Command-Line Args10Arrays: Sorting and Searching
All Mirror Courses
JV
C
Multi-File Programs
MirrorLesson 8 of 10
Lesson 8

Multi-File Programs

Splitting C code across .h/.c files, extern, linkage, and basic Makefile

Introduction

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

Mirror Card
JV
From Java:

In Java, you're familiar with splitting c code across .h/.c files, extern, linkage, and basic makefile.

C
In C:

C has its own approach to splitting c code across .h/.c files, extern, linkage, and basic makefile, 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
// Project layout:
// calculator.h
// calculator.c
// main.c
// Makefile

// calculator.h
#ifndef CALCULATOR_H
#define CALCULATOR_H
int add(int a, int b);
int subtract(int a, int b);
extern int operation_count; // declare global in header
#endif

// calculator.c
#include "calculator.h"
int operation_count = 0;   // define global here
int add(int a, int b) { operation_count++; return a + b; }
int subtract(int a, int b) { operation_count++; return a - b; }

// main.c
#include <stdio.h>
#include "calculator.h"

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

// Makefile (tabs required — not spaces!)
// CC = gcc
// CFLAGS = -Wall -Wextra -std=c11
// TARGET = app
// OBJS = main.o calculator.o
//
// $(TARGET): $(OBJS)
//     $(CC) $(CFLAGS) -o $(TARGET) $(OBJS)
//
// %.o: %.c
//     $(CC) $(CFLAGS) -c $<
//
// clean:
//     rm -f $(OBJS) $(TARGET)

Comparing to Java

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

JV
Java (What you know)
// Java project layout
// src/
//   com/example/
//     Main.java
//     math/
//       Calculator.java
//     io/
//       FileReader.java

// Calculator.java
package com.example.math;
public class Calculator {
    public int add(int a, int b) { return a + b; }
}

// Main.java
package com.example;
import com.example.math.Calculator;

public class Main {
    public static void main(String[] args) {
        Calculator c = new Calculator();
        System.out.println(c.add(1, 2));
    }
}

// Build: javac src/**/*.java -d out/
// Run:   java -cp out com.example.Main
// Maven: mvn compile && mvn exec:java
Mirror Card
JV
From Java:

You may be used to different syntax or behavior.

C
In C:

C splits into .h (interface) + .c (implementation) files — Java uses one .java per class

Mirror Card
JV
From Java:

You may be used to different syntax or behavior.

C
In C:

extern declares a variable defined in another file — Java has no equivalent (use class fields)

Mirror Card
JV
From Java:

You may be used to different syntax or behavior.

C
In C:

Each .c file compiles to a .o object file; linker combines them — Java uses classpath

Mirror Card
JV
From Java:

You may be used to different syntax or behavior.

C
In C:

Makefile describes build rules; Maven/Gradle handle Java builds

Mirror Card
JV
From Java:

You may be used to different syntax or behavior.

C
In C:

static at file scope = file-private in C — Java uses package-private or private

Step-by-Step Breakdown

1. Header Guards and Declarations

Every .h file starts with include guards. It contains only declarations — never definitions (unless inline/static).

JV
Java
// Java: one Calculator.java with full implementation
C
C
// calculator.h
#ifndef CALCULATOR_H
#define CALCULATOR_H
int add(int a, int b);       // declaration only
#endif

2. extern for Shared Globals

To share a global variable across files: declare with 'extern' in the .h, define (without extern) in exactly one .c file.

JV
Java
// Java: class fields, static fields — no global variables
C
C
// calculator.h: extern int count;  // declaration
// calculator.c: int count = 0;     // definition
// main.c: #include "calculator.h"
//          count++; // works — extern resolved at link time

3. static File Scope

static at file scope makes a function/variable private to that .c file — like Java's package-private but stricter (not even same package can access).

JV
Java
// Java: private static int helper()
C
C
// In calculator.c
static int clamp(int v, int lo, int hi) {  // file-private
    return v < lo ? lo : v > hi ? hi : v;
}
int add(int a, int b) { return clamp(a+b, INT_MIN, INT_MAX); }

4. Basic Makefile

Makefile automates compilation. make builds only changed files. Each rule: target: deps → tab + command.

JV
Java
# Maven: mvn compile
# Gradle: gradle build
C
C
CC = gcc
CFLAGS = -Wall -std=c11

app: main.o calc.o
\t$(CC) -o app main.o calc.o

main.o: main.c calc.h
\t$(CC) $(CFLAGS) -c main.c

calc.o: calc.c calc.h
\t$(CC) $(CFLAGS) -c calc.c

clean:
\trm -f *.o app
Rule of Thumb
Makefile rules use TAB characters, not spaces — this is a common source of confusing errors.

Common Mistakes

When coming from Java, developers often make these mistakes:

  • C splits into .h (interface) + .c (implementation) files — Java uses one .java per class
  • extern declares a variable defined in another file — Java has no equivalent (use class fields)
  • Each .c file compiles to a .o object file; linker combines them — Java uses classpath
Common Pitfall
Don't assume C works exactly like Java. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • .h declares interface, .c implements — #include pulls declarations in
  • extern declares global from another file; define it (without extern) in exactly one .c
  • static at file scope makes names private to that .c file
  • Makefile: target: deps → tab command; make only rebuilds changed files
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your Java code in C to practice these concepts.
PreviousNext