C 포인터 다루기
C 포인터 다루기
Fortran과 C를 섞어 쓸 때 가장 먼저 부딪히는 어려움이 포인터예요. C는 포인터를 중심으로 돌아가지만 Fortran에는 같은 개념이 없으니까요. GNU Fortran은 iso_c_binding 모듈과 type(c_ptr) 타입으로 이 다리를 놓아 줘요. 실제로 어떻게 주고받는지 예제와 함께 살펴볼게요.
본문
C 포인터는 Fortran에서 특별한 불투명 파생 타입(opaque derived type)인 type(c_ptr)로 표현돼요. 이 타입은 private 컴포넌트를 가져요. C 포인터는 Fortran의 POINTER 속성이 붙은 객체와는 구별되는 개념이에요. 그래서 C 포인터로 변환하거나 C 포인터에서 변환하려면 내장 변환 프로시저를 사용해야 해요.
어떤 응용에서는 가정 타입(TYPE(*))을 C 포인터의 대안으로 쓸 수도 있고, C 쪽에서 Fortran 포인터에 접근하려면 라이브러리 루틴을 사용할 수도 있어요. 자세한 내용은 Further Interoperability of Fortran with C 항목에서 다뤄요.
Fortran에서 C 포인터를 사용하는 예제를 볼게요.
use iso_c_binding
type(c_ptr) :: cptr1, cptr2
integer, target :: array(7), scalar
integer, pointer :: pa(:), ps
cptr1 = c_loc(array(1)) ! The programmer needs to ensure that the
! array is contiguous if required by the C
! procedure
cptr2 = c_loc(scalar)
call c_f_pointer(cptr2, ps)
call c_f_pointer(cptr2, pa, shape=[7])
여기서 c_loc이 Fortran 객체의 주소를 c_ptr로 바꿔 주고, 반대로 c_f_pointer가 c_ptr을 Fortran 포인터로 되돌려 줘요. 배열을 C 배열로 변환할 때는 1차원 SHAPE 인자를 꼭 넘겨야 해요.
포인터가 상호 운용 가능한 프로시저의 가상 인자(dummy argument)라면, 보통 VALUE 속성으로 선언해야 해요. void*는 TYPE(C_PTR), VALUE에 맞고, TYPE(C_PTR)만 쓰면 void**에 맞아요.
프로시저 포인터는 포인터와 비슷하게 다뤄져요. 이때 C 타입은 TYPE(C_FUNPTR)이고, 내장 변환 프로시저는 C_F_PROCPOINTER와 C_FUNLOC이에요.
프로시저 포인터를 C에서 Fortran으로, 그리고 Fortran에서 C로 실제로 넘기는 두 가지 예를 살펴볼게요. 이 예들은 두 언어 사이에서 일반 포인터를 넘기는 것과도 아주 비슷해요.
먼저 C 쪽 코드예요.
/* Procedure implemented in Fortran. */
void get_values (void (*)(double));
/* Call-back routine we want called from Fortran. */
void
print_it (double x)
{
printf ("Number is %f.\n", x);
}
/* Call Fortran routine and pass call-back to it. */
void
foobar ()
{
get_values (&print_it);
}
이에 대응하는 Fortran의 get_values 구현은 다음과 같아요. C에서 프로시저 포인터를 받아서 제대로 호출할 수 있게 하죠.
MODULE m
IMPLICIT NONE
! Define interface of call-back routine.
ABSTRACT INTERFACE
SUBROUTINE callback (x)
USE, INTRINSIC :: ISO_C_BINDING
REAL(KIND=C_DOUBLE), INTENT(IN), VALUE :: x
END SUBROUTINE callback
END INTERFACE
CONTAINS
! Define C-bound procedure.
SUBROUTINE get_values (cproc) BIND(C)
USE, INTRINSIC :: ISO_C_BINDING
TYPE(C_FUNPTR), INTENT(IN), VALUE :: cproc
PROCEDURE(callback), POINTER :: proc
! Convert C to Fortran procedure pointer.
CALL C_F_PROCPOINTER (cproc, proc)
! Call it.
CALL proc (1.0_C_DOUBLE)
CALL proc (-42.0_C_DOUBLE)
CALL proc (18.12_C_DOUBLE)
END SUBROUTINE get_values
END MODULE m
다음으로, 프로시저 포인터 인자를 기대하는 C 루틴을 호출하면서 Fortran 프로시저를 넘기는 예를 볼게요. 물론 그 프로시저는 반드시 상호 운용 가능해야 해요. C 함수는 다음과 같을 수 있어요.
int
call_it (int (*func)(int), int arg)
{
return func (arg);
}
그리고 다음과 같은 Fortran 코드로 사용할 수 있어요.
MODULE m
USE, INTRINSIC :: ISO_C_BINDING
IMPLICIT NONE
! Define interface of C function.
INTERFACE
INTEGER(KIND=C_INT) FUNCTION call_it (func, arg) BIND(C)
USE, INTRINSIC :: ISO_C_BINDING
TYPE(C_FUNPTR), INTENT(IN), VALUE :: func
INTEGER(KIND=C_INT), INTENT(IN), VALUE :: arg
END FUNCTION call_it
END INTERFACE
CONTAINS
! Define procedure passed to C function.
! It must be interoperable!
INTEGER(KIND=C_INT) FUNCTION double_it (arg) BIND(C)
INTEGER(KIND=C_INT), INTENT(IN), VALUE :: arg
double_it = arg + arg
END FUNCTION double_it
! Call C function.
SUBROUTINE foobar ()
TYPE(C_FUNPTR) :: cproc
INTEGER(KIND=C_INT) :: i
! Get C procedure pointer.
cproc = C_FUNLOC (double_it)
! Use it.
DO i = 1_C_INT, 10_C_INT
PRINT *, call_it (cproc, i)
END DO
END SUBROUTINE foobar
END MODULE m
이 예에서 C_FUNLOC이 Fortran 프로시저를 c_funptr로 바꿔 주고, 그걸 call_it에 넘겨요. 핵심은 양방향 모두 ISO_C_BINDING을 USE 하고, VALUE 속성으로 자료를 전달한다는 점이에요.
더 알아보기
- 데이터 포인터는
C_LOC,C_F_POINTER로, 프로시저 포인터는C_FUNLOC,C_F_PROCPOINTER로 변환해요. - 상호 운용을 위해선 프로시저와 인터페이스에
BIND(C)를 붙이고ISO_C_BINDING의 종류 상수를 사용해야 해요. - 이 항목은 GNU Fortran의 "Interoperability with C" 절에 속하며, 다음 항목으로 Further Interoperability of Fortran with C가 이어져요.