Łączenie obiektów#

Prawie wszystkie programy, za wyjątkiem tych najprostszych, są zbudowane z różnych części. Przyjrzymy się takiej sytuacji bardziej szczegółowo.

Tutaj znajduje się ogólny program służący do ujmowania funkcji w tabeli (kod źródłowy znajduje się w „tabulate.f90”):

program tabulate
    use user_functions

    implicit none
    real    :: x, xbegin, xend
    integer :: i, steps

    write(*,*) 'Please enter the range (begin, end) and the number of steps:'
    read(*,*)  xbegin, xend, steps

    do i = 0, steps
        x = xbegin + i * (xend - xbegin) / steps
        write(*,'(2f10.4)') x, f(x)
    end do
end program tabulate

Zwróć uwagę na wyrażenie use - będzie to miejsce, gdzie zdefiniujemy funkcję f.

Chcemy, aby program był ogólny, dlatego oddzielimy szczegółowy kod źródłowy - np. implementację funkcji f - od głównego kodu źródłowego. Istnieje kilka różnych sposobów na osiągniecie tego, ale jednym z nich jest umieszczenie go w innym pliku źródłowym. Możemy wtedy dać ogólny program użytkownikowi, a on dostarczy mu szczegółowy kod źródłowy.

Na potrzebę tego przykładu przyjmijmy, że funkcja jest zaimplementowana w pliku źródłowym „functions.f90” jako:

module user_functions
    implicit none
contains

real function f( x )
    real, intent(in) :: x
    f = x - x**2 + sin(x)
end function f

end module user_functions

Aby stworzyć program z tą specyficzną funkcją, musimy skompilować dwa pliki źródłowe i połączyć je za pomocą procesu łączenia w jeden wykonywalny program. Ponieważ program „tabulate” jest zależny od modułu „function”, musimy najpierw skompilować plik źródłowy zawierający nasz moduł. Sekwencja komend, która to zrobi wygląda następująco:

$ gfortran -c functions.f90
$ gfortran tabulate.f90 functions.o

Pierwszym krokiem jest skompilowanie modułu, którego wynikiem jest plik obiektowy „functions.o” oraz plik pośredni modułu „user_functions.mod”. Ten plik modułu zawiera wszystkie informacje, które potrzebuje kompilator do określenia, że funkcja f jest zdefiniowana w tym module i jaki jest jej interfejs. Ta informacja jest ważna: pozwala ona kompilatorowi na sprawdzenie czy funkcja jest wywoływana w prawidłowy sposób. Możliwym jest, że popełniłeś błąd i wywołałeś funkcję z dwoma argumentami zamiast jednego. Jeśli kompilator nie wie niczego o interfejsie funkcji, nie może tego sprawdzić.

Kolejny krok to wywołuje kompilator w taki sposób, że:

  • kompiluje on plik „tabulate.f90” (używając pliku modułu);

  • wywołuje program łączący, aby połączyć pliki obiektowe tabulate.o i functions.o w jeden wykonywalny program - używając domyślnej nazwy „a.out” lub „a.exe” (jeśli chcesz użyć innej nazwy, zastosuj opcję „-o”).

To, co zazwyczaj nie jest pokazywane to fakt, że program łączący dodaje dodatkowe pliki podczas procesu łączenia, a mianowicie biblioteki środowiska wykonawczego. Biblioteki te zawierają wszystkie „standardowe” rzeczy - procedury niskiego poziomu odpowiedzialne za dane wejściowe i wyjściowa, funkcję sin i wiele więcej.

Aby zobaczyć wszystkie szczegóły, dodaj opcję „-v”. To zleci kompilatorowi szczegółowe raportowanie wszystkich kroków.

Wynik końcowy, wykonywalny program, zawiera skompilowany kod źródłowy oraz różne procedury pomocnicze, które sprawiają, że działa. Zawiera również odniesienia do tak zwanych dynamicznych bibliotek środowiska wykonawczego (na Windowsie: DLL, na Linuksie: obiekty lub biblioteki współdzielone). Bez tych bibliotek środowiska wykonawczego program nie uruchomi się.