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
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
gcccompilation (preprocessing, compilation, assembly, linking) - Identify key differences between Java and C (native vs. bytecode, manual memory, no classes)
- Read
gccerror messages to locate the file, line number, and column of a syntax error - Explain why
./is required to run a compiled program and why-Wallshould 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.
mainis just a function — the special one that the OS calls to start your program. Theintreturn 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.
-o hello flag do in gcc -Wall -o hello hello.c?-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
- Preprocessing — handles
#includeand#define(text substitution) - Compilation — translates C to assembly language
- Assembly — translates assembly to machine code (object file)
- 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
javacproduces bytecode and the JVM interprets it,gccproduces native machine code. The executable filehellocontains 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.
gcc -Wall -o calc calc.c and get: undefined reference to 'sqrt'. At which stage of the compilation pipeline does this error occur?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.
gcc -Wall -o prog prog.c. What's the best strategy?-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).