Software Engineering
Fortran and C Arrays: Verify Shape and Memory Order
Debug mixed Fortran and C arrays with non-square fixtures, explicit dimensions, interoperable types, index mapping, and tests that catch accidental transposes.
In this article
A numerical routine can return plausible values while reading an array in the wrong order. At a Fortran-to-C boundary, verify shape, element type, index conventions, and memory layout together. Square matrices and repeated values are especially poor tests because they can hide a transpose or dimension swap.
GNU Fortran's C interoperability documentation explains the relevant layout mapping. The Fortran project's September 15, 2026 anniversary article is a reminder that long-lived numerical code still meets modern interfaces. This guide focuses on a small boundary test, not a language popularity comparison.
Label the logical shape
Write down what each dimension means before discussing bytes. A three-by-two logical array might represent three sensors and two time points. If one side calls those dimensions rows and columns and the other calls them width and height, reconcile the meanings explicitly.
Keep the dimensions with the function signature and test fixture. Passing a pointer does not, by itself, communicate every shape or ownership rule. The caller and callee need an agreed contract about the number of elements and how they are indexed.
Do not infer the shape from a filename or a square example. Use a non-square fixture so swapping dimensions becomes visible immediately. Give every element a distinct value that reflects its logical indices.
Use a distinctive fixture
For a Fortran array A(3,2), define the illustrative values by A(i,j) = 100*j + i. The first index varies through 1, 2, and 3 for the first second-index value. The six values make the intended mapping easy to inspect.
The corresponding C storage can be represented as:
double a[2][3] = {
{101, 102, 103},
{201, 202, 203}
};In this mapping, Fortran A(2,1) corresponds to C a[0][1], both holding 102. The example illustrates storage correspondence, not a complete foreign-function interface. Use the interoperable declarations and calling conventions required by the actual routine.
Separate indexing from storage order
Fortran commonly uses a default lower index of one, while C arrays use zero-based indexing. Fortran array bounds can be declared differently, so inspect the actual declaration rather than assuming every Fortran array starts at one.
The layout mapping also reverses the declared dimensions for the ordinary interoperable array example. A Fortran A(n,m) corresponds to C storage declared with the matching reversed dimensions, not an arbitrary A[n][m] declaration.
Do not “fix” an indexing mismatch by transposing every array without understanding the caller's intended layout. A transpose may be necessary for a particular API, but it creates a copy or transformation whose cost and semantics should be explicit.
Match the element and procedure types
Use the language interoperability facilities appropriate to the interface, including ISO_C_BINDING and BIND(C) where required. Do not assume that a default Fortran integer or real type matches a particular C type on every toolchain.
Review whether scalar arguments are passed by value or by reference in the actual interface. Also review how dimensions, pointers, and returned buffers are represented. A correct array order does not compensate for the wrong calling convention.
Keep compiler versions and flags in the test record. Mixed-language builds can accidentally use incompatible assumptions or link an old object file. Rebuild the boundary test cleanly after changing declarations rather than trusting a successful link alone.
Test reads and writes independently
First pass the fixture to a routine that reads known positions and returns them through a simple supported result. Verify corners and an interior element where the shape allows one. Then test a routine that writes a specific logical position and confirm which C element changes.
Use sentinel values around a test buffer where your safe test harness supports them, and run the compiler's applicable bounds or sanitizer checks in a disposable environment. Do not intentionally overrun a production buffer to see whether the interface fails.
Test an empty or zero-sized case only if the interface explicitly supports it. Otherwise verify that the caller rejects it. Include the smallest valid shape and a non-square shape in the regression suite.
Consider contiguity and copies
An array section or view may not have the same contiguous representation as a simple full array. Determine whether the interface accepts such a view, creates a temporary, or requires the caller to prepare contiguous storage. Consult the actual compiler and API documentation for that case.
Measure copying separately from the numerical kernel if performance matters. A fast routine can appear slow when each call transposes or copies a large input. Conversely, removing a necessary copy can silently change the data interpretation.
Keep ownership clear: who allocates the buffer, who may modify it, and how long it remains valid? An otherwise correct mapping can still fail if a caller frees or reuses memory while the callee retains a pointer.
For a readable test manifest, JSON Diff can compare dimensions and configuration between runs. It does not inspect binary arrays or verify numerical correctness. Preserve the actual boundary-test results in your approved build environment.
Conclusion
Use distinct non-square data, explicit types, and position-specific read and write tests at a Fortran/C boundary. Confirm storage, indexing, calling convention, and lifetime together. A plausible numerical result is not enough to prove that both languages interpreted the same array.