Claude-skill-registry convert-java-c
Convert Java code to idiomatic C. Use when migrating Java projects to C, translating Java patterns to idiomatic C, or refactoring Java codebases for performance, minimal runtime dependencies, and systems programming. Extends meta-convert-dev with Java-to-C specific patterns.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/convert-java-c" ~/.claude/skills/majiayu000-claude-skill-registry-convert-java-c && rm -rf "$T"
skills/data/convert-java-c/SKILL.mdConvert Java to C
Convert Java code to idiomatic C. This skill extends
meta-convert-dev with Java-to-C specific type mappings, idiom translations, and tooling for transforming managed, object-oriented Java code into manual, procedural C.
This Skill Extends
- Foundational conversion patterns (APTV workflow, testing strategies)meta-convert-dev
For general concepts like the Analyze → Plan → Transform → Validate workflow, testing strategies, and common pitfalls, see the meta-skill first.
This Skill Adds
- Type mappings: Java types → C types (managed → manual memory)
- Idiom translations: Java OOP patterns → C procedural/opaque pointer patterns
- Error handling: Exceptions → error codes + errno
- Concurrency: Java threads/synchronized → pthreads/mutexes
- Memory/Ownership: GC + automatic cleanup → malloc/free + manual management
- Object-Oriented → Procedural: Classes/interfaces → structs + function pointers
This Skill Does NOT Cover
- General conversion methodology - see
meta-convert-dev - Java language fundamentals - see
lang-java-dev - C language fundamentals - see
lang-c-dev - Reverse conversion (C → Java) - see
convert-c-java
Quick Reference
| Java | C | Notes |
|---|---|---|
| | Guaranteed 32-bit |
| | Guaranteed 64-bit |
| | Signed 8-bit |
| | Signed 16-bit |
| or | Java char is UTF-16 |
| | 32-bit IEEE 754 |
| | 64-bit IEEE 754 |
| (C99+) or | Use stdbool.h |
| or custom struct | Null-terminated or length-tracked |
| + size/capacity | Manual dynamic array |
| Custom hash table | Use uthash or implement |
| | Pointer null |
| | Type-erased pointer |
| with function pointers | Vtable pattern |
| error code + | Manual error propagation |
| | Manual locking |
| | POSIX threads |
When Converting Code
- Analyze source thoroughly before writing target
- Map types first - create type equivalence table (Java primitives have exact C equivalents)
- Preserve semantics over syntax similarity
- Plan memory management - who owns what, when to free
- Adopt C idioms - don't write "Java code in C syntax"
- Handle edge cases - null, exceptions, array bounds
- Test equivalence - same inputs → same outputs
Type System Mapping
Primitive Types
| Java | C | Notes |
|---|---|---|
| | Signed 8-bit (-128 to 127) |
| | Signed 16-bit (-32,768 to 32,767) |
| | Signed 32-bit |
| | Signed 64-bit |
| or | Java char is UTF-16 (2 bytes) |
| | 32-bit IEEE 754 |
| | 64-bit IEEE 754 |
| (C99+) or | Use |
| | No return value |
Critical Note on char: Java's
char is a UTF-16 code unit (unsigned 16-bit). C's char is an ASCII character (8-bit). For Java String → C conversion, use UTF-8 char* or wide characters (wchar_t*).
String Types
| Java | C | Notes |
|---|---|---|
| | Immutable, null-terminated |
| | Mutable, null-terminated |
| + manual realloc | Dynamic buffer |
| or | Depends on encoding |
String Encoding: Java strings are UTF-16. C typically uses UTF-8. Convert using
iconv or manual UTF-16 → UTF-8 conversion.
Collection Types
| Java | C | Notes |
|---|---|---|
| + | Manual dynamic array |
| | Manual linked list |
| + hash | Use uthash library or custom |
| or custom | Hash table with presence |
| + | Fixed-size or dynamic |
| + head/tail pointers | Circular buffer or linked list |
| + top pointer | Array-based stack |
Object Types
| Java | C | Notes |
|---|---|---|
| | Data + opaque pointer pattern |
| with function pointers | Vtable pattern |
| + function pointer vtable | Partial implementation |
| or constants | C enums are just ints |
| | Type-erased pointer (avoid) |
| | Null pointer |
Generic Types → Type Erasure or Macros
| Java | C | Notes |
|---|---|---|
| Separate type per or | No generics; use macros or code generation |
| Function pointer for comparison | Pass comparator explicitly |
| Separate hash table per type | Or use with casting |
Approach 1: Type-specific implementations
// int_list.h typedef struct { int *data; size_t size; size_t capacity; } IntList; // string_list.h typedef struct { char **data; size_t size; size_t capacity; } StringList;
Approach 2: Generic with void (less type-safe)*
typedef struct { void **data; size_t size; size_t capacity; } GenericList;
Approach 3: Macros (type-safe, compile-time)
#define DEFINE_LIST(T, PREFIX) \ typedef struct { \ T *data; \ size_t size, capacity; \ } PREFIX##List;
Idiom Translation
Pattern 1: Object Creation and Destruction
Java:
// Constructor public class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } // Destructor (finalize - rarely used) @Override protected void finalize() { // Cleanup } } // Usage User user = new User("Alice", 30); // GC handles cleanup automatically
C:
// Opaque pointer pattern typedef struct User User; // Constructor User* user_create(const char *name, int age) { User *user = malloc(sizeof(User)); if (user == NULL) { return NULL; } user->name = strdup(name); // Allocate copy user->age = age; return user; } // Destructor void user_destroy(User *user) { if (user != NULL) { free(user->name); free(user); } } // Usage User *user = user_create("Alice", 30); if (user != NULL) { // Use user... user_destroy(user); }
Why this translation:
- Java handles memory automatically with GC; C requires manual
/mallocfree - Opaque pointers hide implementation details (similar to Java private fields)
- Constructor/destructor pattern mimics Java's object lifecycle
- Always check
return for NULL (Java throws OutOfMemoryError)malloc
Pattern 2: Null Handling
Java:
// Nullable reference String name = getUser(id); if (name != null) { System.out.println(name.toUpperCase()); } else { System.out.println("Unknown"); } // Optional (Java 8+) Optional<String> nameOpt = Optional.ofNullable(getUser(id)); String upper = nameOpt.map(String::toUpperCase).orElse("Unknown");
C:
// Null pointer check char *name = get_user(id); if (name != NULL) { // Create uppercase copy char upper[256]; size_t i; for (i = 0; i < sizeof(upper) - 1 && name[i] != '\0'; i++) { upper[i] = toupper(name[i]); } upper[i] = '\0'; printf("%s\n", upper); } else { printf("Unknown\n"); } free(name); // Optional-like pattern (C11+) typedef struct { bool present; char value[256]; } OptionalString; OptionalString get_user_optional(int id) { OptionalString opt = {0}; char *name = get_user(id); if (name != NULL) { opt.present = true; strncpy(opt.value, name, sizeof(opt.value) - 1); free(name); } return opt; }
Why this translation:
- C has no built-in Optional type; must check NULL explicitly
- Manual memory management: know when to free returned pointers
- C strings are mutable; modifications require copying
Pattern 3: Exception Handling → Error Codes
Java:
public void processFile(String path) throws IOException { File file = new File(path); if (!file.exists()) { throw new FileNotFoundException(path); } try (BufferedReader reader = new BufferedReader(new FileReader(file))) { String line = reader.readLine(); // Process... } catch (IOException e) { throw new IOException("Failed to read " + path, e); } }
C:
#include <errno.h> // Error codes #define SUCCESS 0 #define ERR_FILE_NOT_FOUND -1 #define ERR_IO_ERROR -2 int process_file(const char *path) { // Check file exists if (access(path, F_OK) != 0) { errno = ENOENT; return ERR_FILE_NOT_FOUND; } FILE *file = fopen(path, "r"); if (file == NULL) { // errno set by fopen return ERR_IO_ERROR; } char buffer[256]; if (fgets(buffer, sizeof(buffer), file) == NULL) { fclose(file); return ERR_IO_ERROR; } // Process... fclose(file); return SUCCESS; } // Usage int result = process_file("data.txt"); if (result != SUCCESS) { fprintf(stderr, "Error: %s (code %d)\n", strerror(errno), result); }
Why this translation:
- C has no exceptions; use return codes and
errno
is a thread-local global for system error detailserrno- Check every I/O operation return value
- Manual resource cleanup (no try-with-resources)
Pattern 4: Interfaces → Function Pointer Vtables
Java:
interface Drawable { void draw(); int getWidth(); } class Circle implements Drawable { private int radius; public Circle(int radius) { this.radius = radius; } @Override public void draw() { System.out.println("Drawing circle"); } @Override public int getWidth() { return radius * 2; } }
C:
// Interface as vtable typedef struct Drawable Drawable; typedef struct { void (*draw)(Drawable *self); int (*get_width)(Drawable *self); } DrawableVTable; struct Drawable { const DrawableVTable *vtable; void *impl; // Opaque implementation }; // Circle implementation typedef struct { int radius; } Circle; static void circle_draw(Drawable *self) { Circle *circle = (Circle *)self->impl; printf("Drawing circle with radius %d\n", circle->radius); } static int circle_get_width(Drawable *self) { Circle *circle = (Circle *)self->impl; return circle->radius * 2; } static const DrawableVTable circle_vtable = { .draw = circle_draw, .get_width = circle_get_width, }; // Constructor Drawable* circle_create(int radius) { Circle *circle = malloc(sizeof(Circle)); if (circle == NULL) return NULL; circle->radius = radius; Drawable *drawable = malloc(sizeof(Drawable)); if (drawable == NULL) { free(circle); return NULL; } drawable->vtable = &circle_vtable; drawable->impl = circle; return drawable; } // Usage Drawable *obj = circle_create(10); obj->vtable->draw(obj); // Polymorphic call int width = obj->vtable->get_width(obj);
Why this translation:
- C has no built-in polymorphism; simulate with function pointer tables (vtables)
stores the concrete type (requires casting)void *impl- Vtable stores function pointers for interface methods
- Manual vtable initialization (Java does this automatically)
Pattern 5: ArrayList → Dynamic Array
Java:
ArrayList<Integer> numbers = new ArrayList<>(); numbers.add(1); numbers.add(2); numbers.add(3); for (int num : numbers) { System.out.println(num); } numbers.remove(1); // Remove at index 1
C:
typedef struct { int *data; size_t size; size_t capacity; } IntList; IntList* int_list_create(void) { IntList *list = malloc(sizeof(IntList)); if (list == NULL) return NULL; list->data = malloc(10 * sizeof(int)); // Initial capacity if (list->data == NULL) { free(list); return NULL; } list->size = 0; list->capacity = 10; return list; } int int_list_add(IntList *list, int value) { if (list->size >= list->capacity) { // Resize (double capacity) size_t new_capacity = list->capacity * 2; int *new_data = realloc(list->data, new_capacity * sizeof(int)); if (new_data == NULL) { return -1; // Failed to resize } list->data = new_data; list->capacity = new_capacity; } list->data[list->size++] = value; return 0; } void int_list_remove_at(IntList *list, size_t index) { if (index >= list->size) return; // Shift elements left for (size_t i = index; i < list->size - 1; i++) { list->data[i] = list->data[i + 1]; } list->size--; } void int_list_destroy(IntList *list) { if (list != NULL) { free(list->data); free(list); } } // Usage IntList *numbers = int_list_create(); int_list_add(numbers, 1); int_list_add(numbers, 2); int_list_add(numbers, 3); for (size_t i = 0; i < numbers->size; i++) { printf("%d\n", numbers->data[i]); } int_list_remove_at(numbers, 1); int_list_destroy(numbers);
Why this translation:
- Java ArrayList auto-grows; C requires manual
realloc - Track both
(current elements) andsize
(allocated space)capacity - Manual bounds checking (Java throws IndexOutOfBoundsException)
- Explicit destroy function to free memory
Pattern 6: HashMap → Custom Hash Table
Java:
HashMap<String, Integer> ages = new HashMap<>(); ages.put("Alice", 30); ages.put("Bob", 25); Integer age = ages.get("Alice"); if (age != null) { System.out.println(age); } ages.remove("Bob");
C:
// Using uthash library: https://troydhanson.github.io/uthash/ #include "uthash.h" typedef struct { char *key; int value; UT_hash_handle hh; } Entry; Entry *ages = NULL; // Hash table head // Put void put(const char *key, int value) { Entry *entry; HASH_FIND_STR(ages, key, entry); if (entry == NULL) { entry = malloc(sizeof(Entry)); entry->key = strdup(key); entry->value = value; HASH_ADD_KEYPTR(hh, ages, entry->key, strlen(entry->key), entry); } else { entry->value = value; // Update existing } } // Get int get(const char *key, int *out_value) { Entry *entry; HASH_FIND_STR(ages, key, entry); if (entry != NULL) { *out_value = entry->value; return 1; // Found } return 0; // Not found } // Remove void remove_key(const char *key) { Entry *entry; HASH_FIND_STR(ages, key, entry); if (entry != NULL) { HASH_DEL(ages, entry); free(entry->key); free(entry); } } // Cleanup void destroy_all(void) { Entry *entry, *tmp; HASH_ITER(hh, ages, entry, tmp) { HASH_DEL(ages, entry); free(entry->key); free(entry); } } // Usage put("Alice", 30); put("Bob", 25); int age; if (get("Alice", &age)) { printf("%d\n", age); } remove_key("Bob"); destroy_all();
Why this translation:
- C has no built-in hash table; use libraries (uthash, glib) or implement manually
- Manual key copying (
) and freeingstrdup - Return values indicate success/failure (no null to indicate missing)
- Iteration requires library-specific macros
Pattern 7: Synchronized → Mutexes
Java:
public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } }
C:
#include <pthread.h> typedef struct { int count; pthread_mutex_t mutex; } Counter; Counter* counter_create(void) { Counter *counter = malloc(sizeof(Counter)); if (counter == NULL) return NULL; counter->count = 0; pthread_mutex_init(&counter->mutex, NULL); return counter; } void counter_increment(Counter *counter) { pthread_mutex_lock(&counter->mutex); counter->count++; pthread_mutex_unlock(&counter->mutex); } int counter_get(Counter *counter) { pthread_mutex_lock(&counter->mutex); int value = counter->count; pthread_mutex_unlock(&counter->mutex); return value; } void counter_destroy(Counter *counter) { if (counter != NULL) { pthread_mutex_destroy(&counter->mutex); free(counter); } }
Why this translation:
- Java
is automatic; C requires explicit lock/unlocksynchronized - Must remember to unlock (Java does this automatically even on exception)
- Consider using atomics (
) for simple counters (C11+)atomic_int - Initialize and destroy mutexes explicitly
Pattern 8: Thread Creation
Java:
Thread thread = new Thread(() -> { System.out.println("Running in thread"); }); thread.start(); thread.join();
C:
#include <pthread.h> #include <stdio.h> void* thread_function(void *arg) { printf("Running in thread\n"); return NULL; } int main(void) { pthread_t thread; if (pthread_create(&thread, NULL, thread_function, NULL) != 0) { perror("pthread_create"); return 1; } if (pthread_join(thread, NULL) != 0) { perror("pthread_join"); return 1; } return 0; }
Why this translation:
- Java Thread wraps OS threads; C uses POSIX threads directly
- Function pointers instead of lambda/Runnable
- Manual error checking (Java throws exceptions)
- No automatic thread pooling (Java ExecutorService)
Paradigm Translation
Mental Model Shift: Object-Oriented → Procedural
| Java Concept | C Approach | Key Insight |
|---|---|---|
| Class with state | + opaque pointer | Data separated from functions |
| Method | Function with pointer | Explicit as first parameter |
| Inheritance | Struct embedding + casting | Manual vtable for polymorphism |
| Interface | Function pointer table | Manual dispatch |
| Constructor | function | Explicit allocation with |
| Destructor/finalize | function | Manual cleanup with |
keyword | + initialization | Manual memory allocation |
| GC | Manual | Explicit deallocation |
| Access modifiers | Opaque pointers + header/impl split | Module-level visibility |
Memory Management Mental Model
| Java Model | C Model | Conceptual Translation |
|---|---|---|
| Automatic GC | Manual malloc/free | Ownership tracking is manual |
| References | Pointers | Explicit addresses |
| Array bounds checking | Manual checks or buffer overflow | No automatic safety |
| String immutability | Mutable char arrays | Manual copying for safety |
| No dangling references | Dangling pointers possible | Use-after-free bugs possible |
Error Handling Mental Model
| Java Model | C Model | Conceptual Translation |
|---|---|---|
| Exceptions | Error codes | No stack unwinding |
| try-catch | if (error) { handle } | Manual propagation |
| finally | goto cleanup pattern | Manual resource release |
| Checked exceptions | Function documentation | Compiler doesn't enforce |
| Stack traces | Manual logging | No automatic context |
Error Handling
Java Exception Model → C Error Codes
Java Approach:
- Exceptions for error conditions
- Stack unwinding for cleanup
- Checked vs unchecked exceptions
- Try-catch-finally blocks
C Approach:
- Return codes for error conditions
- Manual cleanup with goto or careful ordering
for system call errorserrno- Explicit error checking at every call
Pattern: Error Code + errno
// Error codes typedef enum { SUCCESS = 0, ERR_NULL_POINTER = -1, ERR_OUT_OF_MEMORY = -2, ERR_FILE_NOT_FOUND = -3, ERR_IO_ERROR = -4, } ErrorCode; // Function with error handling ErrorCode read_config(const char *path, Config **out) { if (path == NULL || out == NULL) { return ERR_NULL_POINTER; } FILE *file = fopen(path, "r"); if (file == NULL) { errno = ENOENT; return ERR_FILE_NOT_FOUND; } Config *config = malloc(sizeof(Config)); if (config == NULL) { fclose(file); return ERR_OUT_OF_MEMORY; } // Read data... if (fgets(config->buffer, sizeof(config->buffer), file) == NULL) { free(config); fclose(file); return ERR_IO_ERROR; } fclose(file); *out = config; return SUCCESS; } // Usage with goto cleanup pattern ErrorCode process(void) { Config *config = NULL; Data *data = NULL; ErrorCode result = SUCCESS; result = read_config("app.conf", &config); if (result != SUCCESS) { goto cleanup; } data = malloc(sizeof(Data)); if (data == NULL) { result = ERR_OUT_OF_MEMORY; goto cleanup; } // Process... cleanup: free(data); free(config); return result; }
Concurrency Patterns
Java Concurrency → POSIX Threads
Java Model:
- Thread class + Runnable interface
keywordsynchronized
/wait()notify()- ExecutorService thread pools
- CompletableFuture
C Model:
- pthread_t + function pointers
- pthread_mutex_t explicit locks
- pthread_cond_t condition variables
- Manual thread pool implementation
- No built-in async/await
Pattern: Producer-Consumer with Condition Variables
Java:
class Queue { private LinkedList<Integer> items = new LinkedList<>(); private final int MAX = 10; public synchronized void put(int item) throws InterruptedException { while (items.size() >= MAX) { wait(); } items.add(item); notifyAll(); } public synchronized int take() throws InterruptedException { while (items.isEmpty()) { wait(); } int item = items.removeFirst(); notifyAll(); return item; } }
C:
#include <pthread.h> #define MAX_QUEUE 10 typedef struct { int items[MAX_QUEUE]; int head, tail, count; pthread_mutex_t mutex; pthread_cond_t not_full; pthread_cond_t not_empty; } Queue; Queue* queue_create(void) { Queue *q = malloc(sizeof(Queue)); if (q == NULL) return NULL; q->head = q->tail = q->count = 0; pthread_mutex_init(&q->mutex, NULL); pthread_cond_init(&q->not_full, NULL); pthread_cond_init(&q->not_empty, NULL); return q; } void queue_put(Queue *q, int item) { pthread_mutex_lock(&q->mutex); while (q->count >= MAX_QUEUE) { pthread_cond_wait(&q->not_full, &q->mutex); } q->items[q->tail] = item; q->tail = (q->tail + 1) % MAX_QUEUE; q->count++; pthread_cond_signal(&q->not_empty); pthread_mutex_unlock(&q->mutex); } int queue_take(Queue *q) { pthread_mutex_lock(&q->mutex); while (q->count == 0) { pthread_cond_wait(&q->not_empty, &q->mutex); } int item = q->items[q->head]; q->head = (q->head + 1) % MAX_QUEUE; q->count--; pthread_cond_signal(&q->not_full); pthread_mutex_unlock(&q->mutex); return item; } void queue_destroy(Queue *q) { if (q != NULL) { pthread_mutex_destroy(&q->mutex); pthread_cond_destroy(&q->not_full); pthread_cond_destroy(&q->not_empty); free(q); } }
Why this translation:
- Java
/wait()
→notifyAll()
/pthread_cond_wait()pthread_cond_signal() - Java
→ explicitsynchronizedpthread_mutex_lock/unlock - Manual mutex management (Java does it automatically)
- Condition variables require associated mutex
Memory & Ownership
Java GC → C Manual Memory Management
Key Differences:
| Aspect | Java | C |
|---|---|---|
| Allocation | | |
| Deallocation | Automatic (GC) | Manual () |
| Dangling references | Impossible | Possible (use-after-free) |
| Memory leaks | Possible (unreachable but referenced) | Possible (forgot to free) |
| Null checks | NullPointerException at runtime | Segmentation fault |
| Initialization | Guaranteed (default values) | Uninitialized (garbage) |
Ownership Patterns in C:
- Caller owns, callee borrows (most common)
void process_user(const User *user) { // user is borrowed, don't free printf("%s\n", user->name); } User *user = user_create("Alice", 30); process_user(user); user_destroy(user); // Caller frees
- Callee owns, caller receives
char* create_greeting(const char *name) { char *greeting = malloc(100); snprintf(greeting, 100, "Hello, %s!", name); return greeting; // Caller must free } char *msg = create_greeting("Alice"); printf("%s\n", msg); free(msg); // Caller frees
- Shared ownership (ref counting)
typedef struct { Data data; int ref_count; } RefCounted; RefCounted* ref_create(void) { RefCounted *r = malloc(sizeof(RefCounted)); r->ref_count = 1; return r; } void ref_retain(RefCounted *r) { r->ref_count++; } void ref_release(RefCounted *r) { r->ref_count--; if (r->ref_count == 0) { free(r); } }
Common Pitfalls
-
Forgetting to free memory
- Issue: Java GC handles cleanup; C requires manual
free() - Solution: Every
should have a correspondingmalloc
; use goto cleanup patternfree
- Issue: Java GC handles cleanup; C requires manual
-
Null pointer dereference
- Issue: Java throws NullPointerException; C crashes with segfault
- Solution: Check pointers before dereferencing:
if (ptr != NULL) { ... }
-
Buffer overflow
- Issue: Java arrays are bounds-checked; C arrays are not
- Solution: Use
,strncpy
, track array sizes, validate indicessnprintf
-
Use-after-free
- Issue: Java GC prevents this; C allows accessing freed memory
- Solution: Set pointers to NULL after freeing; use tools like valgrind
-
String encoding mismatch
- Issue: Java strings are UTF-16; C strings are typically UTF-8 or ASCII
- Solution: Convert encoding explicitly; use
library or manual conversioniconv
-
Integer overflow
- Issue: Java detects overflow (throws exception or wraps); C wraps silently
- Solution: Use safe math libraries or check before operations
-
Thread safety
- Issue: Java
is implicit; C mutexes must be explicitsynchronized - Solution: Document thread-safety requirements; use mutexes consistently
- Issue: Java
-
Error handling
- Issue: Java exceptions propagate automatically; C error codes must be checked
- Solution: Check every function return value; use goto for cleanup
-
Missing initialization
- Issue: Java initializes fields to default values; C leaves memory uninitialized
- Solution: Always initialize variables:
or useint x = 0;
for zero-initcalloc
-
Type safety
- Issue: Java has strong typing; C allows dangerous casts with
void* - Solution: Minimize
usage; use typed pointers; document ownershipvoid*
- Issue: Java has strong typing; C allows dangerous casts with
Tooling
| Tool | Purpose | Notes |
|---|---|---|
| GCC / Clang | C compiler | Use for warnings |
| Valgrind | Memory leak detection | Detects leaks, use-after-free, uninitialized memory |
| AddressSanitizer | Memory error detection | Built into GCC/Clang with |
| gdb | Debugger | Debug segfaults and logic errors |
| uthash | Hash table library | Header-only hash table for C |
| cJSON | JSON parsing | Parse/generate JSON in C |
| Unity / CMocka | Unit testing | Testing frameworks for C |
| pthread | POSIX threads | Standard threading library |
| Make / CMake | Build system | Compile multi-file C projects |
Examples
Example 1: Simple - Data Class with Methods
Before (Java):
public class Point { private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } public double distance(Point other) { int dx = this.x - other.x; int dy = this.y - other.y; return Math.sqrt(dx * dx + dy * dy); } }
After (C):
// point.h #ifndef POINT_H #define POINT_H typedef struct { int x; int y; } Point; Point point_create(int x, int y); int point_get_x(const Point *p); int point_get_y(const Point *p); double point_distance(const Point *p1, const Point *p2); #endif // point.c #include "point.h" #include <math.h> Point point_create(int x, int y) { Point p = {x, y}; return p; } int point_get_x(const Point *p) { return p->x; } int point_get_y(const Point *p) { return p->y; } double point_distance(const Point *p1, const Point *p2) { int dx = p1->x - p2->x; int dy = p1->y - p2->y; return sqrt(dx * dx + dy * dy); } // Usage Point p1 = point_create(0, 0); Point p2 = point_create(3, 4); double dist = point_distance(&p1, &p2); // 5.0
Example 2: Medium - Error Handling with Resources
Before (Java):
public String readFirstLine(String path) throws IOException { try (BufferedReader reader = new BufferedReader(new FileReader(path))) { String line = reader.readLine(); if (line == null) { throw new IOException("File is empty"); } return line; } }
After (C):
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define SUCCESS 0 #define ERR_FILE_OPEN -1 #define ERR_FILE_EMPTY -2 #define ERR_OUT_OF_MEMORY -3 int read_first_line(const char *path, char **out_line) { if (path == NULL || out_line == NULL) { return ERR_FILE_OPEN; } FILE *file = fopen(path, "r"); if (file == NULL) { return ERR_FILE_OPEN; } char buffer[256]; if (fgets(buffer, sizeof(buffer), file) == NULL) { fclose(file); return ERR_FILE_EMPTY; } // Remove trailing newline size_t len = strlen(buffer); if (len > 0 && buffer[len - 1] == '\n') { buffer[len - 1] = '\0'; } *out_line = strdup(buffer); if (*out_line == NULL) { fclose(file); return ERR_OUT_OF_MEMORY; } fclose(file); return SUCCESS; } // Usage char *line = NULL; int result = read_first_line("data.txt", &line); if (result == SUCCESS) { printf("First line: %s\n", line); free(line); } else { fprintf(stderr, "Error reading file: %d\n", result); }
Example 3: Complex - Thread-Safe Counter with Interface
Before (Java):
interface Counter { void increment(); int get(); } class AtomicCounter implements Counter { private final AtomicInteger count = new AtomicInteger(0); @Override public void increment() { count.incrementAndGet(); } @Override public int get() { return count.get(); } } class SynchronizedCounter implements Counter { private int count = 0; @Override public synchronized void increment() { count++; } @Override public synchronized int get() { return count; } }
After (C):
// counter.h #ifndef COUNTER_H #define COUNTER_H #include <pthread.h> #include <stdatomic.h> typedef struct Counter Counter; typedef struct { void (*increment)(Counter *self); int (*get)(Counter *self); void (*destroy)(Counter *self); } CounterVTable; struct Counter { const CounterVTable *vtable; void *impl; }; // Atomic counter Counter* atomic_counter_create(void); // Synchronized counter Counter* synchronized_counter_create(void); #endif // counter.c #include "counter.h" #include <stdlib.h> // Atomic implementation typedef struct { atomic_int count; } AtomicCounterImpl; static void atomic_counter_increment(Counter *self) { AtomicCounterImpl *impl = (AtomicCounterImpl *)self->impl; atomic_fetch_add(&impl->count, 1); } static int atomic_counter_get(Counter *self) { AtomicCounterImpl *impl = (AtomicCounterImpl *)self->impl; return atomic_load(&impl->count); } static void atomic_counter_destroy_impl(Counter *self) { free(self->impl); free(self); } static const CounterVTable atomic_vtable = { .increment = atomic_counter_increment, .get = atomic_counter_get, .destroy = atomic_counter_destroy_impl, }; Counter* atomic_counter_create(void) { AtomicCounterImpl *impl = malloc(sizeof(AtomicCounterImpl)); if (impl == NULL) return NULL; atomic_init(&impl->count, 0); Counter *counter = malloc(sizeof(Counter)); if (counter == NULL) { free(impl); return NULL; } counter->vtable = &atomic_vtable; counter->impl = impl; return counter; } // Synchronized implementation typedef struct { int count; pthread_mutex_t mutex; } SyncCounterImpl; static void sync_counter_increment(Counter *self) { SyncCounterImpl *impl = (SyncCounterImpl *)self->impl; pthread_mutex_lock(&impl->mutex); impl->count++; pthread_mutex_unlock(&impl->mutex); } static int sync_counter_get(Counter *self) { SyncCounterImpl *impl = (SyncCounterImpl *)self->impl; pthread_mutex_lock(&impl->mutex); int value = impl->count; pthread_mutex_unlock(&impl->mutex); return value; } static void sync_counter_destroy_impl(Counter *self) { SyncCounterImpl *impl = (SyncCounterImpl *)self->impl; pthread_mutex_destroy(&impl->mutex); free(impl); free(self); } static const CounterVTable sync_vtable = { .increment = sync_counter_increment, .get = sync_counter_get, .destroy = sync_counter_destroy_impl, }; Counter* synchronized_counter_create(void) { SyncCounterImpl *impl = malloc(sizeof(SyncCounterImpl)); if (impl == NULL) return NULL; impl->count = 0; pthread_mutex_init(&impl->mutex, NULL); Counter *counter = malloc(sizeof(Counter)); if (counter == NULL) { pthread_mutex_destroy(&impl->mutex); free(impl); return NULL; } counter->vtable = &sync_vtable; counter->impl = impl; return counter; } // Usage Counter *counter = atomic_counter_create(); counter->vtable->increment(counter); int count = counter->vtable->get(counter); counter->vtable->destroy(counter);
See Also
For more examples and patterns, see:
- Foundational patterns with cross-language examplesmeta-convert-dev
- Similar high-level to low-level conversionconvert-python-c
- Java development patternslang-java-dev
- C development patternslang-c-dev
Cross-cutting pattern skills (for areas not fully covered by lang-*-dev):
- Threads, mutexes, atomics across languagespatterns-concurrency-dev
- JSON, binary formats across languagespatterns-serialization-dev
- Manual memory management patternspatterns-memory-eng