Epigraph
About 15 years ago, when they didn’t know about MSBuild, there were people who loved to write large calculation programs in Fortran, but didn’t use make for religious reasons ...
Level of training
Average.
Foreword
Good day!
')
The word "Fortran" can cause many different overtones in your soul (not to be confused with Oberon).
If it seems to you that his time has already passed, just ignore this article.
Introduction
You wondered what would happen when you write
call Foo();
?
The correct answer - it all depends on where you write it. If the chalk on the fence - then soon you will approach the janitor with bad intentions.
If you write this in your favorite text editor in the file FooProgram.f90, then it is likely that in this way you are asking the Fortran compiler to call the FOO procedure in this place without passing any parameters to it.
!FooProgram.f90 Program FooProgram call Foo(); end program
My experience tells me that at this moment the compiler from Intel inserts the code for calling the _FOO subroutine.
Those. no verification at this stage takes place. If the linker finds the _FOO character when assembling object files and static libraries (Fortran does not distinguish between lowercase and uppercase letters, so characters are always generated in uppercase. It is also forbidden in Fortran to start identifier naming with an underscore character), it will allow reference to _FOO, and Everything will earn you.
If he does not, then he will write:
1>------ Build started: Project: Console1, Configuration: Debug Win32 ------ 1>Compiling with Intel(R) Visual Fortran 11.1.051 [IA-32]... 1>FooProgram.f90 1>Linking... 1>FooProgram.obj : error LNK2019: unresolved external symbol _FOO referenced in function _MAIN__ 1>Debug\Console1.exe : fatal error LNK1120: 1 unresolved externals 1> 1>Build log written to "file://F:\2011_03_12\CPP_PROJECTS\Console1\Console1\Debug\BuildLog.htm" 1>Console1 - 2 error(s), 0 warning(s) ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
Feels trick?
That's right - no static check will occur. And if foo is defined in a different Bar.f90 compilation unit as follows:
!Bar.f90 Subroutine Foo(bar) Integer, intent(in) :: bar Write bar; end subroutine
?
Uncertainty welcomes you by calling your half-sister Vulnerability.
The fact is that FOO inside Bar.f90 is a Program Unit (Program Units) of the type “External Procedure”.
But we remember that there are three more software units in Fortran95:
- Main program
- Modules
- Block data program units
The most interesting thing about this is, of course, the software unit Module.
It will help us to gain a static check at the compilation stage.
Consider a modified program:
!FooProgram2.f90 Program FooProgram use BarModule; call Foo(); end program !Bar.f90 module BarModule contains Subroutine Foo(bar) Integer, intent(in) :: bar Write bar; end subroutine end module
When you run the build, the careful compiler will tell you that without parameters you should not call FOO:
1>------ Build started: Project: Console1, Configuration: Debug Win32 ------ 1>Compiling with Intel(R) Visual Fortran 11.1.051 [IA-32]... 1>Bar.f90 1>FooProgram.f90 1>F:\2011_03_12\CPP_PROJECTS\Console1\Console1\FooProgram.f90(4): error #6631: A non-optional actual argument must be present when invoking a procedure with an explicit interface. [BAR] 1>compilation aborted for F:\2011_03_12\CPP_PROJECTS\Console1\Console1\FooProgram.f90 (code 1) 1> 1>Build log written to "file://F:\2011_03_12\CPP_PROJECTS\Console1\Console1\Debug\BuildLog.htm" 1>Console1 - 2 error(s), 0 warning(s) ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
How did the compiler know that calling FOO in a program unit Main program does not correspond to the implementation? In part, he himself suggested the answer to you: he began to consider FOO as a procedure with an explicit interface (a procedure with an explicit interface), which we perfidiously tried to break.
Here I will cheat and change the imperative point of view on the compilation process to the declarative one.
Consider two processes:
The first: the creation of a module of the specification as a result of the compilation of a program unit, which contains such elements as:
- data objects
- parameters
- structures
- procedures
- operators
This information is placed by the Fortran compiler in the file barmodule.mod (Send hello to header files and CLR).
Second, import the module specification into another software unit.
This is exactly what happens during the compilation of the line.
use BarModule;
Assuming that the first process is implicitly called before process two, it becomes clear to us how the compiler was able to point us to our error.
After all, now FOO () is an explicit, rather than implicit as before, call to the FOO procedure. The compiler is able to test.
It seems to be all right. Everyone is happy.
We start writing module by module, building our application. And the compiler helps us catch stupid coding errors at the compilation stage.
Let us have a module FOO_BAZmodule and procedure FOO_BAZ in it.
Those. Our “heavy project for calculating the dynamics of the coolant in the cooling loop of the reactor” now looks like this:
!FooProgram.f90 Program FooProgram use BarModule; call Foo(1); end program !Bar.f90 module BarModule contains Subroutine Foo(bar) use FOO_BAZModule Integer, intent(in) :: bar Write(*,*) bar; call Foo_BAZ(); end subroutine end module !Foo_Baz.f90 module FOO_BAZModule contains subroutine FOO_BAZ() write(*,*) "FOO_BAZ" endsubroutine end module
It would seem that the dependency chain FooProgram <- Foo <- Foo_BAZ could be broken into two:
FooProgram <- Foo and Foo <- Foo_BAZ.
However, the compiler is not all powerful. And with any change in the implementation of Foo_Baz, you will see that the build log looks like this:
1>------ Build started: Project: Console1, Configuration: Debug Win32 ------ 1>Compiling with Intel(R) Visual Fortran 11.1.051 [IA-32]... 1>FOO_BAZ.f90 1>Bar.f90 1>FooProgram.f90 1>Linking... 1>Embedding manifest... 1> 1>Build log written to "file://F:\2011_03_12\CPP_PROJECTS\Console1\Console1\Debug\BuildLog.htm" 1>Console1 - 0 error(s), 0 warning(s) ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
Bar.f90 and FooProgram.f90 have been recompiled for "reinsurance" a priori.
Believe me - this is unbearable when half of the application is rebuilt due to the fact that you added one extra space in the string constant ...
But what to do if the build system (in my example - based on Intel Visual Fortran) with my obvious actions works in this way?
Actually hack
As you can see, the interfaces for FOO and for FOO_BAZ were generated implicitly, because we provided the compiler with only the implementation of these procedures. Well - again, we are hampered by implicitness ...
We remove it using an explicit interface description for the FOO_BAZ procedure:
interface subroutine FOO_BAZ() endsubroutine endinterface
Put this interface in the module FOO_BAZModule
!Foo_baz.f90 module FOO_BAZModule interface subroutine FOO_BAZ() endsubroutine endinterface contains subroutine FOO_BAZ() write(*,*) "FOO_BAZ 2" endsubroutine end module
But when compiling we are in for an unpleasant surprise:
1>------ Build started: Project: Console1, Configuration: Debug Win32 ------ 1>Compiling with Intel(R) Visual Fortran 11.1.051 [IA-32]... 1>FOO_BAZ.f90 1>F:\2011_03_12\CPP_PROJECTS\Console1\Console1\FOO_BAZ.f90(8): error #6645: The name of the module procedure conflicts with a name in the encompassing scoping unit. [FOO_BAZ] 1>compilation aborted for F:\2011_03_12\CPP_PROJECTS\Console1\Console1\FOO_BAZ.f90 (code 1) 1>Bar.f90 1>FooProgram.f90 1> 1>Build log written to "file://F:\2011_03_12\CPP_PROJECTS\Console1\Console1\Debug\BuildLog.htm" 1>Console1 - 2 error(s), 0 warning(s) ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
The name of the modular procedure conflicts with the name of the interface.
What to do?
Does the implicitness that we are trying to conquer take precedence over us, and we will be doomed to spend half the working time on recompiling on the fact of unchanged code?
Hack Step Two
We will print the implementation of the FOO_BAZ interface into a separate compilation unit:
!Foo_baz.f90 module FOO_BAZModule interface subroutine FOO_BAZ() endsubroutine endinterface end module !FOO_BAZ_Implementation.f90 subroutine FOO_BAZ() write(*,*) "FOO_BAZ 2" endsubroutine
Compile ...
1>------ Build started: Project: Console1, Configuration: Debug Win32 ------ 1>Compiling with Intel(R) Visual Fortran 11.1.051 [IA-32]... 1>FOO_BAZ_Implementation.f90 1>FOO_BAZ.f90 1>Bar.f90 1>FooProgram.f90 1>Linking... 1>Embedding manifest... 1> 1>Build log written to "file://F:\2011_03_12\CPP_PROJECTS\Console1\Console1\Debug\BuildLog.htm" 1>Console1 - 0 error(s), 0 warning(s) ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
What now, instead of three files will have to compile four. Hack didn't work?
Let's make changes to the implementation of FOO_BAZ, which has now become a program unit of type “external procedure”, and run the compilation:
1>------ Build started: Project: Console1, Configuration: Debug Win32 ------ 1>Compiling with Intel(R) Visual Fortran 11.1.051 [IA-32]... 1>FOO_BAZ_Implementation.f90 1>Linking... 1>Embedding manifest... 1> 1>Build log written to "file://F:\2011_03_12\CPP_PROJECTS\Console1\Console1\Debug\BuildLog.htm" 1>Console1 - 0 error(s), 0 warning(s) ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
That's all. Hack worked.
What will have to pay for this hack, in addition to the extra unit of compilation?
The fact that we are now responsible for the fact that the “external procedure” specification FOO_BAZ satisfies (matches) the FOO_BAZ interface from the FOO_BAZModule module.
findings
Didn't we get the same thing that we had at the entrance?
From a practical point of view - no, not received, because manual verification is now necessary only at the last stage - interface implementation.
In my design project, a similar hack allowed me to detach a huge chunk of stable code from modules that were in active development.
Epilogue
Of course, now that the compiler family for the .NET platform has access to the entire project at the very moment when you write code in Microsoft Visual Studio, these reflections can only cause a good-natured smile, but you should not forget that in an engineering environment they don’t rush to go on managed code, and Intel continues to develop the Fortran compiler.
And Fortran itself does not stand still: the standards Fortran 2003 and Fortran 2008 have long been available to us.
My colleague a year ago drew my attention to the fact that the hack offered to your court today was reflected in the new standards, but as far as I know, this has not yet been implemented in most Fortran compilers, both commercial and free.
Links
The article used the compilation logs of the Intel® Visual Fortran Console Application project using the Intel® Visual Fortran Compiler Compiler for Microsoft Visual Studio * 2008, 11.1.3468.2008, Copyright © 2002-2009 Intel Corporation
* Other names and brands may be required.
to Microsoft Visual Studio 2008
Version 9.0.30729.1 SP © 2007 Microsoft Corporation
And materials from the Intel® Fortran Compiler 11.1 User and Reference Guides
Ps The author may be sincerely mistaken about any statement made.