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.