PONYλM2Modula-2

C.CodeCompared.To/Fortran

An interactive executable cheatsheet comparing C and Fortran

C17 (GCC) Fortran 2018 (GCC 16.2)
Program Structure & Output
Hello, World
The shape is familiar: a named entry point with a beginning and an end. What is missing is the include — the printing intrinsic is part of the language, not a library you declare.
#include <stdio.h> int main(void) { printf("Hello, World!\n"); return 0; }
program hello implicit none print '(a)', "Hello, World!" end program hello
The '(a)' is a format specification meaning "one character string", and it is what keeps the output free of the leading blank that print *, would add. The program name after program and end program is documentation — it does not have to be main and nothing links against it. Falling off the end returns a zero exit status; stop 3 is how you return something else.
implicit none: The Line You Must Not Omit
This is the first thing to know about Fortran. Without implicit none, a name that was never declared is silently given a type based on its first letter — i through n are integers and everything else is real — so a typo becomes a new variable rather than an error.
#include <stdio.h> int main(void) { int counter = 0; counter = counter + 1; /* countr = 5; does not compile: undeclared identifier. Every name must be declared, always. */ printf("counter is %d\n", counter); return 0; }
program declarations implicit none ! without this line, a typo compiles integer :: counter counter = 0 counter = counter + 1 ! With implicit none, "countr = 5" is an error. ! Without it, countr would be a brand new integer. print '(a, i0)', "counter is ", counter end program declarations
The implicit-typing rule is why i, j, k, m and n became the conventional loop-counter names — they were the letters that defaulted to integer. It is also why implicit none is the first line of every modern Fortran program and of every example on this page: it turns the language’s oldest misfeature off. There is no way to enable it globally; you write it in every program unit and every module.
Formatted Output: Edit Descriptors Instead of Conversions
A printf format string interleaves literal text and conversions. A Fortran format is a comma-separated list of edit descriptors, and the values come as a separate list — so the format describes the shape of the line and nothing else.
#include <stdio.h> int main(void) { const char *name = "Ada"; int count = 3; double average = 2.5; printf("%s has %d items averaging %.2f\n", name, count, average); printf("%10s|%10s|\n", "left", "right"); printf("%05d %9.3e\n", 42, 1234.5); return 0; }
program formatting implicit none character(len=*), parameter :: name = "Ada" integer, parameter :: count = 3 real, parameter :: average = 2.5 print '(a, a, i0, a, f0.2)', name, " has ", count, & " items averaging ", average print '(a10, "|", a10, "|")', "left", "right" print '(i5.5, 1x, es9.3e2)', 42, 1234.5 end program formatting
The descriptors read straightforwardly once you know the letters: a is character, i integer, f fixed-point real, es scientific, 1x one blank. Widths come after the letter, so i5 is a five-wide integer and i0 means "exactly as wide as it needs to be" — the closest thing to %d. Two things have no counterpart: the a descriptor always right-justifies, so there is no %-10s to be had — you pad with trim and blanks instead — and the exponent letter comes out capital, so 1.234E+03 where C prints 1.234e+03. Note also the & at the end of the continued line: Fortran statements end at the newline unless you say otherwise.
Command-Line Arguments Without argc and argv
There is no argument list on the entry point. Two intrinsics ask the runtime instead, which means every procedure can reach them, not just main.
#include <stdio.h> int main(int argc, char *argv[]) { printf("argument count: %d\n", argc - 1); for (int index = 1; index < argc; index++) { printf(" %d: %s\n", index, argv[index]); } return 0; }
program arguments implicit none integer :: count, index, length character(len=256) :: value count = command_argument_count() print '(a, i0)', "argument count: ", count do index = 1, count call get_command_argument(index, value, length) print '(a, i0, a, a)', " ", index, ": ", value(1:length) end do end program arguments
The count excludes the program name, which argc includes — so the two columns print the same number only because the C one subtracts one. get_command_argument writes into a buffer you sized and reports the true length separately, which is the same discipline as snprintf; asking for argument zero gives the program name.
Types, Kinds, and No Unsigned
Kinds Instead of short, long and long long
Fortran has one integer type and one real type, each parameterized by a kind number. The named kinds in iso_fortran_env spell the width directly, which is the counterpart of <stdint.h> rather than of short and long.
#include <stdio.h> #include <stdint.h> int main(void) { int16_t small = 300; int32_t medium = 70000; int64_t large = 5000000000; float single = 1.0f / 3.0f; double doubled = 1.0 / 3.0; printf("%d %d %lld\n", small, medium, (long long)large); printf("%zu %zu %zu\n", sizeof small, sizeof medium, sizeof large); printf("%.9f\n%.17f\n", (double)single, doubled); return 0; }
program kinds use, intrinsic :: iso_fortran_env, only: int16, int32, int64, real32, real64 implicit none integer(int16) :: small = 300_int16 integer(int32) :: medium = 70000_int32 integer(int64) :: large = 5000000000_int64 real(real32) :: single = 1.0_real32 / 3.0_real32 real(real64) :: doubled = 1.0_real64 / 3.0_real64 print '(i0, 1x, i0, 1x, i0)', small, medium, large print '(i0, 1x, i0, 1x, i0)', & storage_size(small) / 8, storage_size(medium) / 8, storage_size(large) / 8 print '(f11.9)', single print '(f19.17)', doubled end program kinds
The suffix on a literal — 5000000000_int64 — is the counterpart of C’s LL, and it matters for the same reason: without it the literal is default kind and may not fit. storage_size reports bits rather than bytes, which is why the second line divides by eight; there is no sizeof, because a Fortran program is not expected to know how anything is laid out.
There Is No Unsigned Type
Every Fortran integer is signed. The whole family of signed/unsigned conversion surprises you know from C simply does not exist — and neither does the ability to use the top bit as magnitude.
#include <stdio.h> int main(void) { unsigned int large = 4000000000u; printf("as unsigned %u\n", large); printf("as signed %d\n", (int)large); /* The classic trap: the signed operand converts, so this is FALSE. */ int negative = -1; unsigned int zero = 0u; printf("-1 < 0u ? %d\n", negative < zero); return 0; }
program signedness use, intrinsic :: iso_fortran_env, only: int32, int64 implicit none integer(int64) :: large = 4000000000_int64 integer(int32) :: negative = -1 print '(a, i0)', "as int64 ", large print '(a, i0)', "as int32 ", int(large - 4294967296_int64, int32) print '(a, l1)', "-1 < 0 ? ", negative < 0 print '(a, i0)', "huge(int32) ", huge(0_int32) end program signedness
The C column’s last line prints 0, which is correct and almost never what the author meant. Fortran has no way to write that bug, at the cost of needing a wider type whenever a value would only fit unsigned. Note also that overflow is not defined here either — it is simply not specified, exactly as in C, and gfortran will happily fold it away. huge(x) is the largest value of x’s kind, which is INT_MAX spelled as a question about a variable.
A logical Is Not a Number
C’s conditions are integers and zero means false, which is why if (count) works. Fortran has a distinct logical type with its own operators, and no implicit conversion in either direction.
#include <stdio.h> int main(void) { int count = 0; if (!count) { printf("count is zero\n"); } /* A comparison yields an int, so this is arithmetic: */ int flag = (count == 0); printf("flag as int: %d\n", flag + 1); return 0; }
program logicals implicit none integer :: count = 0 logical :: flag if (count == 0) then print '(a)', "count is zero" end if flag = (count == 0) print '(a, l1)', "flag: ", flag ! flag + 1 is a type error; there is no arithmetic on logical. print '(a, i0)', "as an integer: ", merge(1, 0, flag) end program logicals
The operators are different too: .and., .or., .not., .eqv. and .neqv. take logicals, while <, == and friends produce them. merge(a, b, mask) is the ternary operator, and it works element-wise on arrays as well — which is a capability C’s ?: has no equivalent of.
Integer Division Truncates, and the Types Decide
This is the one place where the two languages agree almost exactly: the operand types choose integer or floating division, and integer division truncates toward zero.
#include <stdio.h> int main(void) { printf("7 / 2 = %d\n", 7 / 2); printf("7.0 / 2.0 = %.1f\n", 7.0 / 2.0); printf("7 / 2.0 = %.1f\n", 7 / 2.0); printf("-17 / 5 = %d\n", -17 / 5); printf("-17 %% 5 = %d\n", -17 % 5); return 0; }
program division implicit none print '(a, i0)', "7 / 2 = ", 7 / 2 print '(a, f0.1)', "7.0 / 2.0 = ", 7.0 / 2.0 print '(a, f0.1)', "7 / 2.0 = ", 7 / 2.0 print '(a, i0)', "-17 / 5 = ", -17 / 5 print '(a, i0)', "-17 mod 5 = ", mod(-17, 5) print '(a, i0)', "-17 modulo 5= ", modulo(-17, 5) end program division
The one addition is the pair mod and modulo: mod takes the sign of the numerator and matches C’s % exactly, while modulo takes the sign of the divisor and gives 3 for modulo(-17, 5). Having both spelled separately is a small kindness — in C you write the adjustment by hand every time you want the second one.
Arrays Start at 1 and Store by Column
Subscripts Start at 1 — and the Lower Bound Is Yours
The default lower bound is 1, not 0. More usefully, the bound is part of the declaration: an array can run from −5 to 5, or from 1900 to 2000, and the subscripts mean what they say.
#include <stdio.h> int main(void) { int numbers[5] = { 10, 20, 30, 40, 50 }; printf("first %d, last %d\n", numbers[0], numbers[4]); /* An array indexed by year means subtracting the base by hand. */ int population[3]; /* 1990, 1991, 1992 */ population[1990 - 1990] = 100; population[1991 - 1990] = 110; population[1992 - 1990] = 120; printf("1991: %d\n", population[1991 - 1990]); return 0; }
program bounds implicit none integer :: numbers(5) = [10, 20, 30, 40, 50] integer :: population(1990:1992) print '(a, i0, a, i0)', "first ", numbers(1), ", last ", numbers(5) population(1990) = 100 population(1991) = 110 population(1992) = 120 print '(a, i0)', "1991: ", population(1991) print '(a, i0, a, i0)', "bounds ", lbound(population, 1), & " to ", ubound(population, 1) end program bounds
Round brackets are used for both subscripting and calling, which takes some getting used to — numbers(1) and sin(x) look identical, and the compiler tells them apart from the declarations. The arbitrary lower bound removes a whole category of off-by-one arithmetic: no subtracting a base, and lbound/ubound report what the array actually is.
Two Dimensions Are Stored by Column
This is the difference that causes the most real bugs when the two languages meet. C stores a two-dimensional array row by row, so the last subscript varies fastest in memory. Fortran stores it column by column, so the first subscript does.
#include <stdio.h> int main(void) { int grid[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; /* Written the way the layout wants: last subscript innermost. */ for (int row = 0; row < 2; row++) { for (int column = 0; column < 3; column++) { printf("%d ", grid[row][column]); } } printf("\n"); /* The same six ints, read straight out of memory in order: */ const int *flat = &grid[0][0]; for (int index = 0; index < 6; index++) { printf("%d ", flat[index]); } printf("\n"); return 0; }
program storage_order implicit none integer :: grid(2, 3) integer :: row, column grid = reshape([1, 4, 2, 5, 3, 6], [2, 3]) ! Written the way the layout wants: FIRST subscript innermost. do column = 1, 3 do row = 1, 2 write(*, '(i0, 1x)', advance="no") grid(row, column) end do end do print * ! The same six integers, read straight out of memory in order: print '(6(i0, 1x))', reshape(grid, [6]) end program storage_order
Both columns hold the same matrix — rows 1 2 3 and 4 5 6 — and both walk it in the order their own memory layout prefers, and they print different sequences: 1 2 3 4 5 6 against 1 4 2 5 3 6. Each column’s two lines agree with each other, which is the proof that each really is reading memory in order. The practical consequence is the loop nesting: the innermost loop should vary the last subscript in C and the first in Fortran, and getting it backwards walks memory with a stride instead of sequentially — on a large matrix that is the difference between running at cache speed and not.
An Array Argument Knows Its Own Shape
A C array decays to a pointer the moment it is passed, which is why every function that takes one also takes a length. A Fortran assumed-shape dummy argument carries the bounds with it, so the procedure can ask.
#include <stdio.h> static int sum_of(const int *values, size_t count) { int total = 0; for (size_t index = 0; index < count; index++) { total += values[index]; } return total; } int main(void) { int numbers[] = { 3, 1, 4, 1, 5 }; size_t count = sizeof numbers / sizeof numbers[0]; printf("count %zu, sum %d\n", count, sum_of(numbers, count)); return 0; }
program shapes implicit none integer :: numbers(5) = [3, 1, 4, 1, 5] print '(a, i0, a, i0)', "count ", size(numbers), ", sum ", sum_of(numbers) contains integer function sum_of(values) integer, intent(in) :: values(:) ! assumed shape: bounds come along integer :: index sum_of = 0 do index = 1, size(values) sum_of = sum_of + values(index) end do end function sum_of end program shapes
The colon in values(:) is what makes it assumed-shape, and it requires an explicit interface — which is why the procedure lives after contains inside the program, or in a module. The older style, values(count) with the length passed separately, is exactly the C convention and still works; it is what you will see in code written before 1990.
The Array Is a First-Class Value
Arithmetic on Whole Arrays, With No Loop
This is the feature Fortran exists for. An operator applied to two arrays of the same shape works element by element and produces an array, so the loop is not written, not indexed, and not able to go wrong.
#include <stdio.h> int main(void) { double first[4] = { 1.0, 2.0, 3.0, 4.0 }; double second[4] = { 10.0, 20.0, 30.0, 40.0 }; double result[4]; for (int index = 0; index < 4; index++) { result[index] = first[index] + second[index] * 2.0; } for (int index = 0; index < 4; index++) { printf("%.1f ", result[index]); } printf("\n"); return 0; }
program whole_array implicit none real :: first(4) = [1.0, 2.0, 3.0, 4.0] real :: second(4) = [10.0, 20.0, 30.0, 40.0] real :: result(4) result = first + second * 2.0 print '(4(f0.1, 1x))', result print '(a, f0.1)', "total ", sum(result) print '(4(f0.1, 1x))', sqrt(first) ! intrinsics are elemental too end program whole_array
Every mathematical intrinsic is elemental: sqrt of an array is an array of square roots, with no loop and no map. Because the compiler knows the whole operation, it can vectorize it, fuse the expression, and skip the temporary — which is a large part of why numerical code is still written in this language. The one rule is shape conformance: adding a 4-element array to a 5-element one is a compile error, not a buffer overrun.
Sections and Strides Instead of Pointer Arithmetic
A slice of a C array is a pointer plus a count, and a strided slice is a loop. Fortran has subscript-triplet notation — start, end, stride — and the result is an array that can be assigned, passed, or operated on.
#include <stdio.h> int main(void) { int numbers[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; /* Elements 2..5 */ for (int index = 2; index <= 5; index++) { printf("%d ", numbers[index]); } printf("\n"); /* Every second element */ for (int index = 0; index < 10; index += 2) { printf("%d ", numbers[index]); } printf("\n"); /* Reversed */ for (int index = 9; index >= 0; index--) { printf("%d ", numbers[index]); } printf("\n"); return 0; }
program sections implicit none integer :: numbers(10) = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print '(4(i0, 1x))', numbers(3:6) ! elements 3..6 print '(5(i0, 1x))', numbers(1:10:2) ! every second print '(10(i0, 1x))', numbers(10:1:-1) ! reversed numbers(1:3) = 0 ! assign to a whole section print '(10(i0, 1x))', numbers end program sections
A section is a first-class value: it can be passed to a procedure, assigned to, or used in an expression, and the compiler either passes a descriptor or makes a temporary copy as needed. That last assignment — setting three elements to one scalar — is broadcasting, and it is what C spells as a for loop or a memset that only works for zero.
The Reductions Are Built In
Summing, finding a maximum, locating it, and taking a dot product are four loops in C. Each is one intrinsic here, and each accepts an optional mask so "the sum of the positive elements" is still one call.
#include <stdio.h> int main(void) { int numbers[8] = { 3, 1, 4, 1, 5, 9, 2, 6 }; int total = 0, largest = numbers[0], position = 0, evenTotal = 0; for (int index = 0; index < 8; index++) { total += numbers[index]; if (numbers[index] > largest) { largest = numbers[index]; position = index; } if (numbers[index] % 2 == 0) { evenTotal += numbers[index]; } } printf("sum %d, max %d at %d, even sum %d\n", total, largest, position + 1, evenTotal); return 0; }
program reductions implicit none integer :: numbers(8) = [3, 1, 4, 1, 5, 9, 2, 6] print '(a, i0, a, i0, a, i0, a, i0)', & "sum ", sum(numbers), & ", max ", maxval(numbers), & " at ", maxloc(numbers, 1), & ", even sum ", sum(numbers, mask=mod(numbers, 2) == 0) print '(a, i0)', "dot product ", dot_product(numbers, numbers) print '(a, l1)', "any above 8 ", any(numbers > 8) print '(a, l1)', "all positive ", all(numbers > 0) end program reductions
The mask= argument is the part with no concise C equivalent: it takes a logical array — here produced by comparing the whole array against a scalar — and reduces only where it is true. maxloc returns a position, which is 1-based like every other subscript, so the C column has to add one to agree. count, minloc, product, any and all round out the family.
Matrix Multiply Is an Operator’s Worth of Code
The triple loop is the canonical C exercise. It is an intrinsic here, and so is transposition — both of which the compiler is free to hand to an optimized library.
#include <stdio.h> int main(void) { double left[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; double right[3][2] = { { 7, 8 }, { 9, 10 }, { 11, 12 } }; double product[2][2] = { { 0 } }; for (int row = 0; row < 2; row++) { for (int column = 0; column < 2; column++) { for (int inner = 0; inner < 3; inner++) { product[row][column] += left[row][inner] * right[inner][column]; } } } for (int row = 0; row < 2; row++) { printf("%6.1f %6.1f\n", product[row][0], product[row][1]); } return 0; }
program matrices implicit none real :: left(2, 3), right(3, 2), product(2, 2) integer :: row left = reshape([1., 4., 2., 5., 3., 6.], [2, 3]) right = reshape([7., 9., 11., 8., 10., 12.], [3, 2]) product = matmul(left, right) do row = 1, 2 print '(f6.1, 1x, f6.1)', product(row, 1), product(row, 2) end do end program matrices
The reshape literals are written in column order, which is why they look shuffled next to the C initializers holding the same matrices — that is the storage-order difference from two rows back, showing up in the source. transpose, dot_product and matmul are the three that matter; anything larger goes to BLAS, and the compiler will often call it for you.
A character Has a Fixed Length
A character Variable Has a Length, and Pads With Blanks
This is neither a char * nor a counted string. A character(len=10) is exactly ten characters, always; assigning a shorter value pads it with blanks and assigning a longer one truncates, both silently.
#include <stdio.h> #include <string.h> int main(void) { char buffer[11]; /* 10 characters plus a terminator */ strncpy(buffer, "Ada", sizeof buffer - 1); buffer[sizeof buffer - 1] = '\0'; printf("[%s] length %zu\n", buffer, strlen(buffer)); strncpy(buffer, "a much longer name", sizeof buffer - 1); buffer[sizeof buffer - 1] = '\0'; printf("[%s] length %zu\n", buffer, strlen(buffer)); return 0; }
program fixed_length implicit none character(len=10) :: buffer buffer = "Ada" print '(a, a, a, i0, a, i0)', "[", buffer, "] len ", len(buffer), & " len_trim ", len_trim(buffer) buffer = "a much longer name" ! silently truncated to 10 print '(a, a, a, i0)', "[", buffer, "] len ", len(buffer) end program fixed_length
The two lengths are the point: len is the declared size and never changes, while len_trim is the length ignoring trailing blanks — the closest thing to strlen. That padding is why trim() appears in almost every line of real Fortran that prints a string, and why comparing two character values of different lengths pads the shorter one rather than reporting a difference.
Concatenation, Substrings, and Searching
The operations are all here and none of them takes a destination buffer. Concatenation is an operator, a substring is subscript-triplet notation on a character variable, and index is strstr returning a position.
#include <stdio.h> #include <string.h> int main(void) { const char *text = "the quick brown fox"; printf("length %zu\n", strlen(text)); printf("find %ld\n", (long)(strstr(text, "quick") - text)); char piece[6]; strncpy(piece, text + 4, 5); piece[5] = '\0'; printf("substr %s\n", piece); char joined[64]; snprintf(joined, sizeof joined, "%s%s", "hello, ", "world"); printf("joined %s\n", joined); return 0; }
program string_operations implicit none character(len=*), parameter :: text = "the quick brown fox" print '(a, i0)', "length ", len(text) print '(a, i0)', "find ", index(text, "quick") print '(a, a)', "substr ", text(5:9) print '(a, a)', "joined ", "hello, " // "world" print '(a, a)', "upper ", to_upper(text) contains function to_upper(input) result(output) character(len=*), intent(in) :: input character(len=len(input)) :: output integer :: position, code do position = 1, len(input) code = iachar(input(position:position)) if (code >= iachar("a") .and. code <= iachar("z")) then output(position:position) = achar(code - 32) else output(position:position) = input(position:position) end if end do end function to_upper end program string_operations
index returns a 1-based position and 0 when the substring is absent, which is cleaner than strstr’s pointer-or-NULL — and it is why the two columns print 4 and 5 for the same match. Note that there is no uppercase intrinsic — the helper above is the standard way, and it is the same loop you would write in C, because iachar and achar are exactly the cast to and from a character code. The character(len=len(input)) declaration on the result is an automatic length: the returned string is sized from an argument.
Strings That Size Themselves
A character(len=:), allocatable variable takes the length of whatever is assigned to it. That is the closest Fortran comes to a growable string, and it removes the "how big should the buffer be" question from the places that most need it.
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { /* A result whose length is not known up front means malloc. */ const char *first = "hello"; const char *second = ", world"; size_t length = strlen(first) + strlen(second); char *joined = malloc(length + 1); if (joined == NULL) return 1; strcpy(joined, first); strcat(joined, second); printf("[%s] length %zu\n", joined, strlen(joined)); free(joined); return 0; }
program deferred_length implicit none character(len=:), allocatable :: joined joined = "hello" // ", world" print '(a, a, a, i0)', "[", joined, "] length ", len(joined) joined = "shorter" ! reallocated to fit print '(a, a, a, i0)', "[", joined, "] length ", len(joined) end program deferred_length
No malloc, no length arithmetic, no free — the assignment allocates, reallocates on the next assignment, and deallocates at end of scope. This is the modern replacement for the fixed character(len=256) buffer that older code passes everywhere, and it is worth reaching for whenever the length is not genuinely fixed.
Arguments Arrive by Reference
Arguments Arrive by Reference, So There Is No &
C passes everything by value, so writing back to a caller’s variable means passing its address and dereferencing. Fortran passes by reference by default: assign to the dummy argument and the caller’s variable changes.
#include <stdio.h> static void swap(int *left, int *right) { int held = *left; *left = *right; *right = held; } int main(void) { int first = 1, second = 2; swap(&first, &second); printf("%d %d\n", first, second); return 0; }
program by_reference implicit none integer :: first = 1, second = 2 call swap(first, second) print '(i0, 1x, i0)', first, second contains subroutine swap(left, right) integer, intent(inout) :: left, right integer :: held held = left left = right right = held end subroutine swap end program by_reference
There is no & at the call site and no * in the body, which is convenient and also means you cannot tell from a call whether an argument will be modified — that is what intent, in the next row, is for. The historical consequence to be aware of: passing a literal to a procedure that modifies it used to corrupt the constant, which is why compilers now pass a copy for expressions and literals.
intent Instead of const
C’s const on a parameter says "I will not write through this pointer". Fortran’s intent says more: in means read-only, out means the value on entry is undefined and you must set it, and inout means both.
#include <stdio.h> /* const documents intent for the pointee — and only for the pointee. */ static int sum_of(const int *values, size_t count) { int total = 0; for (size_t index = 0; index < count; index++) { total += values[index]; } return total; } static void fill(int *values, size_t count) { /* Nothing says the previous contents are meaningless. */ for (size_t index = 0; index < count; index++) { values[index] = (int)index; } } int main(void) { int numbers[4]; fill(numbers, 4); printf("%d\n", sum_of(numbers, 4)); return 0; }
program intents implicit none integer :: numbers(4) call fill(numbers) print '(i0)', sum_of(numbers) contains integer function sum_of(values) integer, intent(in) :: values(:) ! assigning to values is an error sum_of = sum(values) end function sum_of subroutine fill(values) integer, intent(out) :: values(:) ! contents on entry are undefined integer :: index do index = 1, size(values) values(index) = index - 1 end do end subroutine fill end program intents
intent(out) is the one with no C counterpart, and it is not just documentation: the compiler may skip copying the argument in, and it will warn if a path through the procedure leaves it unset. Combined with pure, which forbids side effects entirely, these are the declarations that let a Fortran compiler reorder and parallelize loops that a C compiler must assume might alias.
Two Kinds of Procedure
C has one: a function, which may return void. Fortran distinguishes a function, which produces a value and is used in an expression, from a subroutine, which does something and is invoked with call.
#include <stdio.h> static double area(double width, double height) { return width * height; } static void report(double value) { printf("value %.1f\n", value); } int main(void) { /* Both are called the same way; only the use of the result differs. */ report(area(3.0, 4.0)); /* And a result can be ignored with no warning: */ area(1.0, 2.0); return 0; }
program procedures implicit none call report(area(3.0, 4.0)) ! area(1.0, 2.0) is a syntax error on its own: a function call ! is an expression, and an expression is not a statement. contains real function area(width, height) real, intent(in) :: width, height area = width * height ! assign to the function's own name end function area subroutine report(value) real, intent(in) :: value print '(a, f0.1)', "value ", value end subroutine report end program procedures
The result is delivered by assigning to the function’s own name, which is unusual and worth reading twice; function area(...) result(computed) lets you name it something else, and is the common style. The split matters because a function call cannot be a statement — so the C habit of calling something for its side effect and dropping the result has no spelling, and a procedure that does work is declared as the subroutine it is.
Optional Arguments, and Calling by Name
C’s answer to an optional parameter is a second function or a sentinel value. Fortran marks the argument optional and gives you present() to ask, and any argument can be supplied by name at the call.
#include <stdio.h> static void log_message(const char *text, const char *level) { printf("[%s] %s\n", level, text); } /* The one-argument form is a separate function. */ static void log_simple(const char *text) { log_message(text, "info"); } int main(void) { log_simple("started"); log_message("careful", "warn"); return 0; }
program optional_arguments implicit none call log_message("started") call log_message("careful", "warn") call log_message(level="debug", text="named arguments work too") contains subroutine log_message(text, level) character(len=*), intent(in) :: text character(len=*), intent(in), optional :: level if (present(level)) then print '(a, a, a, a)', "[", level, "] ", text else print '(a, a)', "[info] ", text end if end subroutine log_message end program optional_arguments
A missing optional argument is genuinely absent rather than defaulted, which is why present() exists — there is no way to declare a default value, so the "what to do when it is missing" logic lives in the body. Keyword arguments work for every procedure with an explicit interface, and they are what makes the intrinsic library readable: sum(numbers, mask=...) and reshape(source, shape, pad=...) both rely on them.
elemental: Write It for One, Get It for Arrays
A C function operating on a scalar needs a wrapper loop to apply to an array. An elemental procedure is defined for the scalar case and applies element-wise to arrays of any rank automatically.
#include <stdio.h> static double celsius_to_fahrenheit(double celsius) { return celsius * 9.0 / 5.0 + 32.0; } int main(void) { double readings[4] = { 0.0, 20.0, 37.0, 100.0 }; double converted[4]; for (int index = 0; index < 4; index++) { converted[index] = celsius_to_fahrenheit(readings[index]); } for (int index = 0; index < 4; index++) { printf("%.1f ", converted[index]); } printf("\n"); return 0; }
program elementals implicit none real :: readings(4) = [0.0, 20.0, 37.0, 100.0] print '(4(f0.1, 1x))', celsius_to_fahrenheit(readings) print '(a, f0.1)', "one value: ", celsius_to_fahrenheit(37.0) contains elemental real function celsius_to_fahrenheit(celsius) real, intent(in) :: celsius celsius_to_fahrenheit = celsius * 9.0 / 5.0 + 32.0 end function celsius_to_fahrenheit end program elementals
The same procedure now takes a scalar, a vector, or a three-dimensional array, with no overloading and no template. elemental implies pure — no side effects, no I/O, all arguments intent(in) — which is precisely the contract that lets the compiler evaluate the elements in any order or in parallel. Every mathematical intrinsic in the language is declared this way.
A Procedure Interface Is Checked, Not Trusted
A C prototype is a promise you make in a header; if the definition disagrees, the linker will happily connect them and the program misbehaves. When a Fortran procedure has an explicit interface — from a module or from contains — the compiler checks every call against the real definition.
#include <stdio.h> /* If this prototype and the definition disagree, C's separate compilation model means nothing notices until run time. */ static long factorial(int value); static long factorial(int value) { return value <= 1 ? 1L : value * factorial(value - 1); } int main(void) { printf("%ld\n", factorial(10)); /* factorial(1.5, 2); would be caught here — but only because the prototype is in the same file. */ return 0; }
program interfaces implicit none print '(i0)', factorial(10) ! factorial(1.5) is rejected at compile time: the interface is ! explicit, so the argument type is checked against the definition. contains recursive integer function factorial(value) result(computed) integer, intent(in) :: value if (value <= 1) then computed = 1 else computed = value * factorial(value - 1) end if end function factorial end program interfaces
Recursion needs no keyword since Fortran 2018 — recursive is the default now and the word is optional, having been required before. The interface point is the important one: a procedure in a module is checked at every call site in every file that uses it, which is the checking C’s header files only approximate. A procedure with no interface at all — an external one, declared the old way — is trusted exactly as a C prototype is.
allocatable Instead of malloc
allocatable Instead of malloc and free
An allocatable array is sized at run time and released automatically when it goes out of scope. There is no pointer, nothing to check for null, and nothing to forget — the compiler emits the deallocation.
#include <stdio.h> #include <stdlib.h> int main(void) { int count = 5; int *numbers = malloc((size_t)count * sizeof *numbers); if (numbers == NULL) return 1; for (int index = 0; index < count; index++) { numbers[index] = index * index; } for (int index = 0; index < count; index++) { printf("%d ", numbers[index]); } printf("\n"); free(numbers); /* exactly one, on every path out */ return 0; }
program allocation implicit none integer, allocatable :: numbers(:) integer :: count = 5, index allocate(numbers(count)) do index = 1, count numbers(index) = (index - 1) ** 2 end do print '(5(i0, 1x))', numbers print '(a, l1)', "allocated: ", allocated(numbers) ! No deallocate needed: it happens at end of scope. end program allocation
You can write deallocate explicitly when you want the memory back sooner, and allocated() asks whether it currently holds anything — which is the null check, made into a question about the variable rather than about a pointer. An allocate that fails aborts the program unless you supply stat=, which is the same choice C makes you handle at every call.
An Array Sized by an Argument
C99 added variable-length arrays and C11 made them optional, so portable C uses malloc. Fortran has had automatic arrays since 1990: a local array whose bounds come from the arguments, allocated on entry and released on return.
#include <stdio.h> #include <stdlib.h> static double mean_of(const double *values, size_t count) { /* A scratch buffer of the argument's size needs malloc to be portable — VLAs are optional in C11. */ double *scratch = malloc(count * sizeof *scratch); if (scratch == NULL) return 0.0; double total = 0.0; for (size_t index = 0; index < count; index++) { scratch[index] = values[index] * 2.0; total += scratch[index]; } free(scratch); return total / (double)count; } int main(void) { double readings[4] = { 1.0, 2.0, 3.0, 4.0 }; printf("%.2f\n", mean_of(readings, 4)); return 0; }
program automatic_arrays implicit none real :: readings(4) = [1.0, 2.0, 3.0, 4.0] print '(f0.2)', mean_of(readings) contains real function mean_of(values) real, intent(in) :: values(:) real :: scratch(size(values)) ! automatic: sized on entry scratch = values * 2.0 mean_of = sum(scratch) / size(values) end function mean_of end program automatic_arrays
The scratch array is typically placed on the stack, which makes it fast and also means a very large one can overflow it — the same trade as alloca, with the same advice: use allocatable above a few tens of kilobytes. The whole-array assignment scratch = values * 2.0 is doing the loop as well, which is why the Fortran body is four lines against the C column’s dozen.
A Fortran pointer Is an Alias, Not an Address
The word is the same and the thing is not. A Fortran pointer is associated with a target rather than holding an address you can compute with — there is no arithmetic, no cast, and a variable must be declared a target before anything may point at it.
#include <stdio.h> int main(void) { int numbers[5] = { 1, 2, 3, 4, 5 }; int *walk = numbers; /* Arithmetic on the pointer is the whole idiom. */ walk += 2; printf("third element %d\n", *walk); /* And nothing stops this: */ walk += 10; printf("now pointing somewhere undefined\n"); return 0; }
program pointers implicit none integer, target :: numbers(5) = [1, 2, 3, 4, 5] integer, pointer :: view(:) view => numbers(2:4) ! associated with a SECTION print '(a, 3(i0, 1x))', "view is ", view view(1) = 99 ! writes through to numbers print '(a, 5(i0, 1x))', "numbers ", numbers print '(a, l1)', "associated: ", associated(view) view => null() print '(a, l1)', "after null: ", associated(view) end program pointers
The => operator is pointer assignment and = is ordinary assignment through the pointer, which is a distinction C makes with * and its absence. Because a pointer can be associated with an array section, it carries a stride as well as an address — so view => numbers(1:5:2) is a strided view, which C can only express as an index calculation. Pointers are rare in modern Fortran; allocatable covers most of what C uses pointers for.
Modules Instead of Headers
A Module Instead of a Header and a Source File
There is no textual inclusion. A module is compiled once, producing an interface file the compiler reads when another unit uses it — so there is no header to keep in step, no include guard, and no way for a declaration and a definition to disagree.
/* geometry.h */ #ifndef GEOMETRY_H #define GEOMETRY_H #define PI 3.14159265358979323846 double circle_area(double radius); #endif /* geometry.c */ #include "geometry.h" double circle_area(double radius) { return PI * radius * radius; } /* main.c */ #include <stdio.h> #include "geometry.h" int main(void) { printf("%.4f\n", circle_area(1.0)); return 0; }
module geometry implicit none private public :: circle_area, pi real, parameter :: pi = 3.14159265358979323846 contains real function circle_area(radius) real, intent(in) :: radius circle_area = pi * radius ** 2 end function circle_area end module geometry program main use geometry, only: circle_area implicit none print '(f0.4)', circle_area(1.0) end program main
The private line makes everything hidden by default and public lists the exceptions, which is the opposite of C’s default and the same effect as marking every internal function static. use geometry, only: circle_area imports one name — worth doing, because a bare use brings in everything and two modules exporting the same name collide. Both program units can live in one file, as here, or in separate ones compiled in dependency order.
parameter Instead of #define
A #define is text substituted before the compiler sees it: no type, no scope, and no existence at run time. A parameter is a real named constant with a type, and it can be an array or a derived type as easily as a number.
#include <stdio.h> #define MAXIMUM_RETRIES 3 #define GREETING "hello" /* An array constant needs a static variable, not a macro. */ static const int WEIGHTS[3] = { 1, 2, 3 }; int main(void) { printf("%d %s %d\n", MAXIMUM_RETRIES, GREETING, WEIGHTS[2]); return 0; }
program constants implicit none integer, parameter :: maximum_retries = 3 character(len=*), parameter :: greeting = "hello" integer, parameter :: weights(3) = [1, 2, 3] print '(i0, 1x, a, 1x, i0)', maximum_retries, greeting, weights(3) print '(a, i0)', "sum of weights ", sum(weights) end program constants
The character(len=*) on a parameter means "as long as the value", so the length is inferred rather than counted by hand. Because a parameter is a value rather than text, it can be used where a constant expression is required — including as an array bound — and the compiler folds it away exactly as it would a macro, with none of the double-evaluation hazards macros bring.
Derived Types Instead of struct
A Derived Type Instead of a struct
The same idea with different punctuation: fields are declared between type and end type, and reached with % rather than .. Assignment copies the whole thing, exactly as it does in C.
#include <stdio.h> struct Point { double x; double y; }; int main(void) { struct Point origin = { 0.0, 0.0 }; struct Point corner = { 3.0, 4.0 }; struct Point copy = corner; /* a real copy */ copy.x = 99.0; printf("corner %4.1f %4.1f\n", corner.x, corner.y); printf("copy %4.1f %4.1f\n", copy.x, copy.y); printf("origin %4.1f %4.1f\n", origin.x, origin.y); return 0; }
program derived_types implicit none type :: point real :: x = 0.0 ! default initializer real :: y = 0.0 end type point type(point) :: origin, corner, copy corner = point(3.0, 4.0) ! the structure constructor copy = corner ! a real copy copy%x = 99.0 print '(a, f4.1, 1x, f4.1)', "corner ", corner%x, corner%y print '(a, f4.1, 1x, f4.1)', "copy ", copy%x, copy%y print '(a, f4.1, 1x, f4.1)', "origin ", origin%x, origin%y end program derived_types
The default initializers mean origin is (0.0, 0.0) without being assigned, which C can only manage with an explicit = {0}. point(3.0, 4.0) is the structure constructor, and it accepts keywords too — point(y=4.0, x=3.0). The one thing to know before interop: the standard does not fix the field order in memory unless the type is declared bind(c), which the last section covers.
Procedures Attached to a Type
The C pattern is a struct and a family of functions taking a pointer to it. A type-bound procedure moves the function inside the type declaration and makes the receiver an explicit dummy argument called whatever you like.
#include <stdio.h> struct Rectangle { double width; double height; }; static double rectangle_area(const struct Rectangle *self) { return self->width * self->height; } static void rectangle_scale(struct Rectangle *self, double factor) { self->width *= factor; self->height *= factor; } int main(void) { struct Rectangle shape = { 3.0, 4.0 }; printf("area %.1f\n", rectangle_area(&shape)); rectangle_scale(&shape, 2.0); printf("scaled area %.1f\n", rectangle_area(&shape)); return 0; }
module shapes implicit none private public :: rectangle type :: rectangle real :: width = 0.0, height = 0.0 contains procedure :: area procedure :: scale end type rectangle contains real function area(self) class(rectangle), intent(in) :: self area = self%width * self%height end function area subroutine scale(self, factor) class(rectangle), intent(inout) :: self real, intent(in) :: factor self%width = self%width * factor self%height = self%height * factor end subroutine scale end module shapes program main use shapes, only: rectangle implicit none type(rectangle) :: shape shape = rectangle(3.0, 4.0) print '(a, f0.1)', "area ", shape%area() call shape%scale(2.0) print '(a, f0.1)', "scaled area ", shape%area() end program main
Note class(rectangle) rather than type(rectangle) on the receiver: class means "this type or anything extending it", which is what makes the procedure inheritable and overridable. That is the whole object system — extends for inheritance, deferred for an abstract method — and it is exactly what the C column builds by hand out of a struct of function pointers.
Loops and select case
The do Loop Counts, and the Count Is Fixed on Entry
A C for loop re-evaluates its condition every pass, so changing the bound inside the body changes the loop. A Fortran do loop computes its trip count once, before the first iteration.
#include <stdio.h> int main(void) { int limit = 3; for (int index = 1; index <= limit; index++) { printf("%d ", index); limit = 5; /* the loop notices */ } printf("\n"); for (int index = 10; index >= 1; index -= 3) { printf("%d ", index); } printf("\n"); return 0; }
program loops implicit none integer :: index, limit limit = 3 do index = 1, limit write(*, '(i0, 1x)', advance="no") index limit = 5 ! the loop does NOT notice end do print * do index = 10, 1, -3 write(*, '(i0, 1x)', advance="no") index end do print * index = 0 do while (index < 3) index = index + 1 end do print '(a, i0)', "after do while: ", index end program loops
The two columns print different first lines on purpose: C runs five times because the bound is re-read, Fortran three times because the trip count was fixed at entry. That fixed count is what allows the loop to be parallelized or unrolled without analysis, and it also means modifying the loop variable inside the body is forbidden rather than merely unwise. exit and cycle are break and continue, and both can name a labelled loop to leave a nested one.
select case Does Not Fall Through
C’s switch falls through unless you write break, which is the source of a well-known family of bugs. Fortran’s select case runs exactly one block, and its labels can be ranges and lists.
#include <stdio.h> static const char *describe(int code) { switch (code) { case 1: case 2: return "low"; case 3: case 4: case 5: return "medium"; default: return "high"; } } int main(void) { printf("%s %s %s\n", describe(1), describe(4), describe(9)); /* The classic bug: no break. */ int total = 0; switch (2) { case 2: total += 1; case 3: total += 10; default: total += 100; } printf("fell through to %d\n", total); return 0; }
program cases implicit none print '(a, 1x, a, 1x, a)', trim(describe(1)), trim(describe(4)), & trim(describe(9)) print '(a)', "no fallthrough is possible" contains function describe(code) result(text) integer, intent(in) :: code character(len=6) :: text select case (code) case (1, 2) text = "low" case (3:5) ! a range text = "medium" case default text = "high" end select end function describe end program cases
The range form case (3:5) and the open forms case (:0) and case (10:) are what C makes you spell as a chain of ifs or a run of empty labels. The selector may be an integer, a character or a logical, but not a real — because equality on floating point is not something the language wants to encourage.
where: An if for Whole Arrays
Applying a condition element by element is a loop with an if in it. where is that construct as a single statement: the mask selects which elements are assigned, and the shape does the iterating.
#include <stdio.h> int main(void) { double values[6] = { -3.0, 1.0, -1.5, 4.0, 0.0, -8.0 }; double clamped[6]; for (int index = 0; index < 6; index++) { if (values[index] < 0.0) { clamped[index] = 0.0; } else { clamped[index] = values[index]; } } for (int index = 0; index < 6; index++) { printf("%5.1f", clamped[index]); } printf("\n"); return 0; }
program masks implicit none real :: values(6) = [-3.0, 1.0, -1.5, 4.0, 0.0, -8.0] real :: clamped(6) where (values < 0.0) clamped = 0.0 elsewhere clamped = values end where print '(6(f5.1))', clamped ! merge is the same idea as an expression: print '(6(f5.1))', merge(0.0, values, values < 0.0) print '(a, i0)', "how many negative: ", count(values < 0.0) end program masks
The mask values < 0.0 is itself an array — of logical, the same shape as values — which is why it can be counted, combined with .and., or handed to sum as its mask= argument. merge is the expression form and works anywhere a value is wanted, which makes it the element-wise ?: that C has no equivalent of.
do concurrent: A Promise About Independence
A C compiler must assume two pointers might alias, which is why restrict exists and why so many loops go unvectorized. do concurrent is the programmer asserting that the iterations do not depend on each other and may run in any order.
#include <stdio.h> /* restrict is the assertion that these do not overlap, which is what lets the compiler vectorize the loop. */ static void scale(double * restrict output, const double * restrict input, size_t count, double factor) { for (size_t index = 0; index < count; index++) { output[index] = input[index] * factor; } } int main(void) { double input[5] = { 1.0, 2.0, 3.0, 4.0, 5.0 }; double output[5]; scale(output, input, 5, 2.5); for (int index = 0; index < 5; index++) { printf("%.1f ", output[index]); } printf("\n"); return 0; }
program concurrency implicit none real :: input(5) = [1.0, 2.0, 3.0, 4.0, 5.0] real :: output(5) integer :: index do concurrent (index = 1:5) output(index) = input(index) * 2.5 end do print '(5(f0.1, 1x))', output ! Which is also just: print '(5(f0.1, 1x))', input * 2.5 end program concurrency
The aliasing question that restrict answers barely arises here — Fortran’s argument rules already forbid a procedure from being passed the same array twice in a way that lets it alias, which is a large part of why Fortran has historically outrun C on numerical loops. do concurrent adds the loop-level promise, and with the right flag a compiler will offload it to threads or a GPU. The last line is the reminder that most such loops did not need to be written at all.
Numeric Facts the Language Knows
The Language Knows Its Own Limits
C puts these facts in <limits.h> and <float.h> as macros with fixed names, one per type. Fortran makes them inquiry functions you apply to a variable, so the same expression works whatever kind that variable has.
#include <stdio.h> #include <limits.h> #include <float.h> int main(void) { printf("int max %d\n", INT_MAX); printf("double eps %.17g\n", DBL_EPSILON); printf("double max %g\n", DBL_MAX); printf("double dig %d\n", DBL_DIG); return 0; }
program inquiry use, intrinsic :: iso_fortran_env, only: int32, real64 implicit none integer(int32) :: whole = 0 real(real64) :: fraction = 0.0_real64 print '(a, i0)', "int max ", huge(whole) print '(a, es23.16e2)', "double eps ", epsilon(fraction) print '(a, es13.5e3)', "double max ", huge(fraction) print '(a, i0)', "double dig ", precision(fraction) print '(a, i0)', "radix ", radix(fraction) end program inquiry
The argument is only there to say which kind you are asking about — its value is ignored, so huge(0_int32) works as well as a variable. That design is what makes generic numerical code possible: a procedure written for real(wp) can use epsilon(x) and get the right answer for whatever wp turns out to be, where the C equivalent needs a different macro per type.
Infinity and NaN, With a Standard Module
Both languages defer to IEEE 754 for floating point, so the values and their behavior are identical. Fortran wraps the questions in an intrinsic module rather than in <math.h> macros.
#include <stdio.h> #include <math.h> int main(void) { double zero = 0.0; double infinite = 1.0 / zero; double undefined = zero / zero; printf("isinf %d\n", isinf(infinite)); printf("isnan %d\n", isnan(undefined)); printf("nan == nan %d\n", undefined == undefined); printf("value %f\n", infinite); return 0; }
program ieee use, intrinsic :: ieee_arithmetic implicit none real :: zero = 0.0, infinite, undefined infinite = ieee_value(zero, ieee_positive_inf) undefined = ieee_value(zero, ieee_quiet_nan) print '(a, l1)', "is_finite ", ieee_is_finite(infinite) print '(a, l1)', "is_nan ", ieee_is_nan(undefined) print '(a, l1)', "nan == nan ", undefined == undefined print '(a, l1)', "supported ", ieee_support_datatype(zero) end program ieee
The Fortran column constructs the values with ieee_value rather than by dividing by zero, because dividing by zero is not required to produce infinity — a conforming implementation may stop the program instead, and the ieee_arithmetic module is how you turn that behavior on and off. Note the first line inverts the C question: there is ieee_is_finite rather than an "is infinite", which is the same information the other way up.
Formatted and List-Directed I/O
List-Directed I/O: Let the Compiler Choose the Format
The * where a format would go means "use a sensible default for whatever these values are". It is the fastest way to print something while debugging, and the reason its output looks slightly different from every compiler.
#include <stdio.h> int main(void) { int count = 42; double ratio = 2.5; const char *label = "answer"; /* Every value needs its own conversion, chosen by you. */ printf("%d %f %s\n", count, ratio, label); return 0; }
program list_directed implicit none integer :: count = 42 real :: ratio = 2.5 character(len=6) :: label = "answer" integer :: numbers(3) = [1, 2, 3] print *, count, ratio, label print *, numbers ! a whole array, no loop end program list_directed
The leading blank on each line is not an accident — it is the historical carriage-control character, and it is why every other example on this page uses an explicit format. What list-directed output does give you is types you never have to name and arrays that print themselves, which is exactly what you want at three in the morning and never what you want in a file somebody else will parse.
An Internal Write Is sprintf
Formatting into a string rather than to a device is spelled by naming a character variable where the unit number would go. That is sprintf, with the buffer’s size fixed by its declaration rather than passed.
#include <stdio.h> int main(void) { char line[64]; int count = 42; double ratio = 2.5; snprintf(line, sizeof line, "count=%d ratio=%.2f", count, ratio); printf("[%s]\n", line); /* Parsing back: sscanf */ int recovered = 0; sscanf("count=42", "count=%d", &recovered); printf("recovered %d\n", recovered); return 0; }
program internal_io implicit none character(len=64) :: line integer :: count = 42, recovered real :: ratio = 2.5 write(line, '(a, i0, a, f0.2)') "count=", count, " ratio=", ratio print '(a, a, a)', "[", trim(line), "]" ! Parsing back: an internal READ. The unit must be a character ! VARIABLE, not a literal, so the text goes into one first. line = "42" read(line, '(i2)') recovered print '(a, i0)', "recovered ", recovered end program internal_io
Note that the internal read takes a character variable, never a literal — read("42", …) is rejected, because the unit is where the data comes from and a literal cannot be one. The buffer cannot overflow, because a write that does not fit is an error rather than a silent overrun — which is snprintf’s truncation made into a diagnosis. The trim in the print is the blank-padding tax from the strings section: line is 64 characters no matter how few were written. Internal reads are how you parse a number out of text without sscanf’s format-string hazards.
Files: open, write, read, close
The same four operations, with keyword arguments instead of a mode string and a small integer unit number instead of a FILE *. The error handling is a status variable rather than a null return.
#include <stdio.h> int main(void) { const char *path = "/tmp/c-fortran-io.txt"; FILE *handle = fopen(path, "w"); if (handle == NULL) return 1; fprintf(handle, "first line\n"); fprintf(handle, "second line\n"); fclose(handle); handle = fopen(path, "r"); if (handle == NULL) return 1; char line[64]; while (fgets(line, sizeof line, handle) != NULL) { printf("read: %s", line); } fclose(handle); remove(path); return 0; }
program file_io implicit none character(len=*), parameter :: path = "/tmp/c-fortran-io.txt" character(len=64) :: line integer :: unit, status open(newunit=unit, file=path, status="replace", action="write") write(unit, '(a)') "first line" write(unit, '(a)') "second line" close(unit) open(newunit=unit, file=path, status="old", action="read") do read(unit, '(a)', iostat=status) line if (status /= 0) exit print '(a, a)', "read: ", trim(line) end do close(unit, status="delete") end program file_io
newunit= asks the runtime for a free unit number, which is what saves you from the older practice of picking one and hoping — units 5 and 6 are standard input and output by convention, and colliding with them is an old and reliable bug. iostat= is the pattern to internalize: it turns what would otherwise abort the program into a status you test, and a negative value specifically means end of file.
Where They Meet: iso_c_binding
iso_c_binding: Types That Match Exactly
This is the destination the page has been pointing at. The iso_c_binding module names a kind for each C type — c_int, c_double, c_size_t — that the compiler guarantees matches the platform’s C compiler, whatever that turns out to be.
#include <stdio.h> #include <stddef.h> int main(void) { printf("int %zu\n", sizeof(int)); printf("long %zu\n", sizeof(long)); printf("double %zu\n", sizeof(double)); printf("size_t %zu\n", sizeof(size_t)); printf("pointer %zu\n", sizeof(void *)); return 0; }
program c_types use, intrinsic :: iso_c_binding implicit none integer(c_int) :: an_int integer(c_long) :: a_long real(c_double) :: a_double integer(c_size_t) :: a_size type(c_ptr) :: a_pointer print '(a, i0)', "int ", storage_size(an_int) / 8 print '(a, i0)', "long ", storage_size(a_long) / 8 print '(a, i0)', "double ", storage_size(a_double) / 8 print '(a, i0)', "size_t ", storage_size(a_size) / 8 print '(a, i0)', "pointer ", storage_size(a_pointer) / 8 end program c_types
The two columns print the same five numbers, which is the guarantee doing its job. Two kinds are worth singling out: c_char for text and c_ptr, which is an opaque handle rather than something you can dereference — turning one into a usable Fortran array takes c_f_pointer, two rows down. A kind that the platform cannot provide comes back negative, so c_long_double < 0 is how you find out it is unavailable.
Calling a C Function With bind(c)
You declare the C function’s signature in an interface block, tag it bind(c) with the symbol name, and call it like any other procedure. No wrapper, no name-mangling guesswork, and the compiler checks every call against the declaration.
#include <stdio.h> #include <string.h> #include <stdlib.h> int main(void) { /* The functions on the other side are these, from the C library that both languages already link against. */ printf("strlen %zu\n", strlen("hello")); printf("abs %d\n", abs(-5)); return 0; }
program calling_c use, intrinsic :: iso_c_binding implicit none interface function c_strlen(text) bind(c, name="strlen") result(length) import :: c_char, c_size_t character(kind=c_char), intent(in) :: text(*) integer(c_size_t) :: length end function c_strlen function c_abs(value) bind(c, name="abs") result(magnitude) import :: c_int integer(c_int), value :: value integer(c_int) :: magnitude end function c_abs end interface print '(a, i0)', "strlen ", c_strlen("hello" // c_null_char) print '(a, i0)', "abs ", c_abs(-5_c_int) end program calling_c
Three details do all the work. value on the abs argument is essential — Fortran passes by reference by default, and C expects the integer itself, so without it you would be handing abs a pointer. c_null_char is appended by hand because a C string is terminated and a Fortran one is not. And import is needed because an interface block does not inherit the enclosing scope’s use.
A Derived Type That Matches a C struct
The standard does not fix how a derived type is laid out — the compiler may reorder or pad as it likes. Declaring it bind(c) forces the C layout, and restricts the fields to types that C has.
#include <stdio.h> #include <stddef.h> struct Header { int kind; double weight; int flags; }; int main(void) { struct Header header = { 7, 2.5, 3 }; printf("sizeof %zu\n", sizeof(struct Header)); printf("kind %zu\n", offsetof(struct Header, kind)); printf("weight %zu\n", offsetof(struct Header, weight)); printf("flags %zu\n", offsetof(struct Header, flags)); printf("values %d %.1f %d\n", header.kind, header.weight, header.flags); return 0; }
program c_structs use, intrinsic :: iso_c_binding implicit none type, bind(c) :: header integer(c_int) :: kind real(c_double) :: weight integer(c_int) :: flags end type header type(header) :: item item = header(7, 2.5_c_double, 3) print '(a, i0)', "sizeof ", storage_size(item) / 8 print '(a, i0, 1x, f0.1, 1x, i0)', "values ", item%kind, & item%weight, item%flags end program c_structs
Both columns report 24 bytes: four for kind, four of padding so weight lands on an eight-byte boundary, eight for weight, four for flags and four more of tail padding. Getting the same number from both sides is the point — without bind(c) the Fortran compiler is free to lay it out differently and nothing would tell you. Only the C column can print the individual offsets: there is no offsetof in Fortran, because a program is not supposed to know where a field sits, and the total size is the whole of what you can check from this side. A bind(c) type may not have allocatable or pointer components, default initializers, or type-bound procedures.
Turning a C Pointer Into a Fortran Array
A c_ptr is opaque; you cannot subscript it. c_f_pointer takes one, plus a shape, and gives back a Fortran pointer array that views the same memory — which is how a buffer allocated on the C side becomes something you can write sum() over.
#include <stdio.h> #include <stdlib.h> int main(void) { size_t count = 5; double *block = malloc(count * sizeof *block); if (block == NULL) return 1; for (size_t index = 0; index < count; index++) { block[index] = (double)index * 1.5; } double total = 0.0; for (size_t index = 0; index < count; index++) { total += block[index]; } printf("total %.1f\n", total); free(block); return 0; }
program c_pointers use, intrinsic :: iso_c_binding implicit none interface function c_malloc(size) bind(c, name="malloc") result(address) import :: c_size_t, c_ptr integer(c_size_t), value :: size type(c_ptr) :: address end function c_malloc subroutine c_free(address) bind(c, name="free") import :: c_ptr type(c_ptr), value :: address end subroutine c_free end interface integer, parameter :: count = 5 type(c_ptr) :: address real(c_double), pointer :: block(:) integer :: index address = c_malloc(int(count * c_sizeof(1.0_c_double), c_size_t)) call c_f_pointer(address, block, [count]) do index = 1, count block(index) = real(index - 1, c_double) * 1.5_c_double end do print '(a, f0.1)', "total ", sum(block) call c_free(address) end program c_pointers
Once c_f_pointer has run, block is an ordinary Fortran array: sum works on it, so do sections and whole-array assignment, and it is 1-based like everything else. It does not own the memory — the c_free at the end is still yours to write, and this is the one place on the page where the C ownership discipline comes back in full. c_loc goes the other way, handing a Fortran target’s address to C.
The Bug That Catches Everyone: Subscript Order
When the two languages share a two-dimensional array, they are looking at the same bytes with opposite conventions. Fortran’s a(i,j) and C’s a[j][i] name the same element — and writing a[i][j] instead reads a transposed matrix, silently.
#include <stdio.h> int main(void) { /* Six doubles as a flat block, exactly as they would arrive from a Fortran caller holding a(2,3). */ double flat[6] = { 1, 2, 3, 4, 5, 6 }; /* Fortran's a(row, column) with 2 rows: the element at (row, column) lives at flat[(column-1)*2 + (row-1)]. */ for (int row = 1; row <= 2; row++) { for (int column = 1; column <= 3; column++) { printf("%.0f ", flat[(column - 1) * 2 + (row - 1)]); } printf("\n"); } return 0; }
program order_trap implicit none real :: a(2, 3) integer :: row, column ! The same six values in the same six memory slots. a = reshape([1., 2., 3., 4., 5., 6.], [2, 3]) do row = 1, 2 do column = 1, 3 write(*, '(i0, 1x)', advance="no") nint(a(row, column)) end do print * end do end program order_trap
Both columns print the same two rows, and the C one had to do the index arithmetic by hand to get there — which is exactly what a real interop layer does. The three practical rules: declare the C side as a flat array and index it yourself, or as double a[3][2] with the subscripts reversed; never pass a non-contiguous Fortran array section to C without letting the compiler make a contiguous copy; and remember that C’s a[j][i] is not a transpose, it is the same matrix read correctly.