C 포인터 다루기

C 포인터 다루기

Fortran과 C를 섞어 쓸 때 가장 먼저 부딪히는 어려움이 포인터예요. C는 포인터를 중심으로 돌아가지만 Fortran에는 같은 개념이 없으니까요. GNU Fortran은 iso_c_binding 모듈과 type(c_ptr) 타입으로 이 다리를 놓아 줘요. 실제로 어떻게 주고받는지 예제와 함께 살펴볼게요.

출처: Working with C Pointers

본문

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_pointerc_ptr을 Fortran 포인터로 되돌려 줘요. 배열을 C 배열로 변환할 때는 1차원 SHAPE 인자를 꼭 넘겨야 해요.

포인터가 상호 운용 가능한 프로시저의 가상 인자(dummy argument)라면, 보통 VALUE 속성으로 선언해야 해요. void*TYPE(C_PTR), VALUE에 맞고, TYPE(C_PTR)만 쓰면 void**에 맞아요.

프로시저 포인터는 포인터와 비슷하게 다뤄져요. 이때 C 타입은 TYPE(C_FUNPTR)이고, 내장 변환 프로시저는 C_F_PROCPOINTERC_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_BINDINGUSE 하고, 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가 이어져요.