c-foundations Lesson 3 20 min read

How Do I Translate from Java to C Compilation?

From javac + JVM to gcc + native binary — your first C program and what makes it different

Reading: C Text: Ch. 1 §1–4 (pp. 34–65), Ch. 2 §4 (pp. 85–91), Ch. 2 §7 (pp. 117–127)

After this lesson, you will be able to:

  • Write, compile, and run a minimal C program using gcc -Wall -o
  • Explain the four stages of gcc compilation (preprocessing, compilation, assembly, linking)
  • Identify key differences between Java and C (native vs. bytecode, manual memory, no classes)
  • Read gcc error messages to locate the file, line number, and column of a syntax error
  • Explain why ./ is required to run a compiled program and why -Wall should always be used

What If There Were No JVM?

In CSCD 210, your workflow looked like this:

Hello.java → javac → Hello.class → JVM → CPU

The JVM sat between your code and the hardware, translating bytecode to machine instructions on the fly. That’s convenient — write once, run anywhere.

But what if you could skip the middleman? What if your source code compiled directly to machine instructions that the CPU runs natively? That’s C:

hello.c → gcc → hello (executable) → CPU

No JVM. No bytecode. No garbage collector. Just your code talking directly to the processor. This is why C is fast, why it’s used for operating systems, and why it gives you more power — and more responsibility — than Java.


Your First C Program

hello.c

Create a file called hello.c:

#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}

Let’s break this down, line by line:

Line C Java Equivalent
#include <stdio.h> Include standard I/O library import java.util.Scanner;
int main(void) Entry point, returns int, takes no args public static void main(String[] args)
printf("Hello, world!\n"); Print to stdout System.out.println("Hello, world!");
return 0; Exit with success status System.exit(0); (implicit)

From Java: In Java, everything lives inside a class. In C, there are no classes. Functions exist at the top level. main is just a function — the special one that the OS calls to start your program. The int return type means “return an integer exit status” (0 = success).

Compiling with gcc

gcc -Wall -o hello hello.c

Breaking down the flags:

Flag Meaning
gcc The GNU C Compiler
-Wall Enable all warnings (required in this course)
-o hello Name the output file hello
hello.c The source file to compile

Common Pitfall: Always compile with -Wall. Without it, gcc silently accepts code that’s probably wrong — like using a variable before initializing it, or calling a function without a prototype. Warnings are your friend.

Check Your Understanding
What does the -o hello flag do in gcc -Wall -o hello hello.c?
A Optimizes the output binary for performance
B Opens the file hello for editing after compilation
C Names the output executable hello instead of the default a.out
D Specifies that hello is an object file, not an executable
Answer: C. Without -o, gcc names the executable a.out by default. The -o flag lets you choose the output filename. Optimization flags are -O1, -O2, etc. (uppercase O, not lowercase).

Run it:

./hello
Hello, world!

Remember from Lesson 1.6: you need ./ because the current directory isn’t in your PATH. Then check the exit status:

echo $?
0

That 0 means “success.” Every program returns an exit status — return 0; in main() is what set it.

The GitHub Classroom Workflow

Your CSCD 240 workflow is similar to CSCD 210, but the tools change:

CSCD 210 (Java) CSCD 240 (C)
Clone from GitHub Classroom Clone from GitHub Classroom
Edit in VS Code Edit in VS Code or nano
./gradlew build gcc -Wall -o program program.c
./gradlew test make test (later in the course)
Commit and push Commit and push

The structure is the same — clone, edit, build, test, push. The tools are different: gcc replaces Gradle, Makefiles replace build.gradle, and the Check library replaces JUnit. You’ll set up Makefiles in a few weeks.

What gcc Actually Does (Four Stages)

When you run gcc, it performs four steps behind the scenes:

hello.c → [Preprocessor] → hello.i → [Compiler] → hello.s → [Assembler] → hello.o → [Linker] → hello
  1. Preprocessing — handles #include and #define (text substitution)
  2. Compilation — translates C to assembly language
  3. Assembly — translates assembly to machine code (object file)
  4. Linking — combines object files with library code to produce the executable

You don’t need to run these separately — gcc does all four. But understanding the pipeline explains error messages:

  • Syntax errors happen in stage 2 (compilation)
  • “undefined reference” errors happen in stage 4 (linking)

Key Insight: Unlike Java, where javac produces bytecode and the JVM interprets it, gcc produces native machine code. The executable file hello contains actual CPU instructions. No runtime environment needed — just the OS.

Why does this matter?

Understanding the four-stage pipeline tells you where to look when something goes wrong. If you see “undefined reference,” you know it’s a linker problem (stage 4), not a typo in your code. If you see “implicit declaration,” you’re missing a header file (stage 1). The error message tells you the stage; the stage tells you the fix.

