상호 운용 가능한 서브루틴과 함수

상호 운용 가능한 서브루틴과 함수 (Interoperable Subroutines and Functions)

Fortran으로 작성한 서브루틴이나 함수를 C에서 호출해야 할 때가 있어요. 이때 가장 중요한 건 바로 값을 전달하는 방식이에요. C는 기본적으로 값을 복사해서 넘기는 반면, Fortran은 대개 참조로 넘기는 방식이라 이 차이를 이해하는 게 핵심이에요. 이 글에서는 C와 호환되는 서브루틴과 함수를 만드는 방법을 살펴볼게요.

출처: Interoperable Subroutines and Functions

본문

BIND(C)와 인자 전달

서브루틴과 함수가 C와 호환되려면 BIND(C) 속성을 가져야 해요. 가짜 인자(dummy argument)의 선언은 비교적 직관적이에요. 다만 조심해야 할 점이 있어요. C는 기본적으로 값에 의한 호출(call-by-value)을 사용하는 반면 Fortran은 보통 참조에 의한 호출(call-by-reference)과 유사하게 동작하기 때문이에요. 게다가 문자열(string)과 포인터도 다르게 처리돼요.

변수를 값으로 넘기려면 VALUE 속성을 사용하면 돼요. 예를 들어 아래 C 프로토타입이 있어요.

int func(int i, int *j)

이것은 다음 Fortran 선언과 대응돼요.

  integer(c_int) function func(i,j)
    use iso_c_binding, only: c_int
    integer(c_int), VALUE :: i
    integer(c_int) :: j

여기서 i는 값으로 넘겨지고(VALUE), j는 참조로 넘겨져요. 포인터 인자도 자주 VALUE 속성을 필요로 한다는 점을 기억해 두면 좋아요. 자세한 내용은 Working with C Pointers에서 확인할 수 있어요.

문자열 처리

문자열은 C와 Fortran에서 상당히 다르게 처리돼요. C에서 문자열은 NUL로 끝나는 문자 배열이에요. 반면 Fortran에서 각 문자열은 길이를 함께 가지기 때문에 NUL 같은 것으로 끝나지 않아요.

예를 들어 다음과 같은 C 함수가 있다고 해볼게요.

  #include <stdio.h>
  void print_C(char *string) /* equivalent: char string[]  */
  {
     printf("%s\n", string);
  }

이 함수를 Fortran에서 "Hello World"를 출력하는 데 사용하려면 다음과 같이 호출할 수 있어요.

  use iso_c_binding, only: C_CHAR, C_NULL_CHAR
  interface
    subroutine print_c(string) bind(C, name="print_C")
      use iso_c_binding, only: c_char
      character(kind=c_char) :: string(*)
    end subroutine print_c
  end interface
  call print_c(C_CHAR_"Hello World"//C_NULL_CHAR)

예시에서 보듯이, 문자열이 NUL로 끝나도록 직접 보장해 줘야 해요. 또한 print_C의 가짜 인자 string은 길이 1의 가정 크기 배열(assumed-size array)이에요. 여기에 character(len=*)를 사용할 수는 없어요. 위 예시는 c_char_"Hello World"를 사용해서 문자열 리터럴이 올바른 타입을 갖도록 보장해요. 보통 기본 문자 종류와 c_char는 같기 때문에 "Hello World"로 써도 동등하지만, 표준이 이를 보장하지는 않아요.

strncpy 예시

문자열 사용을 C 라이브러리 함수 strncpy로 더 자세히 보여줄게요. 프로토타입은 다음과 같아요.

  char *strncpy(char *restrict s1, const char *restrict s2, size_t n);

strncpy는 문자열 s2에서 최대 n개 문자를 s1에 복사하고 s1을 반환해요. 아래 예시에서는 반환 값을 무시해요.

  use iso_c_binding
  implicit none
  character(len=30) :: str,str2
  interface
    ! Ignore the return value of strncpy -> subroutine
    ! "restrict" is always assumed if we do not pass a pointer
    subroutine strncpy(dest, src, n) bind(C)
      import
      character(kind=c_char),  intent(out) :: dest(*)
      character(kind=c_char),  intent(in)  :: src(*)
      integer(c_size_t), value, intent(in) :: n
    end subroutine strncpy
  end interface
  str = repeat('X',30) ! Initialize whole string with 'X'
  call strncpy(str, c_char_"Hello World"//C_NULL_CHAR, &
               len(c_char_"Hello World",kind=c_size_t))
  print '(a)', str ! prints: "Hello WorldXXXXXXXXXXXXXXXXXXX"
  end

Fortran 2023의 문자열 변환 함수

Fortran 2023에서는 C와 Fortran 문자열 표현 사이를 변환하는 두 개의 새로운 내장 함수가 추가됐어요.

  • f_c_string — Fortran 문자열에 널 문자(null character)를 붙여 C 문자열로 변환해요.
  • c_f_strpointer — 널로 끝나는 C 문자열, 혹은 단순히 연속된(contiguous) c_char 배열을 Fortran의 지연 길이(deferred-length) 문자 포인터로 접근할 수 있게 해 줘요.

이 내장 함수들에 대한 자세한 설명은 Intrinsic Procedures 문서에서 확인할 수 있어요.

더 알아보기