Program Structure
Hello, World
program hello
implicit none
print *, "Hello, World!"
end program hello #include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
} A Fortran program is a single
program name ... end program name block. C has no dedicated program wrapper — instead, execution always begins at a specific function named main, which must explicitly return 0 to signal success to the operating system (Fortran needs no equivalent statement; falling off the end of the program implies success).Comments
program comments_demo
implicit none
! This is a full-line comment
integer :: total ! This is a trailing comment
total = 10
print *, total
end program comments_demo #include <stdio.h>
int main(void) {
/* This is a full-line comment */
int total = 10; // This is a trailing comment (C99+)
printf("%d\n", total);
return 0;
} Fortran comments use
!, running to the end of the line. C has two comment forms: /* ... */ block comments (available since C89, can span multiple lines but do not nest) and // single-line comments (added in C99, matching Fortran's style more closely).Ahead-of-Time Compilation (Both Languages Agree)
program compile_demo
implicit none
integer :: value
value = 42
print *, value
end program compile_demo #include <stdio.h>
int main(void) {
int value = 42;
printf("%d\n", value);
return 0;
} Both languages agree here, unlike R or Julia: Fortran and C are both compiled ahead of time to a native binary before they run, with no JIT and no separate interpreter step.
gfortran example.f90 -o example and gcc example.c -o example are the same two-step compile-then-execute workflow.IMPLICIT NONE vs. Mandatory Declarations (Agreement)
program implicit_none_demo
implicit none
integer :: count
count = 5
print *, count
end program implicit_none_demo #include <stdio.h>
int main(void) {
int count = 5;
printf("%d\n", count);
return 0;
} Fortran with
implicit none requires every variable to be declared with an explicit type. C has always required this unconditionally, with no equivalent of Fortran's pre-implicit none implicit-typing history — every C variable has always needed a declared type before use, in every version of the language.Data Types
KIND System vs. Sized Integer Types (Both Agree)
program sized_integer_demo
use iso_fortran_env, only: int32, int64
implicit none
integer(kind=int32) :: normal_number
integer(kind=int64) :: large_number
normal_number = 2147483647
large_number = 9000000000_int64
print *, normal_number
print *, large_number
end program sized_integer_demo #include <stdio.h>
#include <stdint.h>
int main(void) {
int32_t normalNumber = 2147483647;
int64_t largeNumber = 9000000000LL;
printf("%d\n", normalNumber);
printf("%lld\n", (long long)largeNumber);
return 0;
} Both languages let a programmer choose an exact integer width deliberately: Fortran's
integer(kind=int32)/integer(kind=int64) from iso_fortran_env map directly onto C's int32_t/int64_t from <stdint.h> — the same idea, the same widths, a near-identical mechanism.REAL/DOUBLE PRECISION vs. float/double
program real_types_demo
implicit none
real :: single_value
double precision :: double_value
single_value = 3.14159
double_value = 3.14159265358979d0
print *, single_value
print *, double_value
end program real_types_demo #include <stdio.h>
int main(void) {
float singleValue = 3.14159f;
double doubleValue = 3.14159265358979;
printf("%f\n", singleValue);
printf("%.14f\n", doubleValue);
return 0;
} Fortran's
real and double precision map directly onto C's float and double — both languages give the programmer explicit 32-bit and 64-bit IEEE 754 floating-point types with no default-precision ambiguity, right down to needing a suffix to mark a single-precision literal (3.14159f in C, 3.14159e0 in Fortran) versus the double-precision default.LOGICAL vs. Bool (a Historically Recent C Addition)
program logical_demo
implicit none
logical :: is_ready
is_ready = .true.
if (is_ready) then
print *, "Ready"
end if
end program logical_demo #include <stdio.h>
#include <stdbool.h>
int main(void) {
bool isReady = true;
if (isReady) {
printf("Ready\n");
}
return 0;
} Fortran has had a genuine
logical boolean type since its earliest days. C did not gain a real boolean type until C99's <stdbool.h> header (which defines bool, true, and false as macros over _Bool) — before that, C conditions used plain int, where any nonzero value meant true. A Fortran programmer's reflex that "of course there's a boolean type" needs the explicit #include <stdbool.h> in C.CHARACTER(len=n) vs. Fixed char Array (Both Agree)
program fixed_char_demo
implicit none
character(len=10) :: name
name = "Alice"
print *, "Name: [", trim(name), "]"
end program fixed_char_demo #include <stdio.h>
#include <string.h>
int main(void) {
char name[11];
strcpy(name, "Alice");
printf("Name: [%s]\n", name);
return 0;
} Both languages agree on fixed-width character storage, unlike R or Julia's dynamic strings — a genuine similarity between two low-level languages. The size mismatch to watch: Fortran's
character(len=10) holds exactly 10 characters, blank-padded; C's char name[11] needs one extra byte beyond the 10 visible characters for the mandatory null terminator ('\0'), a concept Fortran strings have no equivalent of at all.POINTER Attribute vs. Pointer Type
program pointer_demo
implicit none
integer, target :: value
integer, pointer :: value_pointer
value = 42
value_pointer => value
print *, value_pointer
end program pointer_demo #include <stdio.h>
int main(void) {
int value = 42;
int *valuePointer = &value;
printf("%d\n", *valuePointer);
return 0;
} Fortran's
pointer attribute requires the thing it points to be explicitly marked target first, and association uses the distinct => operator (never plain =) so the compiler can tell "point at this" apart from "copy this value." C has no such restriction — any variable's address can be taken with & at any time with no prior declaration, and dereferencing uses the prefix * operator rather than a bare name.Variables & Constants
PARAMETER vs. const
program parameter_demo
implicit none
real, parameter :: pi = 3.14159265358979
real, parameter :: gravity = 9.81
print *, "Pi:", pi
print *, "Gravity:", gravity
end program parameter_demo #include <stdio.h>
int main(void) {
const double pi = 3.14159265358979;
const double gravity = 9.81;
printf("Pi: %f\n", pi);
printf("Gravity: %f\n", gravity);
return 0;
} Fortran's
parameter attribute and C's const qualifier both mark a value the compiler rejects any attempt to reassign — a close match. One difference: C's preprocessor also offers #define PI 3.14159265358979 as an older, type-unchecked alternative with no Fortran equivalent at all, since Fortran has no preprocessor built into the base language.Assignment
program assignment_demo
implicit none
integer :: count
count = 5
count = count + 1
print *, count
end program assignment_demo #include <stdio.h>
int main(void) {
int count = 5;
count = count + 1;
printf("%d\n", count);
return 0;
} Both languages use a bare
= for assignment. C additionally has compound assignment operators (+=, -=, and so on) and pre/post increment (count++), none of which Fortran has — Fortran always spells out count = count + 1 in full.Declaring Several Variables at Once
program multiple_demo
implicit none
integer :: first_number, second_number, third_number
first_number = 1
second_number = 2
third_number = 3
print *, first_number, second_number, third_number
end program multiple_demo #include <stdio.h>
int main(void) {
int firstNumber = 1, secondNumber = 2, thirdNumber = 3;
printf("%d %d %d\n", firstNumber, secondNumber, thirdNumber);
return 0;
} Both languages let a comma-separated list of names share one type declaration — Fortran's
integer :: a, b, c and C's int a, b, c; read almost identically.Arrays
⚠ 1-Based Indexing vs. 0-Based Indexing
program one_based_demo
implicit none
integer :: numbers(5)
numbers = [10, 20, 30, 40, 50]
print *, "First:", numbers(1)
print *, "Last:", numbers(5)
end program one_based_demo #include <stdio.h>
int main(void) {
int numbers[5] = {10, 20, 30, 40, 50};
printf("First: %d\n", numbers[0]);
printf("Last: %d\n", numbers[4]);
return 0;
} Fortran arrays default to 1-based indexing (the first element is
numbers(1)); C arrays are always 0-based (the first element is numbers[0]), with no way to change this. A 5-element array's last valid index is 5 in Fortran but 4 in C — one of the most common off-by-one traps when translating an algorithm between the two languages.⚠ Column-Major vs. Row-Major Storage
program column_major_demo
implicit none
integer :: matrix(2,3)
integer :: i
matrix = reshape([1, 2, 3, 4, 5, 6], [2,3])
! Fortran stores column-by-column: memory layout is 1,2, 3,4, 5,6
print *, "Memory order:"
do i = 0, 5
print *, matrix(mod(i,2)+1, i/2+1)
end do
end program column_major_demo #include <stdio.h>
int main(void) {
/* C stores row-by-row: memory layout is 1,2,3, 4,5,6 */
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
int *flat = &matrix[0][0];
printf("Memory order:\n");
for (int i = 0; i < 6; i++) {
printf("%d\n", flat[i]);
}
return 0;
} This is the headline structural difference between these two languages. Fortran stores multi-dimensional arrays column-major: the first index varies fastest in memory, so a 2×3 array's elements sit in memory in the order (1,1), (2,1), (1,2), (2,2), (1,3), (2,3). C stores arrays row-major: the last index varies fastest, so a 2×3 array's elements sit in memory as (0,0), (0,1), (0,2), (1,0), (1,1), (1,2) — the opposite convention entirely. This matters enormously in practice: looping in the wrong nesting order for a language's storage layout causes cache-unfriendly memory access patterns that can slow numeric code down by an order of magnitude, and passing a matrix between Fortran and C code (a common scenario when Fortran numerical libraries are called from C, or vice versa) silently transposes it unless the interop code explicitly accounts for the difference.
⚠ No Fortran Equivalent — Arrays Decay to Pointers
program array_size_preserved_demo
implicit none
integer :: numbers(5)
numbers = [10, 20, 30, 40, 50]
call print_array(numbers)
contains
subroutine print_array(values)
! Fortran arrays retain their size information when passed —
! size(values) genuinely works inside the subroutine.
integer, intent(in) :: values(:)
print *, "Size:", size(values)
end subroutine print_array
end program array_size_preserved_demo #include <stdio.h>
void printArraySize(int values[]) {
/* "values" has already decayed to a plain int* here —
sizeof(values) gives the POINTER's size (8), not the
array's size. The element count must be passed separately. */
printf("sizeof inside function: %zu\n", sizeof(values));
}
int main(void) {
int numbers[5] = {10, 20, 30, 40, 50};
printf("sizeof in main: %zu\n", sizeof(numbers));
printArraySize(numbers);
return 0;
} A Fortran assumed-shape array argument (
values(:)) genuinely knows its own size — size(values) works correctly inside the subroutine because Fortran passes array metadata (a "descriptor"), not just a raw address. A C array parameter, by contrast, immediately "decays" into a plain pointer to its first element the moment it crosses a function boundary — sizeof(values) inside printArraySize returns the size of a pointer (8 bytes on a 64-bit system), not the size of the original 5-element array. This is why virtually every C function that takes an array also takes a separate integer size parameter — the array itself cannot tell you how big it is.No Bounds Checking by Default (Both Agree)
program out_of_bounds_demo
implicit none
integer :: numbers(3)
numbers = [1, 2, 3]
! Without the -fcheck=bounds compiler flag, this reads
! past the end of the array with no error at all.
print *, numbers(1)
end program out_of_bounds_demo #include <stdio.h>
int main(void) {
int numbers[3] = {1, 2, 3};
/* C never checks bounds, with no compiler flag that adds it
retroactively the way Fortran's -fcheck=bounds does. */
printf("%d\n", numbers[0]);
return 0;
} Neither language checks array bounds by default — reading or writing past the end of an array is undefined behavior in both, silently corrupting memory or crashing unpredictably rather than raising a clean error. The one asymmetry: gfortran offers an opt-in
-fcheck=bounds compile flag that adds bounds checking for debugging; standard C has no equivalent flag at all (some platforms offer sanitizers like AddressSanitizer as a separate, more heavyweight tool, but nothing built into the compiler the way Fortran's flag is).Two-Dimensional Array Declaration
program two_d_demo
implicit none
integer :: matrix(2,3)
integer :: row, col
do row = 1, 2
do col = 1, 3
matrix(row, col) = 0
end do
end do
matrix(1, 2) = 5
print *, matrix(1, 2)
end program two_d_demo #include <stdio.h>
int main(void) {
int matrix[2][3] = {0};
matrix[0][1] = 5;
printf("%d\n", matrix[0][1]);
return 0;
} Fortran indexes a 2D array with comma-separated subscripts inside one set of parentheses:
matrix(row, col). C uses a separate bracket pair per dimension: matrix[row][col]. Watch the index shift alongside the row-major/column-major difference: Fortran's matrix(1, 2) (1-based) is C's matrix[0][1] (0-based) for the logically equivalent "first row, second column" element.No Whole-Array Arithmetic in C
program whole_array_demo
implicit none
real :: prices(4)
prices = [10.0, 20.0, 30.0, 40.0]
prices = prices * 0.9
print *, prices
end program whole_array_demo #include <stdio.h>
int main(void) {
double prices[4] = {10.0, 20.0, 30.0, 40.0};
for (int i = 0; i < 4; i++) {
prices[i] = prices[i] * 0.9;
}
for (int i = 0; i < 4; i++) {
printf("%.2f ", prices[i]);
}
printf("\n");
return 0;
} Fortran treats an array as a value you can operate on directly —
prices = prices * 0.9 scales every element in one statement with no loop. C, like Pascal, has no whole-array operators at all; scaling every element always requires an explicit for loop written by hand.Pointers & Argument Passing
INTENT(IN) vs. const Parameter
program intent_in_demo
implicit none
call display_score("Alice", 95)
contains
subroutine display_score(person_name, score)
character(len=*), intent(in) :: person_name
integer, intent(in) :: score
print *, trim(person_name), ": ", score
end subroutine display_score
end program intent_in_demo #include <stdio.h>
void displayScore(const char *personName, int score) {
printf("%s: %d\n", personName, score);
}
int main(void) {
displayScore("Alice", 95);
return 0;
} Fortran's
intent(in) and C's const qualifier both tell the compiler a parameter is read-only, rejecting any attempt to modify it inside the function. Plain scalar C parameters like score are always read-only in the caller's sense anyway (C passes by value, so modifying the local copy never affects the caller) — const matters most for pointer parameters like personName, protecting the pointed-to data from modification through that pointer.⚠ INTENT(OUT) vs. Explicit Output Pointer
program intent_out_demo
implicit none
real :: area, circumference
call circle_stats(5.0, area, circumference)
print *, "Area:", area
print *, "Circumference:", circumference
contains
subroutine circle_stats(radius, area_out, circ_out)
real, intent(in) :: radius
real, intent(out) :: area_out, circ_out
real, parameter :: pi = 3.14159265358979
area_out = pi * radius * radius
circ_out = 2.0 * pi * radius
end subroutine circle_stats
end program intent_out_demo #include <stdio.h>
void circleStats(double radius, double *areaOut, double *circOut) {
const double pi = 3.14159265358979;
*areaOut = pi * radius * radius;
*circOut = 2.0 * pi * radius;
}
int main(void) {
double area, circumference;
circleStats(5.0, &area, &circumference);
printf("Area: %f\n", area);
printf("Circumference: %f\n", circumference);
return 0;
} This is half of the headline difference. Fortran's
intent(out) parameter is really a pointer under the hood, but the syntax hides that completely — area_out = ... looks like an ordinary assignment. C has no equivalent implicit mechanism: because C passes by value, "returning" a second value through a parameter requires the caller to explicitly pass a pointer (&area) and the function to explicitly dereference it on every write (*areaOut = ...). Forgetting either the & at the call site or the * inside the function is a common, and sometimes silently wrong-but-compiling, C bug that Fortran's intent(out) mechanism makes structurally impossible.⚠ Pass-by-Reference Default vs. Pass-by-Value Default
program pass_by_reference_demo
implicit none
integer :: value
value = 5
call double_it(value)
print *, value
contains
subroutine double_it(number)
! Fortran passes "number" by reference by default — modifying
! it here modifies the CALLER's variable too, with no pointer
! syntax needed anywhere.
integer, intent(inout) :: number
number = number * 2
end subroutine double_it
end program pass_by_reference_demo #include <stdio.h>
void doubleIt(int number) {
/* C passes "number" by VALUE by default — this modifies only
the LOCAL copy. The caller's variable is completely
unaffected, with no warning or error of any kind. */
number = number * 2;
}
void doubleItByReference(int *number) {
/* To actually modify the caller's variable, C requires an
explicit pointer parameter and explicit dereference. */
*number = *number * 2;
}
int main(void) {
int value = 5;
doubleIt(value);
printf("After pass-by-value call: %d\n", value); /* still 5 */
doubleItByReference(&value);
printf("After pass-by-reference call: %d\n", value); /* now 10 */
return 0;
} This is the other half of the headline difference, and the single most consequential one for anyone porting code between the two languages. Fortran passes every argument by reference by default — even a plain
integer parameter with no special attribute is implemented as a hidden pointer, so a subroutine that reassigns its parameter silently changes the caller's variable too, unless the parameter is marked intent(in) (which is a promise the compiler checks, not a change in the underlying calling convention). C passes every argument by value by default — a function can freely reassign its own parameter with zero effect on the caller, and modifying the caller's actual variable requires an explicit pointer parameter, as the second function here demonstrates. A Fortran programmer's reflex that "the subroutine already sees my real variable" is exactly backwards for C.NULL() vs. NULL Pointer
program null_pointer_demo
implicit none
integer, pointer :: maybe_value
maybe_value => null()
print *, associated(maybe_value)
end program null_pointer_demo #include <stdio.h>
#include <stddef.h>
int main(void) {
int *maybeValue = NULL;
printf("%d\n", maybeValue != NULL);
return 0;
} Fortran's
null() intrinsic and C's NULL macro (from <stddef.h>, or several other standard headers) both represent a pointer that points to nothing. Fortran checks this safely with the dedicated associated() intrinsic; C compares directly against NULL with an ordinary equality operator, since a C pointer is just an address with no separate "association state" the language tracks specially.No Fortran Equivalent — Raw Pointer Arithmetic
program array_indexing_demo
implicit none
integer :: numbers(5)
numbers = [10, 20, 30, 40, 50]
! Fortran always indexes through the array name — there is no
! way to "walk" a raw memory address the way C pointer
! arithmetic does.
print *, numbers(3)
end program array_indexing_demo #include <stdio.h>
int main(void) {
int numbers[5] = {10, 20, 30, 40, 50};
int *pointer = numbers; /* array decays to a pointer to numbers[0] */
printf("%d\n", *(pointer + 2)); /* same as numbers[2] */
pointer++; /* now points at numbers[1] */
printf("%d\n", *pointer);
return 0;
} Fortran array access always goes through the array's own name and an index — there is no way to obtain a raw, incrementable memory address and "walk" it manually the way C does. In C,
*(pointer + 2) is defined to mean exactly the same thing as pointer[2], and a pointer variable itself can be incremented (pointer++) to advance to the next element's address — a genuinely lower-level capability with no Fortran counterpart, and a common source of C bugs when the arithmetic drifts past the array's actual bounds.Arithmetic
Integer Division & Modulo (Both Agree)
program division_demo
implicit none
integer :: numerator, denominator, quotient, remainder_value
numerator = 17
denominator = 5
quotient = numerator / denominator
remainder_value = mod(numerator, denominator)
print *, "Quotient:", quotient
print *, "Remainder:", remainder_value
end program division_demo #include <stdio.h>
int main(void) {
int numerator = 17;
int denominator = 5;
int quotient = numerator / denominator;
int remainderValue = numerator % denominator;
printf("Quotient: %d\n", quotient);
printf("Remainder: %d\n", remainderValue);
return 0;
} Both languages agree: plain
/ performs integer division (truncating toward zero) when both operands are integers, and the remainder operator (mod() in Fortran, % in C) has matching truncating semantics in both languages since C99 standardized truncation-toward-zero for negative operands.⚠ ** Operator vs. No Exponentiation Operator at All
program exponent_demo
implicit none
real :: result_value
result_value = 2.0 ** 10
print *, "2^10 =", result_value
end program exponent_demo #include <stdio.h>
#include <math.h>
int main(void) {
double resultValue = pow(2.0, 10);
printf("2^10 = %f\n", resultValue);
return 0;
} Fortran has a dedicated
** exponentiation operator built into the language. C has no exponentiation operator at all — raising a number to a power always goes through the pow() function from <math.h>. (A common trap for programmers used to other C-family languages: ^ in C means bitwise XOR, not exponentiation — writing 2 ^ 10 compiles without error but silently computes something completely unrelated.)Math Intrinsics vs. math.h (Needs an Import AND a Link Flag)
program math_intrinsics_demo
implicit none
print *, "sqrt(144):", sqrt(144.0)
print *, "sin(0.0):", sin(0.0)
end program math_intrinsics_demo #include <stdio.h>
#include <math.h>
int main(void) {
printf("sqrt(144): %f\n", sqrt(144.0));
printf("sin(0.0): %f\n", sin(0.0));
return 0;
} Fortran's
sqrt, sin, and other math functions are intrinsic — always available with no import. C requires #include <math.h> for the equivalent functions, and on many platforms/toolchains also requires linking against the math library explicitly with the -lm compiler flag — a two-part requirement (header AND linker flag) that Fortran has no equivalent of at all.NINT/INT vs. round/trunc
program rounding_demo
implicit none
real :: value
value = 3.7
print *, "nint:", nint(value)
print *, "int (truncate):", int(value)
print *, "floor:", floor(value)
print *, "ceiling:", ceiling(value)
end program rounding_demo #include <stdio.h>
#include <math.h>
int main(void) {
double value = 3.7;
printf("round: %.0f\n", round(value));
printf("trunc: %.0f\n", trunc(value));
printf("floor: %.0f\n", floor(value));
printf("ceil: %.0f\n", ceil(value));
return 0;
} Fortran's
nint()/int()/floor()/ceiling() map directly onto C's round()/trunc()/floor()/ceil() from <math.h> — same four operations, nearly the same names, both requiring the header import in C that Fortran does not need.Strings
⚠ // Operator vs. No Concatenation Operator at All
program concat_demo
implicit none
character(len=20) :: first_name, last_name
character(len=41) :: full_name
first_name = "Alice"
last_name = "Smith"
full_name = trim(first_name) // " " // trim(last_name)
print *, trim(full_name)
end program concat_demo #include <stdio.h>
#include <string.h>
int main(void) {
char firstName[20] = "Alice";
char lastName[20] = "Smith";
char fullName[41];
strcpy(fullName, firstName);
strcat(fullName, " ");
strcat(fullName, lastName);
printf("%s\n", fullName);
return 0;
} Fortran concatenates with the dedicated
// operator in a single expression. C has no concatenation operator whatsoever — building a combined string always means an explicit strcpy() into a large-enough destination buffer followed by one or more strcat() calls, each of which re-scans the destination for its existing null terminator, an even more manual and error-prone process than Fortran's already-explicit //.LEN_TRIM vs. strlen()
program length_demo
implicit none
character(len=20) :: greeting
greeting = "Hello"
print *, "Trimmed length:", len_trim(greeting)
end program length_demo #include <stdio.h>
#include <string.h>
int main(void) {
char greeting[20] = "Hello";
printf("%zu\n", strlen(greeting));
return 0;
} Fortran needs
len_trim() to get the length ignoring trailing blank padding. C's strlen() counts characters up to (but not including) the null terminator — conceptually similar to len_trim()'s "ignore the meaningless part," except C's meaningless part is everything after a single sentinel byte rather than trailing spaces.Substring Slicing vs. Manual Extraction
program substring_demo
implicit none
character(len=11) :: phrase
phrase = "Hello World"
print *, phrase(1:5)
print *, phrase(7:11)
end program substring_demo #include <stdio.h>
#include <string.h>
int main(void) {
char phrase[] = "Hello World";
char firstWord[6];
strncpy(firstWord, phrase, 5);
firstWord[5] = '\0'; /* strncpy does not null-terminate automatically */
printf("%s\n", firstWord);
printf("%s\n", phrase + 6); /* pointer arithmetic reaches "World" directly */
return 0;
} Fortran's bracket syntax
phrase(1:5) extracts a substring directly, with the result automatically sized. C has no substring operator or function at all — extracting one requires strncpy() into a separate buffer, and the programmer must remember to add the null terminator manually, since strncpy() famously does not add one if the source is at least as long as the requested count. Reaching a substring that runs to the end of the string is simpler: plain pointer arithmetic (phrase + 6) already produces a valid, null-terminated C string starting from that offset.⚠ No Null Terminator in Fortran vs. Mandatory in C
program no_terminator_demo
implicit none
character(len=5) :: word
word = "Hello"
! Fortran knows the length is exactly 5 from the declared
! len=5 — there is no sentinel byte marking the end at all.
print *, len(word)
end program no_terminator_demo #include <stdio.h>
#include <string.h>
int main(void) {
char word[6] = "Hello"; /* 5 visible chars + 1 for '\0' */
/* word[5] is the null terminator — C's ONLY way of knowing
where the string ends. Omit the extra byte, or overwrite
the terminator, and every string function reads garbage
past the end looking for a '\0' that isn't there. */
printf("%zu\n", strlen(word));
return 0;
} Fortran strings carry their length as separate metadata (the declared
len=) — there is no in-band sentinel character marking the end, and a Fortran string can freely contain any byte value, including one that would be a null byte. C strings are exactly the opposite: the only way any C string function knows where a string ends is by scanning for a null byte ('\0'), so every C character buffer needs one extra byte beyond its visible content, and every string operation that overwrites or omits that terminator causes the next read to run past the buffer looking for a terminator that may not exist for a long, dangerous distance. This single design difference is the root cause of an enormous fraction of historical C security vulnerabilities, and has no Fortran parallel at all.Control Flow
IF / ELSE IF vs. if/else if
program if_demo
implicit none
integer :: score
score = 85
if (score >= 90) then
print *, "Grade: A"
else if (score >= 80) then
print *, "Grade: B"
else
print *, "Grade: C or below"
end if
end program if_demo #include <stdio.h>
int main(void) {
int score = 85;
if (score >= 90) {
printf("Grade: A\n");
} else if (score >= 80) {
printf("Grade: B\n");
} else {
printf("Grade: C or below\n");
}
return 0;
} Both languages support a flat multi-branch chain with matching structure: Fortran's
if / else if / else / end if and C's if / else if / else with brace-delimited blocks read almost identically.⚠ SELECT CASE Never Falls Through, C switch Does
program select_case_demo
implicit none
integer :: day_number
day_number = 3
select case (day_number)
case (1)
print *, "Monday"
case (2)
print *, "Tuesday"
case (3)
print *, "Wednesday"
case default
print *, "Some other day"
end select
end program select_case_demo #include <stdio.h>
int main(void) {
int dayNumber = 3;
switch (dayNumber) {
case 1:
printf("Monday\n");
break; /* WITHOUT this break, execution falls through */
case 2:
printf("Tuesday\n");
break;
case 3:
printf("Wednesday\n");
break; /* omitting this would also print the default case! */
default:
printf("Some other day\n");
}
return 0;
} Fortran's
select case never falls through to the next branch — each case is fully independent, just like most modern languages. C's switch falls through by default: without an explicit break statement at the end of each case, execution continues into the next case's code regardless of whether its label matches. Forgetting a single break is one of the most common and notorious C bugs, with no Fortran equivalent risk at all..AND./.OR. vs. &&/||
program logical_ops_demo
implicit none
logical :: is_weekday, is_holiday
is_weekday = .true.
is_holiday = .false.
if (is_weekday .and. .not. is_holiday) then
print *, "Office is open"
end if
end program logical_ops_demo #include <stdio.h>
#include <stdbool.h>
int main(void) {
bool isWeekday = true;
bool isHoliday = false;
if (isWeekday && !isHoliday) {
printf("Office is open\n");
}
return 0;
} Fortran surrounds its logical operators with dots (
.and., .or., .not.). C uses the symbols &&, ||, ! — both operators short-circuit (stop evaluating as soon as the result is determined) in both languages.Loops
DO Loop vs. Three-Part for Loop
program do_loop_demo
implicit none
integer :: i
do i = 1, 5
print *, "Iteration:", i
end do
end program do_loop_demo #include <stdio.h>
int main(void) {
for (int i = 1; i <= 5; i++) {
printf("Iteration: %d\n", i);
}
return 0;
} Fortran's
do i = 1, 5 declares the start and (inclusive) end of a count in one compact clause. C's for loop spells out all three parts explicitly and separately: an initializer (int i = 1), a continuation condition (i <= 5, using <= since C has no inclusive-range shorthand), and an increment (i++) — considerably more verbose, but correspondingly more flexible, since each of the three parts can be any valid C expression, not just a simple count.DO WHILE vs. while
program do_while_demo
implicit none
integer :: balance
balance = 1000
do while (balance > 0)
balance = balance - 300
print *, "Balance:", balance
end do
end program do_while_demo #include <stdio.h>
int main(void) {
int balance = 1000;
while (balance > 0) {
balance = balance - 300;
printf("Balance: %d\n", balance);
}
return 0;
} Fortran's
do while (condition) and C's while (condition) { } are functionally identical — both test the condition before each iteration and may run zero times.C's do/while Has No Direct Fortran Equivalent
program simulated_post_test_demo
implicit none
integer :: attempts
logical :: keep_going
attempts = 0
keep_going = .true.
do while (keep_going)
attempts = attempts + 1
print *, "Attempt:", attempts
if (attempts >= 3) keep_going = .false.
end do
end program simulated_post_test_demo #include <stdio.h>
int main(void) {
int attempts = 0;
do {
attempts = attempts + 1;
printf("Attempt: %d\n", attempts);
} while (attempts < 3);
return 0;
} C's
do { ... } while (condition); tests its condition after the loop body, guaranteeing at least one execution — the classic post-tested loop. Fortran has no direct equivalent construct; a Fortran programmer must simulate one with a pre-tested do while and a manually managed flag (as shown here), or an unconditional do with an if/exit placed at the bottom of the loop body.EXIT/CYCLE vs. break/continue
program exit_cycle_demo
implicit none
integer :: i
do i = 1, 10
if (mod(i, 2) == 0) cycle
if (i > 7) exit
print *, i
end do
end program exit_cycle_demo #include <stdio.h>
int main(void) {
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue;
}
if (i > 7) {
break;
}
printf("%d\n", i);
}
return 0;
} Fortran's
cycle (skip to the next iteration) and exit (break out of the loop) map onto C's continue and break — the same two concepts, different names for the first one.Functions
SUBROUTINE/FUNCTION Split vs. One Unified Concept
program split_demo
implicit none
call greet("Alice")
print *, "Fahrenheit:", to_fahrenheit(100.0)
contains
subroutine greet(person_name)
character(len=*), intent(in) :: person_name
print *, "Hello, " // trim(person_name) // "!"
end subroutine greet
function to_fahrenheit(celsius) result(fahrenheit)
real, intent(in) :: celsius
real :: fahrenheit
fahrenheit = celsius * 9.0 / 5.0 + 32.0
end function to_fahrenheit
end program split_demo #include <stdio.h>
void greet(const char *personName) {
printf("Hello, %s!\n", personName);
}
double toFahrenheit(double celsius) {
return celsius * 9.0 / 5.0 + 32.0;
}
int main(void) {
greet("Alice");
printf("Fahrenheit: %f\n", toFahrenheit(100.0));
return 0;
} Fortran distinguishes two kinds of callable unit: a
subroutine (called with call, returns nothing, communicates through arguments) and a function (called like an expression, returns exactly one value via result()). C unifies both into a single concept — an ordinary function — with a void return type standing in for "returns nothing," playing the role Fortran's subroutine keyword plays, but with no separate keyword or calling syntax needed.INTERFACE Block vs. Function Prototype
program interface_demo
implicit none
interface
function square(n) result(product_value)
integer, intent(in) :: n
integer :: product_value
end function square
end interface
print *, square(7)
end program interface_demo
function square(n) result(product_value)
implicit none
integer, intent(in) :: n
integer :: product_value
product_value = n * n
end function square #include <stdio.h>
int square(int n); /* prototype: declares the signature up front */
int main(void) {
printf("%d\n", square(7));
return 0;
}
int square(int n) {
return n * n;
} Fortran's explicit
interface block and C's function prototype (int square(int n);) serve the same purpose: telling the compiler a function's signature before its actual definition appears later in the file (or in a different file entirely), so calls before that point can still be checked. Fortran only needs this for genuinely external procedures — internal contains procedures are automatically visible with no interface block required, since the compiler processes the whole program together.Generic Interfaces vs. No Overloading At All
module generics
implicit none
interface double_value
module procedure double_integer
module procedure double_real
end interface double_value
contains
function double_integer(n) result(doubled)
integer, intent(in) :: n
integer :: doubled
doubled = n * 2
end function double_integer
function double_real(n) result(doubled)
real, intent(in) :: n
real :: doubled
doubled = n * 2.0
end function double_real
end module generics
program generic_demo
use generics
implicit none
print *, double_value(5)
print *, double_value(5.0)
end program generic_demo #include <stdio.h>
int doubleInteger(int n) {
return n * 2;
}
double doubleReal(double n) {
return n * 2.0;
}
int main(void) {
/* C has NO function overloading at all — two functions can
never share a name, so each type needs its own distinct
function name entirely (doubleInteger vs. doubleReal). */
printf("%d\n", doubleInteger(5));
printf("%f\n", doubleReal(5.0));
return 0;
} Fortran can overload a single generic name across multiple type-specific procedures with a generic
interface block — calling double_value with an integer or a real argument resolves to the matching specific procedure automatically. Standard C has no function overloading mechanism whatsoever: two functions can never share a name no matter how different their parameter types are, so each type combination needs its own distinctly-named function, as shown here. C is genuinely more restrictive than Fortran on this point.RECURSIVE Keyword vs. No Special Marking Needed
program recursive_demo
implicit none
print *, "5! =", factorial(5)
contains
recursive function factorial(n) result(product_value)
integer, intent(in) :: n
integer :: product_value
if (n <= 1) then
product_value = 1
else
product_value = n * factorial(n - 1)
end if
end function factorial
end program recursive_demo #include <stdio.h>
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
int main(void) {
printf("5! = %d\n", factorial(5));
return 0;
} Fortran requires the explicit
recursive keyword before a function or subroutine may call itself — an important safety default, since an ordinary Fortran subprogram is not guaranteed reentrant otherwise. C has no such requirement or keyword at all: every C function may call itself freely, with no special declaration needed.Structs
Derived Type vs. struct
program derived_type_demo
implicit none
type :: point
real :: x
real :: y
end type point
type(point) :: origin
origin = point(1.0, 2.0)
print *, origin%x, origin%y
end program derived_type_demo #include <stdio.h>
typedef struct {
double x;
double y;
} Point;
int main(void) {
Point origin = {1.0, 2.0};
printf("%f %f\n", origin.x, origin.y);
return 0;
} A Fortran
type and a C struct both group named, typed fields into a single value, accessed with a member operator — % in Fortran, . in C. C's typedef struct { ... } Name idiom lets the type be referred to as Point rather than the more verbose struct Point everywhere afterward.Array of Derived Types vs. Array of Structs
program struct_array_demo
implicit none
type :: point
real :: x, y
end type point
type(point) :: points(2)
points(1) = point(1.0, 2.0)
points(2) = point(3.0, 4.0)
print *, points(1)%x, points(2)%y
end program struct_array_demo #include <stdio.h>
typedef struct {
double x, y;
} Point;
int main(void) {
Point points[2] = {{1.0, 2.0}, {3.0, 4.0}};
printf("%f %f\n", points[0].x, points[1].y);
return 0;
} Both languages combine arrays and structs the same way: an array whose element type is a derived type/struct, accessed by combining the subscript and the member operator —
points(1)%x versus points[0].x, differing only by the 1-based/0-based index shift already familiar from ordinary arrays.Data-Only by Default (Both Agree)
program data_only_demo
implicit none
type :: point
real :: x, y
end type point
type(point) :: origin
origin = point(3.0, 4.0)
print *, distance_from_origin(origin)
contains
function distance_from_origin(p) result(distance_value)
! A free function operating on the type, NOT a method
! bound to it — this is the plain, data-only default in
! both languages.
type(point), intent(in) :: p
real :: distance_value
distance_value = sqrt(p%x**2 + p%y**2)
end function distance_from_origin
end program data_only_demo #include <stdio.h>
#include <math.h>
typedef struct {
double x, y;
} Point;
double distanceFromOrigin(Point p) {
return sqrt(p.x * p.x + p.y * p.y);
}
int main(void) {
Point origin = {3.0, 4.0};
printf("%f\n", distanceFromOrigin(origin));
return 0;
} Both languages agree: a plain Fortran
type and a plain C struct hold only data by default, with no methods bound to them at all — behavior always lives in ordinary free functions that take the value as a parameter, as shown here. (Later Fortran standards did add optional type-bound procedures for a more object-oriented style, but the plain data-only struct/type shown here remains each language's simplest and most common default.)Memory & I/O
⚠ ALLOCATE/DEALLOCATE vs. malloc()/free()
program allocatable_demo
implicit none
integer, allocatable :: items(:)
integer :: item_count, i
item_count = 3
allocate(items(item_count))
items = [100, 200, 300]
do i = 1, item_count
print *, items(i)
end do
deallocate(items)
end program allocatable_demo #include <stdio.h>
#include <stdlib.h>
int main(void) {
int itemCount = 3;
int *items = malloc(itemCount * sizeof(int));
if (items == NULL) {
return 1;
}
items[0] = 100;
items[1] = 200;
items[2] = 300;
for (int i = 0; i < itemCount; i++) {
printf("%d\n", items[i]);
}
free(items);
return 0;
} Fortran's
allocate()/deallocate() and C's malloc()/free() both manage memory manually with no garbage collector in either language — a real point of agreement, unlike R or Julia. The details differ sharply: Fortran's allocate(items(3)) already knows it is allocating 3 integers; C's malloc() only knows a raw byte count, so the programmer must compute itemCount * sizeof(int) explicitly, and malloc() can return NULL on failure (which the caller must check) where Fortran's allocate instead has an optional stat= argument for the same purpose.PRINT * vs. printf()
program print_demo
implicit none
integer :: count
real :: price
count = 42
price = 9.99
print *, "Count:", count
print *, "Price:", price
end program print_demo #include <stdio.h>
int main(void) {
int count = 42;
double price = 9.99;
printf("Count: %d\n", count);
printf("Price: %f\n", price);
return 0;
} print *, ... in Fortran infers default formatting for each value automatically. C's printf() requires an explicit format specifier per value (%d for an integer, %f for a floating-point value) — more verbose, but giving precise control over the output that Fortran's write(*, '(format)') descriptors provide separately from the simpler print *, form.KIND/STORAGE_SIZE vs. sizeof
program storage_size_demo
implicit none
integer :: whole_number
real :: fractional_number
print *, "Integer bits:", storage_size(whole_number)
print *, "Real bits:", storage_size(fractional_number)
end program storage_size_demo #include <stdio.h>
int main(void) {
int whole_number;
double fractionalNumber;
printf("Integer bytes: %zu\n", sizeof(whole_number));
printf("Double bytes: %zu\n", sizeof(fractionalNumber));
return 0;
} Fortran's
storage_size() intrinsic reports a variable's size in bits. C's sizeof operator reports a variable's size in bytes — both answer "how much memory does this take," just in different units, and sizeof is a compile-time operator in C (evaluated at compile time whenever possible) rather than an ordinary function call.OPEN/READ/WRITE/CLOSE vs. fopen()/fprintf()/fclose()
program file_io_demo
implicit none
integer :: file_unit
open(unit=10, file="output.txt", status="replace", action="write")
write(10, *) "Alice,30"
close(10)
end program file_io_demo #include <stdio.h>
int main(void) {
FILE *file = fopen("output.txt", "w");
if (file == NULL) {
return 1;
}
fprintf(file, "Alice,30\n");
fclose(file);
return 0;
} Fortran identifies an open file by an arbitrary integer
unit number. C identifies one by a FILE * pointer returned from fopen(), which must be checked against NULL to detect a failed open (Fortran instead uses an optional iostat= argument for the same purpose). Both require a real filesystem and cannot run in the browser sandbox.⚠ Gotchas for Fortran Programmers
⚠ Undefined Behavior Is Far More Pervasive in C
program bounds_violation_demo
implicit none
integer :: numbers(3)
numbers = [1, 2, 3]
! Reading past the end without -fcheck=bounds is undefined
! behavior in Fortran too — but the RANGE of things that count
! as undefined behavior in C is dramatically larger.
print *, numbers(1)
end program bounds_violation_demo #include <stdio.h>
#include <limits.h>
int main(void) {
/* Signed integer overflow, out-of-bounds access, using an
uninitialized value, dereferencing a NULL pointer, a data
race, and dozens of other situations are ALL undefined
behavior in C — the compiler is permitted to assume none
of them ever happen and optimize accordingly, sometimes
deleting the very check meant to guard against them. */
int value = INT_MAX;
printf("%d\n", value);
return 0;
} Both languages have some notion of undefined behavior, but C's is dramatically broader and more consequential in practice: signed integer overflow, out-of-bounds array access, using an uninitialized variable, dereferencing a
NULL pointer, and violating strict aliasing rules are all undefined behavior in C, and modern optimizing compilers are permitted to assume none of them ever happen — sometimes eliminating a programmer's explicit safety check entirely because the compiler proves the "impossible" branch it guards against is technically undefined behavior. Fortran's undefined-behavior surface (mainly out-of-bounds access without -fcheck=bounds, and a handful of aliasing rules around intent) is considerably narrower.Fixed-Width Integer Overflow (Both Agree, Mostly)
program overflow_demo
implicit none
integer(kind=1) :: tiny_value
tiny_value = 127
tiny_value = tiny_value + 1
print *, tiny_value
end program overflow_demo #include <stdio.h>
#include <stdint.h>
int main(void) {
int8_t tinyValue = 127;
tinyValue = (int8_t)(tinyValue + 1);
printf("%d\n", tinyValue);
return 0;
} Both an
integer(kind=1) in Fortran and an int8_t in C wrap silently on overflow with no error. One asymmetry worth knowing: unlike Fortran, C's signed integer overflow is technically undefined behavior per the language standard (even though it wraps predictably on virtually every real machine and compiler) — the explicit cast shown here keeps the arithmetic in int8_t to sidestep any ambiguity, since plain C arithmetic on small integer types is first promoted to int before the operation happens.No Garbage Collector in Either Language (Agreement)
program manual_memory_demo
implicit none
integer, allocatable :: numbers(:)
allocate(numbers(3))
numbers = [1, 2, 3]
print *, numbers
deallocate(numbers) ! forgetting this leaks memory — nothing frees it automatically
end program manual_memory_demo #include <stdio.h>
#include <stdlib.h>
int main(void) {
int *numbers = malloc(3 * sizeof(int));
numbers[0] = 1; numbers[1] = 2; numbers[2] = 3;
printf("%d %d %d\n", numbers[0], numbers[1], numbers[2]);
free(numbers); /* forgetting this leaks memory here too */
return 0;
} Both languages agree, unlike R or Julia: nothing frees dynamically allocated memory automatically in either Fortran or C. Forgetting
deallocate() in Fortran or free() in C both leak memory for the remaining lifetime of the process, with no garbage collector ever stepping in to clean up an orphaned allocation in either language.MODULE Declarations vs. Header File Duplication
program module_demo
use iso_fortran_env, only: real64
implicit none
real(kind=real64) :: value
value = 3.14159265358979_real64
! Fortran's compiler reads the actual MODULE source, so the
! interface a caller sees is always automatically in sync with
! the real implementation — there is no separate copy to drift.
print *, value
end program module_demo #include <stdio.h>
/* A C header (.h file) is a MANUALLY MAINTAINED, separate copy
of every function's signature. If mathutils.c changes a
function's parameter types but mathutils.h isn't updated to
match, the two silently drift out of sync — sometimes
compiling without error and producing subtly wrong results. */
int main(void) {
double value = 3.14159265358979;
printf("%f\n", value);
return 0;
} Fortran's
use module_name reads compiler-generated module information directly from the module's own compiled form (a .mod file), so the interface a caller sees can never drift from the real implementation — the compiler guarantees they match. C headers are just manually written, separate text files declaring what the corresponding .c file is supposed to contain; nothing enforces that a header's declared function signatures actually match the real definitions elsewhere, and a header that falls out of sync with its implementation is a real, if increasingly rare with modern tooling, category of C bug that Fortran's module system makes structurally impossible.