상호 운용 가능한 서브루틴과 함수
상호 운용 가능한 서브루틴과 함수 (Interoperable Subroutines and Functions)
Fortran으로 작성한 서브루틴이나 함수를 C에서 호출해야 할 때가 있어요. 이때 가장 중요한 건 바로 값을 전달하는 방식이에요. C는 기본적으로 값을 복사해서 넘기는 반면, Fortran은 대개 참조로 넘기는 방식이라 이 차이를 이해하는 게 핵심이에요. 이 글에서는 C와 호환되는 서브루틴과 함수를 만드는 방법을 살펴볼게요.
본문
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 문서에서 확인할 수 있어요.
더 알아보기
- C 포인터를 Fortran에서 다루는 방법은 Working with C Pointers 문서에서 확인할 수 있어요.
- C와의 전체적인 상호 운용 개요는 Interoperability with C 문서에서 다뤄요.
- 전역 변수를 C에서 접근 가능하게 만드는 방법은 Interoperable Global Variables 문서를 참고해 보세요.