PONY λ M2 Modula-2

Pascal.CodeCompared.To/C

An interactive executable cheatsheet comparing Pascal and C

Free Pascal 3.2.2 C17 (GCC)
Hello World & Compilation
Hello, World
program HelloWorld; begin writeln('Hello, World!'); end.
#include <stdio.h> int main(void) { printf("Hello, World!\n"); return 0; }
The structural differences are immediately visible: Pascal wraps code in program Name; begin ... end.; C wraps it in int main(void) { ... }. Pascal's writeln adds a newline automatically; C's printf needs an explicit \n. C requires #include <stdio.h> to use printf.
Compile & run
{ Single file: fpc hello.pas && ./hello With optimisation: fpc -O2 hello.pas Debug build: fpc -g hello.pas View assembler output: fpc -al hello.pas }
/* Single file: gcc -std=c17 -Wall -Wextra -o hello hello.c && ./hello With optimisation: gcc -O2 -o hello hello.c With math library: gcc hello.c -lm -o hello Debug build: gcc -g -o hello hello.c View assembler: gcc -S hello.c */
Both languages compile directly to native machine code. Free Pascal's single-file workflow is nearly identical to GCC's: compile to binary, run binary. C's -Wall -Wextra flags enable warnings that catch many Pascal-style assumptions (implicit conversions, missing return statements). The -lm flag links the math library for sqrt, sin, etc.
Formatted output
program PrintFormat; uses SysUtils; var name: string; age: integer; pi: double; begin name := 'Alice'; age := 30; pi := 3.14159; writeln(Format('Name: %s, Age: %d, Pi: %.2f', [name, age, pi])); writeln(name, ' is ', age, ' years old'); end.
#include <stdio.h> int main(void) { const char *name = "Alice"; int age = 30; double pi = 3.14159; printf("Name: %s, Age: %d, Pi: %.2f\n", name, age, pi); printf("%s is %d years old\n", name, age); return 0; }
C's printf format specifiers (%s, %d, %.2f) map directly to Pascal's Format function specifiers. Pascal's bare writeln(a, b, c) concatenates multiple arguments; C requires a format string with one specifier per value. The \n in C strings is a literal escape — Pascal's writeln adds it automatically.
Variables & Types
Variable declarations
program Variables; var count: integer; message: string; ratio: double; flag: boolean; begin count := 42; message := 'hello'; ratio := 3.14; flag := true; writeln(count, ' ', ratio:5:2, ' ', flag); end.
#include <stdio.h> #include <stdbool.h> /* bool, true, false (C99+) */ int main(void) { int count = 42; double ratio = 3.14; bool flag = true; /* C has no built-in string type — see the Strings section */ printf("%d %.2f %d\n", count, ratio, flag); return 0; }
Pascal declares variables in a var block before begin; C declares them at the point of use (since C99) or at the top of a block. The assignment operator is = in C, not Pascal's :=. C had no boolean type until C99's <stdbool.h> — and bool is really _Bool underneath.
Integer types
program IntTypes; var small: shortint; { 8-bit signed } medium: integer; { 32-bit signed } large: int64; { 64-bit signed } unsigned: cardinal; { 32-bit unsigned } size_val: sizeint; { pointer-width } begin small := 127; medium := 2000000; large := 9000000000; unsigned := 3000000000; size_val := 256; writeln(small, ' ', medium, ' ', large, ' ', unsigned); end.
#include <stdio.h> #include <stdint.h> /* int8_t, int32_t, int64_t, uint32_t, size_t */ int main(void) { int8_t small = 127; int32_t medium = 2000000; int64_t large = 9000000000LL; uint32_t unsigned_val = 3000000000U; size_t size_val = 256; printf("%d %d %lld %u\n", small, medium, (long long)large, unsigned_val); return 0; }
C's plain int, long, etc. have implementation-defined sizes — notoriously a source of portability bugs. The <stdint.h> header (C99) provides exact-width types like int32_t and uint64_t, which map directly to Pascal's integer/cardinal/int64. Always prefer int32_t-style names in new C code.
Constants
program Constants; const MAX_SIZE = 100; PI = 3.14159265; GREETING = 'Hello'; begin writeln('Max: ', MAX_SIZE); writeln('Pi: ', PI:12:8); writeln(GREETING); end.
#include <stdio.h> #define MAX_SIZE 100 /* preprocessor macro — no type */ static const double PI = 3.14159265; /* typed constant — preferred */ static const char *GREETING = "Hello"; int main(void) { printf("Max: %d\n", MAX_SIZE); printf("Pi: %.8f\n", PI); printf("%s\n", GREETING); return 0; }
C has two mechanisms for constants: the preprocessor #define (text substitution, no type checking) and const variables (typed, scope-respecting). Use const over #define in modern C — the compiler gives better error messages and the debugger can see the name. Pascal's typed constants (const x: integer = 5) correspond to C's const variables.
Type aliases
program TypeAliases; type TAge = 0..150; { subrange type } TScore = real; { alias for a floating type — Pascal has no real subranges } TPersonID = integer; { alias } var age: TAge; score: TScore; person_id: TPersonID; begin age := 25; score := 87.5; person_id := 1001; writeln(age, ' ', score:5:1, ' ', person_id); end.
#include <stdio.h> typedef int age_t; /* alias for int */ typedef int person_id_t; /* alias — same underlying type */ typedef float score_t; /* C has no subrange types — values are unchecked at runtime */ int main(void) { age_t age = 25; score_t score = 87.5f; person_id_t person_id = 1001; printf("%d %.1f %d\n", age, score, person_id); return 0; }
C's typedef creates a new name for an existing type, like Pascal's type AliasName = BaseType. C has no subrange types — declaring age_t as an alias for int adds no bounds checking. In Pascal, assigning 200 to a 0..150 subrange raises a range-check error at runtime (when compiled with {$R+}).
Strings
Strings — String vs char array
program StringBasics; var greeting: string; name: string; full_msg: string; begin greeting := 'Hello'; name := 'Alice'; full_msg := greeting + ', ' + name + '!'; writeln(full_msg); writeln(Length(full_msg)); end.
#include <stdio.h> #include <string.h> int main(void) { const char *greeting = "Hello"; const char *name = "Alice"; char full_msg[64]; /* must size the buffer yourself */ snprintf(full_msg, sizeof(full_msg), "%s, %s!", greeting, name); printf("%s\n", full_msg); printf("%zu\n", strlen(full_msg)); return 0; }
C has no string type — strings are null-terminated char arrays. You must allocate the buffer (either on the stack with a fixed size or on the heap with malloc), track its capacity, and terminate it with '\0'. snprintf writes at most n-1 characters, always null-terminating the result — always use it over sprintf.
String operations
program StringOps; uses SysUtils; var text: string; begin text := ' Hello, World! '; writeln(Trim(text)); writeln(UpperCase(text)); writeln(Length(text)); writeln(Pos('World', text)); writeln(Copy(text, 3, 5)); end.
#include <stdio.h> #include <string.h> #include <ctype.h> /* C has no trim; loop manually */ void print_trimmed(const char *text) { while (*text == ' ') text++; const char *end = text + strlen(text) - 1; while (end > text && *end == ' ') end--; printf("%.*s\n", (int)(end - text + 1), text); } int main(void) { const char *text = " Hello, World! "; print_trimmed(text); /* no uppercase built-in either — toupper works char-by-char */ printf("%zu\n", strlen(text)); /* length */ /* strstr returns a pointer, not a position integer */ const char *found = strstr(text, "World"); printf("%td\n", found ? (found - text + 1) : 0); return 0; }
C's <string.h> provides strlen, strstr, strcpy, strcat, and strcmp, but many of Pascal's SysUtils conveniences (Trim, UpperCase, Format) have no C equivalent — you write them yourself or use a library. strstr returns a pointer into the string, not a 1-based position.
String comparison
program StringCompare; var a, b: string; begin a := 'apple'; b := 'banana'; if a = b then writeln('equal') else if a < b then writeln('a comes first') else writeln('b comes first'); end.
#include <stdio.h> #include <string.h> int main(void) { const char *a = "apple"; const char *b = "banana"; int result = strcmp(a, b); if (result == 0) printf("equal\n"); else if (result < 0) printf("a comes first\n"); else printf("b comes first\n"); return 0; }
In C, a == b compares pointers, not string contents — it is almost always wrong for string comparison. Use strcmp from <string.h>: it returns 0 for equal, negative if the first string is lexicographically less, positive if greater. This is one of the most common C gotchas for programmers coming from any other language.
Arrays
Static arrays — indexing and bounds
program ArrayStatic; var primes: array[1..5] of integer; index: integer; begin primes[1] := 2; primes[2] := 3; primes[3] := 5; primes[4] := 7; primes[5] := 11; for index := 1 to 5 do write(primes[index], ' '); writeln; { primes[6] raises ERangeError at runtime (with {$R+}) } end.
#include <stdio.h> int main(void) { int primes[5] = {2, 3, 5, 7, 11}; /* 0-indexed: [0]..[4] */ for (int i = 0; i < 5; i++) { printf("%d ", primes[i]); } printf("\n"); /* primes[5] = out of bounds — NO ERROR, undefined behaviour */ return 0; }
Pascal arrays can start at any index; C arrays always start at 0. More critically: Pascal's range-check compiler option ({$R+}) raises an exception on out-of-bounds access; C performs no bounds checking at all. An out-of-bounds access in C is undefined behaviour — it may silently corrupt memory, crash later, or appear to work. This is one of the most dangerous differences.
Iterating arrays
program ArrayIteration; const numbers: array[1..5] of integer = (10, 20, 30, 40, 50); var total: integer; number: integer; begin total := 0; for number in numbers do total := total + number; writeln('Sum: ', total); end.
#include <stdio.h> int main(void) { int numbers[] = {10, 20, 30, 40, 50}; int count = sizeof(numbers) / sizeof(numbers[0]); int total = 0; for (int i = 0; i < count; i++) { total += numbers[i]; } printf("Sum: %d\n", total); return 0; }
C arrays have no built-in length property — a common idiom is sizeof(array) / sizeof(array[0]) to compute the element count from the total byte size. This only works on stack arrays, not pointers. C has no for-each loop for arrays; C++11 added range-based for, but plain C requires an index variable.
Dynamic arrays
program DynamicArray; var numbers: array of integer; index: integer; begin SetLength(numbers, 3); numbers[0] := 10; numbers[1] := 20; numbers[2] := 30; writeln('Length: ', Length(numbers)); for index := Low(numbers) to High(numbers) do write(numbers[index], ' '); writeln; end.
#include <stdio.h> #include <stdlib.h> int main(void) { int count = 3; int *numbers = malloc(count * sizeof(int)); /* allocate */ if (!numbers) { perror("malloc"); return 1; } numbers[0] = 10; numbers[1] = 20; numbers[2] = 30; printf("Length: %d\n", count); for (int i = 0; i < count; i++) { printf("%d ", numbers[i]); } printf("\n"); free(numbers); /* must free */ return 0; }
Pascal's SetLength is handled automatically by the runtime. In C, dynamic arrays are raw heap memory managed with malloc/free. You must track the count yourself (C does not store it), check for allocation failure, and free the memory when done. Forgetting to free is a memory leak; freeing twice is a crash.
Pointers
Pointer syntax
program PointerSyntax; var number: integer; pointer: ^integer; { pointer type uses ^ prefix } begin number := 42; pointer := @number; { @ takes the address } writeln('Value: ', number); writeln('Via pointer: ', pointer^); { ^ after dereferences } pointer^ := 100; writeln('After write: ', number); end.
#include <stdio.h> int main(void) { int number = 42; int *pointer = &number; /* & takes the address */ printf("Value: %d\n", number); printf("Via pointer: %d\n", *pointer); /* * before dereferences */ *pointer = 100; printf("After write: %d\n", number); return 0; }
The pointer symbols are inverted between the languages: Pascal's ^TType declares a pointer type; C's int * declares a pointer to int. Pascal's @variable takes the address; C's &variable takes the address. Pascal's pointer^ dereferences (caret after); C's *pointer dereferences (asterisk before).
Pointer arithmetic
program PtrArithmetic; var numbers: array[0..4] of integer; pointer: ^integer; index: integer; begin for index := 0 to 4 do numbers[index] := (index + 1) * 10; pointer := @numbers[0]; { Pascal pointer arithmetic: Inc/Dec move by element size } for index := 0 to 4 do begin write(pointer^, ' '); Inc(pointer); end; writeln; end.
#include <stdio.h> int main(void) { int numbers[5] = {10, 20, 30, 40, 50}; int *pointer = numbers; /* array decays to pointer to first element */ for (int i = 0; i < 5; i++) { printf("%d ", *pointer); pointer++; /* advances by sizeof(int) bytes */ } printf("\n"); return 0; }
C pointer arithmetic automatically scales by the element size — pointer++ on an int * advances by sizeof(int) bytes, just like Pascal's Inc(pointer). In C, an array name decays to a pointer to its first element in most contexts, which is why pointer = numbers works without &.
Control Flow
if / else
program IfElse; var score: integer; begin score := 75; if score >= 90 then writeln('A') else if score >= 80 then writeln('B') else if score >= 70 then writeln('C') else writeln('F'); end.
#include <stdio.h> int main(void) { int score = 75; if (score >= 90) printf("A\n"); else if (score >= 80) printf("B\n"); else if (score >= 70) printf("C\n"); else printf("F\n"); return 0; }
C's if requires parentheses around the condition — if (condition). Pascal uses then instead. C uses braces {} for multi-statement blocks; Pascal uses begin/end. For single statements, both languages allow the body without braces/begin-end, though braces are recommended in C to avoid the dangling-else ambiguity.
case / switch — watch for fallthrough
program CaseSwitch; var day: integer; begin day := 3; case day of 1: writeln('Monday'); 2: writeln('Tuesday'); 3: writeln('Wednesday'); 4: writeln('Thursday'); 5: writeln('Friday'); 6, 7: writeln('Weekend'); else writeln('Invalid'); end; end.
#include <stdio.h> int main(void) { int day = 3; switch (day) { case 1: printf("Monday\n"); break; case 2: printf("Tuesday\n"); break; case 3: printf("Wednesday\n"); break; case 4: printf("Thursday\n"); break; case 5: printf("Friday\n"); break; case 6: case 7: printf("Weekend\n"); break; default: printf("Invalid\n"); } return 0; }
C's switch falls through from one case to the next unless you add break. Pascal's case does not fall through — each branch exits automatically. This is one of the most common Pascal-to-C bugs: forgetting break runs the next case body. Multiple values in Pascal (6, 7:) become stacked case 6: case 7: labels in C.
Assignment vs equality — the := vs = trap
program AssignEquality; var x: integer; begin x := 5; { := is assignment } if x = 5 then { = is equality comparison } writeln('Five'); { Pascal compiler prevents: if x := 5 then <- syntax error } end.
#include <stdio.h> int main(void) { int x = 5; /* = is assignment */ if (x == 5) /* == is equality */ printf("Five\n"); /* C compilers warn about this but it compiles: */ /* if (x = 5) { ... } <- assigns 5 to x, then tests if non-zero */ return 0; }
Pascal uses := for assignment and = for equality — they are distinct operators. C uses = for assignment and == for equality. The accidental if (x = 5) in C is a legal expression (assigns 5, tests non-zero) — a notoriously silent bug. GCC's -Wall warns about this; writing if (5 == x) ("Yoda condition") prevents it entirely.
Loops
for loop
program ForLoop; var index: integer; begin { Pascal for counts up or down by 1 only } for index := 1 to 5 do write(index, ' '); writeln; for index := 5 downto 1 do write(index, ' '); writeln; end.
#include <stdio.h> int main(void) { /* C for loop: any init, condition, and step */ for (int i = 1; i <= 5; i++) { printf("%d ", i); } printf("\n"); for (int i = 5; i >= 1; i--) { printf("%d ", i); } printf("\n"); return 0; }
Pascal's for is restricted: it always steps by exactly 1 (or -1 for downto), and the loop variable must be an ordinal type. C's for (init; condition; step) is completely general — the step can be any expression (i += 2, i *= 2), and the condition can test anything. C arrays are 0-indexed, so C for loops conventionally start at 0.
while loop
program WhileLoop; var count: integer; begin count := 1; while count <= 5 do begin write(count, ' '); Inc(count); end; writeln; end.
#include <stdio.h> int main(void) { int count = 1; while (count <= 5) { printf("%d ", count); count++; } printf("\n"); return 0; }
The while loop structure is identical in both languages. Pascal uses begin/end for the body; C uses {}. Pascal's Inc(count) increments by 1; C's count++ (postfix) or ++count (prefix) do the same. C also has the += compound assignment: count += 1.
repeat-until vs do-while
program RepeatUntil; var number: integer; begin number := 1; repeat write(number, ' '); Inc(number); until number > 5; writeln; end.
#include <stdio.h> int main(void) { int number = 1; do { printf("%d ", number); number++; } while (number <= 5); printf("\n"); return 0; }
Pascal's repeat...until loops until the condition is true. C's do...while loops while the condition is true. Both execute the body at least once before checking the condition. To convert: negate the Pascal condition — until number > 5 becomes while (number <= 5).
Procedures & Functions
Procedures vs void functions
program Procedures; procedure Greet(name: string); begin writeln('Hello, ', name, '!'); end; begin Greet('Alice'); Greet('Bob'); end.
#include <stdio.h> void greet(const char *name) { printf("Hello, %s!\n", name); } int main(void) { greet("Alice"); greet("Bob"); return 0; }
Pascal distinguishes procedure (no return value) from function (has a return value). C uses only void return type for the equivalent of a procedure — both are just functions syntactically. In C, all functions (including main) must be declared before use, or you must provide a prototype above the call site.
Functions with return values
program Functions; function Square(n: integer): integer; begin Result := n * n; end; function Max(a, b: integer): integer; begin if a > b then Result := a else Result := b; end; begin writeln(Square(5)); writeln(Max(3, 7)); end.
#include <stdio.h> int square(int n) { return n * n; } int max_of(int a, int b) { return (a > b) ? a : b; } int main(void) { printf("%d\n", square(5)); printf("%d\n", max_of(3, 7)); return 0; }
Pascal uses an implicit Result variable (or the function name itself as the target). C uses an explicit return statement. C also has the ternary operator condition ? true_value : false_value — Pascal has no equivalent and requires a full if...then...else. Note: max conflicts with the C standard library macro on some systems — use a different name.
Pass by reference — var vs pointer parameter
program PassByRef; procedure Swap(var a, b: integer); var temp: integer; begin temp := a; a := b; b := temp; end; var x, y: integer; begin x := 10; y := 20; Swap(x, y); writeln(x, ' ', y); end.
#include <stdio.h> void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main(void) { int x = 10, y = 20; swap(&x, &y); /* pass addresses explicitly */ printf("%d %d\n", x, y); return 0; }
Pascal's var parameter is pass-by-reference — the function receives a reference to the caller's variable. C has no pass-by-reference; you pass a pointer explicitly with &x and dereference with *a inside the function. The effect is the same, but C makes the mechanism explicit at every call site.
Records & Structs
Records vs structs
program RecordStruct; type TPerson = record name: string[50]; age: integer; end; var person: TPerson; begin person.name := 'Alice'; person.age := 30; writeln(person.name, ' is ', person.age); end.
#include <stdio.h> struct person { char name[51]; /* +1 for null terminator */ int age; }; int main(void) { struct person alice; snprintf(alice.name, sizeof(alice.name), "Alice"); alice.age = 30; printf("%s is %d\n", alice.name, alice.age); return 0; }
Pascal records and C structs are nearly identical in concept: named fields with types, laid out sequentially in memory. The main syntax differences: C omits the record...end wrapper, uses braces, and requires a semicolon after the closing brace. C struct fields cannot hold Pascal's heap-managed String — strings must be char arrays with a fixed maximum length.
typedef struct — avoiding the struct keyword
program TypedefEquiv; { Pascal: type alias creates a clean name } type TPoint = record x, y: double; end; var origin: TPoint; begin origin.x := 0.0; origin.y := 0.0; writeln(origin.x:4:1, ' ', origin.y:4:1); end.
#include <stdio.h> /* Without typedef: must write "struct point" everywhere */ /* With typedef: write "point_t" */ typedef struct { double x; double y; } point_t; int main(void) { point_t origin = {0.0, 0.0}; /* struct initialiser */ printf("%.1f %.1f\n", origin.x, origin.y); return 0; }
In C, struct person requires the struct keyword every time it is used as a type. The typedef struct { ... } name_t; pattern creates an alias so you can write just name_t. Pascal's type TPerson = record always creates a clean alias. Compound initialisers ({0.0, 0.0}) initialise struct fields in declaration order.
Structs via pointer — arrow operator
program StructPointer; type TPerson = record name: string[50]; age: integer; end; PPerson = ^TPerson; var person: PPerson; begin New(person); person^.name := 'Bob'; person^.age := 25; writeln(person^.name, ' ', person^.age); Dispose(person); end.
#include <stdio.h> #include <stdlib.h> typedef struct { char name[51]; int age; } person_t; int main(void) { person_t *person = malloc(sizeof(person_t)); if (!person) return 1; snprintf(person->name, sizeof(person->name), "Bob"); person->age = 25; printf("%s %d\n", person->name, person->age); free(person); return 0; }
Pascal uses pointer^.field to access a field through a pointer. C's arrow operator pointer->field is shorthand for (*pointer).field. The arrow operator was introduced specifically because dereferencing-then-accessing is so common with struct pointers. person->name reads as "the name field of the struct that person points to."
Memory Management
Heap allocation — New vs malloc
program HeapAlloc; type TIntegerArray = array[0..99] of integer; PIntegerArray = ^TIntegerArray; { a pointer must target a named type } var numbers: PIntegerArray; index: integer; begin New(numbers); for index := 0 to 9 do numbers^[index] := index * 10; writeln(numbers^[5]); Dispose(numbers); { must free } end.
#include <stdio.h> #include <stdlib.h> int main(void) { int count = 10; int *numbers = malloc(count * sizeof(int)); if (!numbers) { perror("malloc"); return 1; } for (int i = 0; i < count; i++) { numbers[i] = i * 10; } printf("%d\n", numbers[5]); free(numbers); /* must free */ return 0; }
Pascal's New(pointer) allocates exactly enough memory for the pointed-to type. C's malloc(n * sizeof(type)) is more explicit — you specify the byte count. Always check for NULL (allocation failure) before using the returned pointer. Both languages require a matching deallocation: Pascal's Dispose and C's free.
Nil / NULL — the null pointer
program NilCheck; type PInteger = ^integer; var ptr: PInteger; begin ptr := nil; if ptr = nil then writeln('Pointer is nil') else writeln('Value: ', ptr^); end.
#include <stdio.h> #include <stddef.h> /* NULL */ int main(void) { int *ptr = NULL; if (ptr == NULL) printf("Pointer is NULL\n"); else printf("Value: %d\n", *ptr); return 0; }
Pascal's nil and C's NULL are both the null pointer constant. NULL is a macro defined in <stddef.h> (and several other headers). In C, dereferencing a null pointer is undefined behaviour — it typically causes a segmentation fault. C11 introduced the more expressive nullptr keyword via <stddef.h>'s nullptr_t; C23 makes nullptr a built-in keyword.
Resizing — SetLength vs realloc
program DynResize; var numbers: array of integer; index: integer; begin SetLength(numbers, 3); numbers[0] := 1; numbers[1] := 2; numbers[2] := 3; SetLength(numbers, 5); { grow: preserves existing elements } numbers[3] := 4; numbers[4] := 5; for index := Low(numbers) to High(numbers) do write(numbers[index], ' '); writeln; end.
#include <stdio.h> #include <stdlib.h> int main(void) { int count = 3; int *numbers = malloc(count * sizeof(int)); numbers[0] = 1; numbers[1] = 2; numbers[2] = 2; count = 5; int *grown = realloc(numbers, count * sizeof(int)); if (!grown) { free(numbers); return 1; } numbers = grown; /* realloc may move the allocation */ numbers[3] = 4; numbers[4] = 5; for (int i = 0; i < count; i++) printf("%d ", numbers[i]); printf("\n"); free(numbers); return 0; }
realloc resizes a heap allocation, preserving existing contents — the C equivalent of Pascal's SetLength. Crucially, realloc may return a different pointer if it needs to move the data to find a larger block. Always assign the result to a separate pointer before overwriting the original, so you can free the old block if realloc fails.
Type System Differences
Type safety — Pascal strict vs C permissive
program TypeSafety; var integer_val: integer; double_val: double; begin double_val := 3.7; integer_val := Round(double_val); { explicit conversion required } writeln(integer_val); { Pascal compiler rejects: integer_val := double_val; (type mismatch) } end.
#include <stdio.h> int main(void) { double double_val = 3.7; int integer_val = double_val; /* implicit truncation — no error */ /* -Wconversion would warn, but it compiles */ printf("%d\n", integer_val); /* prints 3, not 4 */ return 0; }
Pascal is more strictly typed than C: assigning a double to an integer is a compile error; you must call Round or Trunc explicitly. C silently truncates — int x = 3.7 gives 3 without any error. GCC's -Wconversion flag adds warnings for these implicit conversions, but they remain legal C.
Enumeration types
program EnumTypes; type TColor = (Red, Green, Blue); var color: TColor; begin color := Green; case color of Red: writeln('Red'); Green: writeln('Green'); Blue: writeln('Blue'); end; writeln(Ord(color)); { ordinal value: 1 } end.
#include <stdio.h> typedef enum { RED, GREEN, BLUE } color_t; int main(void) { color_t color = GREEN; switch (color) { case RED: printf("Red\n"); break; case GREEN: printf("Green\n"); break; case BLUE: printf("Blue\n"); break; } printf("%d\n", (int)color); /* ordinal value: 1 */ return 0; }
C enums are less type-safe than Pascal's: a C enum value is just an int, and you can assign any integer to an enum variable without a cast. Pascal enum variables can only hold valid enum values, and the compiler enforces this. Conventionally, C enum constants are all-caps (RED) to distinguish them from variables.
Sets — bit operations instead
program SetTypes; type TPermission = (Read, Write, Execute); TPermissions = set of TPermission; var perms: TPermissions; begin perms := [Read, Execute]; if Read in perms then writeln('Can read'); Include(perms, Write); perms := perms - [Execute]; writeln(Write in perms, ' ', Execute in perms); end.
#include <stdio.h> #include <stdbool.h> #define PERM_READ (1 << 0) /* 0b001 */ #define PERM_WRITE (1 << 1) /* 0b010 */ #define PERM_EXECUTE (1 << 2) /* 0b100 */ int main(void) { unsigned int perms = PERM_READ | PERM_EXECUTE; if (perms & PERM_READ) printf("Can read\n"); perms |= PERM_WRITE; /* Include */ perms &= ~PERM_EXECUTE; /* Exclude */ printf("%d %d\n", (bool)(perms & PERM_WRITE), (bool)(perms & PERM_EXECUTE)); return 0; }
Pascal's set of type is a high-level bit-set abstraction with readable operators (in, +, -, *). C has no equivalent — the idiom is manual bitfield manipulation with | (union), & (intersection), and &~ (set difference). The #define flag approach is conventional and efficient, but loses all type safety.
Range types — no C equivalent
program RangeTypes; type TMonth = 1..12; TYear = 1900..2100; TPercent = real; { only ordinal types have subranges; reals do not } var month: TMonth; year: TYear; percent: TPercent; begin month := 6; year := 2024; percent := 75.5; writeln(month, ' ', year, ' ', percent:5:1); { With {$R+}: month := 13 raises ERangeError at runtime } end.
#include <stdio.h> #include <assert.h> /* C has no range types — validate manually or use assertions */ void set_month(int *month, int value) { assert(value >= 1 && value <= 12); /* debug only */ *month = value; } int main(void) { int month; int year = 2024; double percent = 75.5; set_month(&month, 6); /* set_month(&month, 13); would assert-fail in debug */ printf("%d %d %.1f\n", month, year, percent); return 0; }
Pascal's subrange types (1..12) are a first-class feature: the type system knows the valid range, and the runtime can verify it. C has no equivalent — you validate constraints manually. assert from <assert.h> is stripped in release builds (-DNDEBUG). Third-party C libraries (like those based on GNU's checked types) can add bounds checking, but it is never automatic.