Check Your Understanding
You compile with gcc -Wall -o calc calc.c and get: undefined reference to 'sqrt'. At which stage of the compilation pipeline does this error occur?
A Preprocessing — the #include for the math library is missing
B Compilation — the compiler doesn't recognize sqrt
C Assembly — the assembler can't translate the sqrt instruction
D Linking — the linker can't find the math library's compiled code
Answer: D. "Undefined reference" is always a linker error. The compiler saw the declaration of sqrt (from #include <math.h>) and compiled your code just fine. But the linker can't find the actual compiled implementation. Fix it by adding -lm to link the math library: gcc -Wall -o calc calc.c -lm.

The Development Workflow

Your workflow for every lab:

nano hello.c                # 1. Edit
gcc -Wall -o hello hello.c  # 2. Compile
./hello                      # 3. Run
# Found a bug? Go back to step 1

When the compiler reports errors:

hello.c:5:5: error: expected ';' after expression
    printf("Hello\n")
    ^                ~
                     ;

Read the error: hello.c (file), 5 (line number), 5 (column). The compiler even suggests the fix! Always fix the first error first — later errors are often caused by the first one.

The Trick: When you see multiple compiler errors, fix only the first one and recompile. Subsequent errors are often cascading effects of the first error, not real problems.

Java vs. C: The Big Differences

Feature Java C
Compilation target Bytecode (.class) Native machine code
Runtime JVM required Runs directly on OS
Memory management Garbage collector Manual (malloc/free)
Strings String objects char arrays
Arrays Bounds-checked, .length No bounds checking, no length
OOP Classes, inheritance Functions only, no classes
Error handling Exceptions Return codes, errno
Build system Gradle/Maven Makefiles

Big Picture: C exists because Dennis Ritchie needed to rewrite Unix in a language higher than assembly but lower than anything else available. C gives you direct access to memory, direct access to hardware, and virtually no safety net. That’s why it’s fast, and that’s why it’s dangerous. The JVM, Python interpreter, and most operating systems are written in C precisely because nothing else gives you this level of control.

Common Mistakes (and What They Look Like)

These are the errors you’ll hit in your first week of C programming. Knowing them in advance saves time:

Forgetting #include <stdio.h>: Without it, printf is undeclared. You’ll see a warning like implicit declaration of function 'printf'.

Forgetting \n in printf: Unlike Java’s println, C’s printf does NOT add a newline automatically. printf("Hello") prints “Hello” with no newline — the next prompt appears on the same line.

Running hello instead of ./hello: The shell searches your PATH for hello and won’t find it. You must write ./hello to run a program in the current directory.

Forgetting -Wall: Without it, gcc stays quiet about problems that are almost certainly bugs. Always compile with -Wall.

Missing semicolons: Same as Java, but C error messages are worse. A missing semicolon on line 5 might produce an error pointing to line 6 or later.

Why C Still Matters

  • Operating systems: Linux, Windows kernel, macOS kernel — all C
  • Language runtimes: The JVM (that ran your Java), CPython, V8 (JavaScript) — all C/C++
  • Embedded systems: Medical devices, cars, spacecraft — C
  • Performance-critical code: Databases, game engines, networking — C/C++

The JVM itself is a C program. Learning C means understanding the layer beneath everything you’ve used.

Check Your Understanding
You see multiple compiler errors after running gcc -Wall -o prog prog.c. What's the best strategy?
A Fix all errors at once before recompiling
B Fix only the first error and recompile — later errors are often caused by the first one
C Remove -Wall to reduce the number of errors
D Fix the last error first since it's the most recent problem
Answer: B. The C compiler reads top-to-bottom. A single error (like a missing semicolon) can confuse the compiler about everything that follows, generating cascading errors that aren't real problems. Fix the first error, recompile, and see what remains. Option C is terrible advice — -Wall helps you catch bugs, not create them.
Quick Check: Why does C produce faster programs than Java?

C compiles directly to native machine code that the CPU executes. Java compiles to bytecode that the JVM must interpret or JIT-compile at runtime. Skipping the middleman (JVM) eliminates overhead. C also gives the programmer direct control over memory, avoiding garbage collection pauses.

Quick Check: What does -Wall do and why is it required?

-Wall enables all common warnings during compilation. It catches likely bugs like uninitialized variables, unused variables, and implicit function declarations. This course requires it because warnings often indicate real bugs that would cause subtle runtime errors.

Quick Check: You get an "undefined reference to sqrt" error. Is this a compilation error or a linking error?

It’s a linking error (stage 4). The compiler found your code valid, but the linker can’t find the sqrt function. You need to link the math library: gcc -Wall -o program program.c -lm. The -lm flag links libm (the math library).


You’re a C Programmer Now

You’ve written, compiled, and run your first C program. The gcc./program workflow will be your daily routine for the next eight weeks. And unlike Java, there’s no IDE magic hiding the compilation process — you see every step, every flag, every error message.

In the next lesson, you’ll look inside a C program in detail: #include, #define, data types, function prototypes, and the structure that every C file follows. This is where the Java-to-C translation really begins.

Big Picture: Understanding the compilation pipeline makes you a better debugger. When you know that “undefined reference” is a linker error, not a compiler error, you know to check if you’re linking the right libraries — not to look for typos in your code. The pipeline also explains why C has header files (the preprocessor needs them) and why you need function prototypes (the compiler reads top-to-bottom).