Fortranでポインタ変数のアドレスをインクリメントする

# 概要
Fortranには，ポインタ変数に対する算術演算は存在しません．この記事では，Fortranでもポインタ変数が持つアドレスをインクリメントする方法について述べます．
結論から言うと，C言語の関数とポインタを利用するので，**Fortranで**というタイトルは少し過大かもしれません．

## 使用環境
|Fortranコンパイラ(バージョン)|Cコンパイラ(バージョン)|
|:--|:--|
|intel Parallel Studio XE Composer Edition for Fortran (17.0.4.210)|Microsoft(R) C/C++ Optimizing Compiler (19.00.24234.1 for x86)|
|gfortran (7.3.0)|gcc (7.3.0)|

## 更新履歴

- 2018年12月15日
    - 章題目の誤字を修正．
    - 使用したコンパイラとその出力の対応がずれていたので修正．
    - 使用したコンパイラとバージョンの情報を追記．


# C言語におけるポインタ
C言語のポインタ変数とは，ざっくりいうとメモリアドレスを格納する変数です．メモリアドレスは整数なので，ポインタ変数が保持する値は整数型ですが，宣言の際にそのメモリアドレスに何型の値があるかの情報を明記します．**メモリアドレス＋そこにある値の型の特定**の二つが，ポインタを利用する上で欠かすことができません．

```C
#include<stdio.h>
int main()
{
    int a = 10;
    int *ptr_a;

    ptr_a = &a;
    printf("%d, %d\n",  a, *ptr_a); ! 10, 10
    printf("%p, %p\n", &a,  ptr_a); !0x7fffcb7b131c, 0x7fffcb7b131c

    return 0;
}
```

C言語のポインタ変数は，演算をすることで当該ポインタ変数が指すアドレスを変化させることができます．このとき，単純に1を足すという演算を行っただけでも，当該メモリアドレスに存在する値の型のサイズ（バイト数）分アドレスが変化します．

```C
#include<stdio.h>
int main()
{
    int a[] = {2,1};
    int *ptr_a;

    ptr_a = a;
    printf("%d, %p\n", *ptr_a, ptr_a); ! 2, 0x7fffe2062a90
    ptr_a++;
    printf("%d, %p\n", *ptr_a, ptr_a); ! 1, 0x7fffe2062a94

    return 0;
}
```

この挙動を利用して，文字型配列に対するポインタ変数をインクリメント/デクリメントすることで，当該ポインタ変数が指すアドレスを連続的に変化させて文字列を読み出すことができます．

```C
#include<stdio.h>
int main()
{
    char *str = "hello world\n";

    while(*str != '\n'){
        printf("%c, %p\n", *str, str);
        str++;
    }

    return 0;
}
```

# Fortranにおけるポインタ
Fortranのポインタも，メモリアドレスとそのアドレスにある型が何型かの情報を管理しています．Fortranはメモリアドレスを意識しなくてもよい言語なので，C言語にのようにメモリアドレスを操作することは想定されていません．想定されているFortranの使用用途ではポインタの取り扱いは全く苦ではありません．

しかしながら，C言語との相互利用では，メモリアドレスの操作が必要になる場面があります．著者が遭遇したのは，C言語の文字列を読む場面です．

C言語では，文字列[^arr]を関数に渡す時に，その先頭アドレスを渡します．受け取った関数側では，Null文字を検出するまでアドレスを単項演算子でインクリメントして文字列を読みます．

[^arr]: 文字列（文字型の配列）だけに限りません．

C言語との相互利用では，C言語のポインタ変数を`type(c_ptr)`に置き換えることになります．この置き換えは，C言語のポインタ変数と同様な扱いをするには非常に難しい問題を抱えています．

1. メモリアドレスにある値の型の情報が失われる．
1. 実際にアドレスを持つ成分の`integer(c_intptr_t)`型変数が`private`であるため，読み込めない．
1. 何らかの方法でアドレスが読めても，同様の理由で書き込めない．

しかし，`type(c_ptr)`は`integer(c_intptr_t)`型変数のみを成分にもつ派生型なので，種別が同じ整数を`type(c_ptr)`であると騙すことができれば，`type(c_ptr)`のアドレスを変更できます．

- コンストラクタ`c_ptr()`は，成分が`private`属性のため利用できません．
- Fortranの関数は，型のチェックに引っかかるので利用できません．

そこで，アドレスをC言語の関数でポインタ変数として受取り，関数内で編集して，ポインタ変数を返すという手法を採用します．

# アドレスをインクリメントする関数
C言語では，ポインタ変数の宣言において，アドレスの先にある値が何型かという情報を明記することは上で述べました．そして，`type(c_ptr)`ではメモリアドレスにある値の型の情報が失われることも指摘しました．

そのため，C言語側でアドレスを1バイトずつインクリメントするようにして，インクリメントするバイト数を引数で渡すことにしました．1バイト単位でアドレスを取り扱うために`char`型に対するポインタ`char *`を利用します．
関数内では，引数として受け取ったポインタ変数`address`と，バイト数の増分`increment_bytes`を足しています．
仮引数の型を定めたのと同様の理由で，戻り値もchar型のポインタ`char *`としています．

```C
extern "C" {
    char* incrementAddress(char *address, int increment_bytes) {
        return address + increment_bytes;
    }
}
```

インタフェースは次のように書けます．

```Fortran
    interface
        type(c_ptr) function incrementAddress(address, byte) bind(c,name="incrementAddress")
            use,intrinsic :: iso_c_binding
            implicit none
            type(c_ptr)        ,intent(in),value :: address
            integer(c_intptr_t),intent(in),value :: byte
        end function incrementAddress
    end interface
```

引数はどちらも値渡しとしています．Fortranはポインタ渡しが標準であるため，値渡しとしない場合には，C言語側の関数の引数`char *address, int increment_bytes`の型を変更する必要があります．
`byte`の実引数はユーザが管理しなければなりません．`sizeof()`を利用するのが無難です．種別がバイト数に等しいという決まりがFortranにあれば，一手間削減できるのですが･･･

```Fortran
program main
    use, intrinsic :: iso_c_binding
    implicit none
    
    interface
        type(c_ptr) function incrementAddress(address, byte) bind(c,name="incrementAddress")
            use,intrinsic :: iso_c_binding
            implicit none
            type(c_ptr)        ,intent(in),value :: address
            integer(c_intptr_t),intent(in),value :: byte
        end function incrementAddress
    end interface
            
    type(c_ptr) :: addr
    integer(c_intptr_t) :: inc

    character(:),allocatable,target :: str
    integer,target :: a(10) = [1,2,3,4,5,6,7,8,9,10]

    character,pointer :: ptr_s
    integer  ,pointer :: ptr_i

    integer :: i
    
    str = "abcdefghijklmnopqrstuvwxyz"//c_null_char
    print *,len(str),str ! 27 abcdefghijklmnopqrstuvwxyz

    inc = sizeof(c_null_char) ! character型のバイト数
    addr = c_loc(str)         ! 文字列strの先頭アドレス
    call c_f_pointer(addr,ptr_s)
    do while( ptr_s(1:1) /= c_null_char )
        print *,ptr_s(1:1)
        addr = incrementAddress(addr, byte=inc)
        call c_f_pointer(addr,ptr_s)
    end do

    inc = sizeof(0) ! integer型（標準の種別）のバイト数
    addr = c_loc(a) ! 配列aの先頭アドレス
    do i=1, size(a)
        call c_f_pointer(addr,ptr_i)
        print *,ptr_i
        addr = incrementAddress(addr, byte=inc)
    end do
end program main
```

これを実行すると，一つずつ文字と数字が表示されます．

```console
 a
 b
 c
 d
 e
 f
 g
 h
 i
 j
 k
 l
 m
 n
 o
 p
 q
 r
 s
 t
 u
 v
 w
 x
 y
 z
           1
           2
           3
           4
           5
           6
           7
           8
           9
          10
```

# 黒魔術
`transfer`関数は，`private`属性など意にも介さずビット列を別の型に変換します．

```Fortran
program main
    use, intrinsic :: iso_c_binding
    implicit none

    type(c_ptr)         :: addr
    integer(c_intptr_t) :: add,inc

    inc = sizeof(c_null_char) ! アドレスの増分
    add = transfer(addr,add)  ! 整数型をポインタに変換

    print *, add     ! I 21646640    g 140737119488032    
    print *, add+inc !   21646641      140737119488033
    
    addr = transfer(add+inc,addr) ! 増分を足した整数をポインタに変換
end program main
```

これを利用すると，わざわざC言語の関数を作成する必要がなくなります．
まず`transfer`で`type(c_ptr)`を`integer(c_intptr_t)`に変換し，アドレスの増分を足した後に，もう一度`transfer`で`integer(c_intptr_t)`から`type(c_ptr)`に変換します．
下のプログラムでは，見本となる変数を用意する手間を省くために，長さ0の`integer(c_intptr_t)`型配列`[integer(c_intptr_t)::]`を見本とし，配列サイズを1としています．

```Fortran
program main
    use, intrinsic :: iso_c_binding
    implicit none
    
    interface
        type(c_ptr) function incrementAddress(address, byte) bind(c,name="incrementAddress")
            use,intrinsic :: iso_c_binding
            implicit none
            type(c_ptr)        ,intent(in),value :: address
            integer(c_intptr_t),intent(in),value :: byte
        end function incrementAddress
    end interface
            
    type(c_ptr) :: addr
    integer(c_intptr_t) :: inc
    character(:),allocatable,target :: str
    character,pointer :: ptr_s
    
    str = "abcdefghijklmnopqrstuvwxyz"//c_null_char
    print *,len(str),str ! 27 abcdefghijklmnopqrstuvwxyz

    inc = sizeof(c_null_char) ! character型のバイト数
    addr = c_loc(str)         ! 文字列strの先頭アドレス
    call c_f_pointer(addr,ptr_s)
    do while( ptr_s(1:1) /= c_null_char )
        print *,ptr_s(1:1)
        addr = transfer( transfer(addr,[integer(c_intptr_t)::],1)+inc, addr )
        call c_f_pointer(addr,ptr_s)
    end do
end program main
```

# まとめ
これを利用することで，C言語のコマンドライン引数`char **argv`をFortranから読めるようになります．