Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Why do we confuse String and Array of Characters in Fortran?

0 views

Published on

レガシーなFORTRANコードでは,文字列と文字の配列を混同して使用している例が多く見受けられる.なぜ混同してしまうのかを調査し,その対策をまとめた.
2019年5月4日 モダンFortran勉強会.f01にて発表

Published in: Software
  • Be the first to comment

  • Be the first to like this

Why do we confuse String and Array of Characters in Fortran?

  1. 1. なぜFortranの文字列と文字型配列を 混同してしまうのか 出川智啓(モダンFortran勉強会)
  2. 2. 文字列と文字の配列の混同  企業のFORTRAN資産で見られるレガシーコードの代表例  文字列を文字の配列として処理している  関数に渡した文字列を文字の配列として受け取っている  仮引数の配列要素数を決め打ちしており,柔軟性がない  なぜ文字列と文字の配列を混同・誤用してしまうのか?  個人のスキルの問題2割,Fortranの問題8割くらい 2019/5/4モダンFortran勉強会.f012
  3. 3. 内容  Fortranの文字型配列  Fortranの文字列  文字型配列・文字列を受け取る関数の仮引数  まとめ 2019/5/4モダンFortran勉強会.f013
  4. 4. 型宣言  文字の配列    属性で指定することもできる  文字列    UNICODEが扱える処理系では,1文字あたりのByte数を指定可能  標準は1Byteで,4Byteを選べるのは今のところgfortranのみ character :: charArray(要素数) character,dimension(要素数) :: charArray character(文字列長) :: str character(len=文字列長,kind={1,4}) :: str 2019/5/4モダンFortran勉強会.f014
  5. 5. 文字の配列  型がcharacterの配列  Fortranの配列操作の機能が利用できる  charArray(:)で全要素を一括参照  size関数で文字列の長さがわかる print *,charArray(:) !test.dat print *,size(charArray) !8 2019/5/4モダンFortran勉強会.f015
  6. 6. 文字の配列  型がcharacterの配列  Fortranの配列操作の機能が利用できる  charArray(i)でi文字目を参照  charArray(i:j)でi文字目からj文字目までのサブ配列を 参照  charArray(終端:1:-1)で要素を反転 print *,charArray(6) !d print *,charArray(3:7) !st.da print *,charArray(8:1:-1) !tad.tset 2019/5/4モダンFortran勉強会.f016
  7. 7. 文字の配列の長所  elementalな関数に配列ごと渡して一括で処理できる   iachar() 文字のASCIIコードを取得する関数  achar() ASCIIコードから対応するASCII文字を取得する関数  配列中の小文字をまとめて大文字に変換する  演算はできないが比較はできる print *,iachar(charArray) !116 101 115 116 46 100 97 116 where('a' <= charArray .and. charArray <= 'z')& charArray = achar(iachar(charArray) & + (iachar('A')-iachar('a'))) print *,charArray !TEST.DAT 2019/5/4モダンFortran勉強会.f017
  8. 8. 文字の配列の長所  再自動割付ができる   代入時に右辺の配列要素数に応じて勝手に割付  一度割り付けられた配列でも,代入時に自動で再割付 character,allocatable :: charArray(:) print *,size(charArray) !0 character,allocatable :: charArray(:) charArray = ["F","o","r","t","r","a","n"] print *,charArray,size(charArray) !Fortran 7 charArray = ["C","/","C","+","+"] print *,charArray,size(charArray) !C/C++ 5 charArray(:) = ["J","a","v","a"] !(:)を書くと再割付を抑制 print *,charArray,size(charArray) !Java+ 5 2019/5/4モダンFortran勉強会.f018
  9. 9. 文字の配列の短所  代入がやりにくい  文字列ではなく,あくまで配列  1文字ずつ代入するか,配列構成子を使う   文字列としては使えない charArray = ["F","o","r","t","r","a","n"] character,allocatable :: charArray(:) charArray = ["F","o","r","t","r","a","n"] print *,charArray,size(charArray) !Fortran 7 charArray = "C/C++" !文字列C/C++を代入 print *,charArray,size(charArray) !CCCCCCC 7 2019/5/4モダンFortran勉強会.f019
  10. 10. 文字列の長所  文字の配列ではなく,文字列として取り扱える  代入が簡単  代入する文字列より文字列長が長ければ末尾にスペース, 足りなければ文字列長で打ち切り   character(3) :: str = "Fortran" print *,str !For character(10) :: str = "Fortran" print *,str !Fortran˽˽˽ 2019/5/4モダンFortran勉強会.f0110
  11. 11. 文字列の長所  再自動割付ができる  定数の宣言が簡単  character(:),allocatable :: str str = "Fortran" print *,str !Fortran str = "C/C++" print *,str !C/C++ character(*),parameter :: str = "Fortran" 2019/5/4モダンFortran勉強会.f0111
  12. 12. 文字列の長所  文字列専用の処理が色々使える  文字列の長さの取得  len(str)  文字列の結合  str1//str2  空白の移動や除去  trim(str) 右端の空白を除去  adjustl(str) 文字列を左に寄せる(先頭の空白を末尾に詰める)  adjustr(str) 文字列を右に寄せる(末尾の空白を先頭に詰める) 2019/5/4モダンFortran勉強会.f0112
  13. 13. 文字列の長所  文字列専用の処理が色々使える  文字列の検索  index(str,word) strからwordを検索する  scan(str,charGrp) strからcharGrpに含まれる文字のいず れかを検索する  verify(str,charGrp) strからcharGrpに含まれない文字を 検索する  文字列の大小比較  lgt(str1,str2) str1の文字コードがstr2より大きいかを判定  lge(str1,str2) str1の文字コードがstr2以上かを判定  llt(str1,str2) str1の文字コードがstr2より小さいかを判定  lle(str1,str2) str1の文字コードがstr2以下かを判定 2019/5/4モダンFortran勉強会.f0113
  14. 14. 文字列の短所  要素の参照がめんどう  str(i:j)でi文字目からj文字目までのサブ文字列を参照  1文字だけでも,範囲指定が必要   str(i:i)でi文字目を参照  間違えたときのエラーメッセージがわかりにくい  str(終端:1:-1)で文字列反転は不可能  エラーメッセージがわかりにくい print *,str(6:6) !d print *,str(3:7) !st.da character(*),parameter :: str = "test.dat" 2019/5/4モダンFortran勉強会.f0114
  15. 15. 文字型配列と文字列の相互変換  文字列も文字型配列も,メモリ上での並びは同じ  相互に変換可能  Fortranの秘密兵器transfer関数 character(:),allocatable :: str character,allocatable :: charArray(:) str = "test.dat" charArray = transfer(str, charArray) print *,charArray,size(charArray) !test.dat 8 !文字の配列を反転して文字列へ変換 str = transfer(charArray(size(charArray):1:-1),str) print *,str,len(str) !tad.tset 8 2019/5/4モダンFortran勉強会.f0115
  16. 16. 混同する理由  まずどっちがどっちかわかってない  文字列と文字型配列に区別がないと思っている  Fortranの混沌とした型宣言が原因の一つ  例えばinteger型の配列(8要素) character(8) :: a character :: a(8) use,intirnsic :: iso_fortran_env,only:int32 integer,dimension(8) :: int integer :: int(8) integer*4,dimension(8) :: int integer(4) :: int(8) integer(int32) :: int(8) !←これが良い 全て同じ (kind=4が4バイトを 意味する環境では) 同じとしか思えない 2019/5/4モダンFortran勉強会.f0116
  17. 17. 混同する理由  区別しなくても問題ない使い方(しかできない/知らない)  文字列専用の関数の存在を知らない  自力で空白を除去する処理を書いている  要素を参照する場合,参照の仕方が違うだけなので,場当たり的に 対処できる  char(i)としてエラーが出たらchar(i:i)にすればいい  配列要素数を固定しているので,柔軟でなくてもよい  レガシー環境では,OSの都合で文字制限が設けられる  ファイル名8文字+拡張子3文字 2019/5/4モダンFortran勉強会.f0117
  18. 18. 混同する理由  関数の型チェックが機能しない場合がある  仮引数の書き方がややこしい  関数に配列を渡すときの仮引数  形状明示配列  形状引継配列  大きさ引継配列  関数に文字列を渡すときの仮引数  形状明示  大きさ引継 2019/5/4モダンFortran勉強会.f0118
  19. 19. 関数を書く位置と呼称の対応  関数を書く場所によって呼称が変わる  内部関数,モジュール関数は型チェックが行われる  グローバル関数はコンパイル時の型チェックがザル program main use :: kernel implicit none contains ここに書くと内部関数(サブルーチン) end program main ここに書くとグローバル関数(サブルーチン) module kernel use :: kernel implicit none contains ここに書くとモジュール関数(サブルーチン) end module kernel 2019/5/4モダンFortran勉強会.f0119
  20. 20. 文字型配列を受け取る関数  大きさ引継  形状引継(配列を渡す時の標準的な書き方)  形状明示 subroutine internalReceiveCharArrayAssumedSize(charArray) implicit none character,intent(in) :: charArray(*) print *,charArray(:8) !使うときに要素数を明記 end subroutine internalReceiveCharArrayAssumedSize subroutine internalReceiveCharArrayAssumedShape(charArray) implicit none character,intent(in) :: charArray(:) print *,charArray end subroutine internalReceiveCharArrayAssumedShape subroutine internalReceiveCharArrayExplicitShape(charArray) implicit none character,intent(in) :: charArray(8) print *,charArray end subroutine internalReceiveCharArrayExplicitShape 2019/5/4モダンFortran勉強会.f0120
  21. 21. 文字列を受け取る関数  大きさ引継(という名称かは不明だが,類似性から)  形状引継  形状明示 subroutine internalReceiveStringAssumedSize(str) implicit none character(*),intent(in) :: str print *,str !使うときに要素数を明記しなくてよい end subroutine internalReceiveStringAssumedSize subroutine internalReceiveStringAssumedShape(str) implicit none character(:),intent(in) :: str print *,str end subroutine internalReceiveStringAssumedShape subroutine internalReceiveStringExplicitShape(str) implicit none character(8),intent(in) :: str print *,str end subroutine internalReceiveStringExplicitShape コンパイルエラー character(:)はallocatableと一緒に使う 2019/5/4モダンFortran勉強会.f0121
  22. 22. 仮引数の型とその書き方の対応 文字型配列 文字列 型宣言 要素数 の明記 型宣言 要素数 の明記 大きさ引継 character :: charArray(*) 要 character(*) :: str 不要 形状引継 character :: charArray(:) 不要 character(:) :: str 形状明示 character :: charArray(8) 不要 character(8) :: str 不要 2019/5/4モダンFortran勉強会.f0122
  23. 23. 仮引数が文字型配列 実引数が文字型配列  大きさ引継   当然OK  形状引継   当然OK  形状明示   当然OK call internalReceiveCharArrayAssumedSize(charArray) call internalReceiveCharArrayAssumedShape(charArray) call internalReceiveCharArrayExplicitShape(charArray) 2019/5/4モダンFortran勉強会.f0123
  24. 24. 仮引数が文字列 実引数が文字列  大きさ引継   当然OK  形状引継  形状明示   当然OK call internalReceiveStringAssumedSize(str) call internalReceiveStringExplicitShape(str) 2019/5/4モダンFortran勉強会.f0124
  25. 25. 仮引数が文字列 実引数が文字型配列  大きさ引継   コンパイル時に型チェックでエラー  形状引継  形状明示   コンパイル時に型チェックでエラー call internalReceiveStringAssumedSize(charArray) call internalReceiveStringExplicitShape(charArray) 2019/5/4モダンFortran勉強会.f0125
  26. 26. 仮引数が文字型配列 実引数が文字列  大きさ引継   なぜかOK  形状引継   コンパイル時に型チェックでエラー  形状明示(レガシーコードでは形状を明示することが多いので,型違いに気づかない)   なぜかOK call internalReceiveCharArrayAssumedSize(str) call internalReceiveCharArrayAssumedShape(str) call internalReceiveCharArrayExplicitShape(str) 2019/5/4モダンFortran勉強会.f0126
  27. 27. 仮引数・実引数の型とコンパイルエラー  内部関数,モジュール関数 文字型配列 文字列 文字型 配列 大きさ引継 ○ ○ 形状引継 ○ × 形状明示 ○ ○ 仮引数 実引数 文字型配列 文字列 文字列 大きさ引継 × ○ 形状明示 × ○ 仮引数 実引数 これがマズい 2019/5/4モダンFortran勉強会.f0127
  28. 28. 仮引数・実引数の型とコンパイルエラー  グローバル関数 文字型配列 文字列 文字型 配列 大きさ引継 ○ ×* 形状引継 ○ × 形状明示 ○ ○ 仮引数 実引数 文字型配列 文字列 文字列 大きさ引継 × ○ 形状明示 × ○ 仮引数 実引数 *関数インタフェース が必要というエラー 2019/5/4モダンFortran勉強会.f0128
  29. 29. 仮引数が文字型配列 実引数が文字列  関数をグローバルに置いた場合   コンパイルエラー  形状引継および大きさ引継配列が仮引数のグローバル関数は インタフェースが必要  FORTRANでは,1ファイルに1サブルーチンのみを書く コーディング規約があった  すべてのサブルーチンがグローバル  形状明示配列ならエラーはでない  仮引数を形状/大きさ引継配列にするとエラーが出る  形状/大きさ引継配列を避け,形状明示配列を使う傾向がある globalReceiveCharArrayAssumedSize(str) 2019/5/4モダンFortran勉強会.f0129
  30. 30. なぜ混同してしまうか  文字列と文字型配列の区別が付いていない  文字列と文字型配列を渡す場合の仮引数の書き方がや やこしい  文字列と文字型配列を混同しても,関数の型チェックが 機能しない場合がある  コーディング規約に起因するエラーが発生する  形状明示配列が全てを解決してくれる 2019/5/4モダンFortran勉強会.f0130
  31. 31. 対策  とりあえずは以下の組合せを使う  機能を最大限利用でき,かつ間違いがない  文字列  型宣言 character(:),allocatable :: str  仮引数 character(*) :: str  文字型配列  型宣言 character,allocatable :: charArray(:)  仮引数 character(:) :: charArray 2019/5/4モダンFortran勉強会.f0131
  32. 32. 対策  文字列と文字型配列があることを知る  仮引数の種類と,エラーが出ない実引数の組合せを知る  関数をグローバルに置かず,内部関数もしくはモジュール を使う  1ファイル1サブルーチンを徹底したいのなら,Fortran2008の サブモジュールを使う  関数自身やモジュールの責務が曖昧=設計がイマイチ  グローバル関数の使用を止める  コンパイル時の型チェックが行われない  型の不一致があってもリンクエラーしか出ない  そんな関数みつからないぞというエラー 2019/5/4モダンFortran勉強会.f0132
  33. 33. 根本的な対策  String型を作ろう!  プロトタイプができたので,gnu,PGI Fortranで動作確認中  そのうち公開します type(String) :: str str = otherStr str = "string" str = charArray str = str//othrStr str = str//"string" str = str//charArray print *,str print '(DT"String"(5))',str String型,文字列,文字の配列を代入, あるいは連結ができる 派生型入出力サブルーチンを定義済み 2019/5/4モダンFortran勉強会.f0133
  34. 34. Type-Bound Procedure  length()  文字列の長さを返す  charAt(番号)  引数で指定した番号の文字を返す  asciiCodeAt(番号)  引数で指定した番号の文字をASCIIコードで返す 2019/5/4モダンFortran勉強会.f0134
  35. 35. Type-Bound Procedure  split(delimiter)  引数で指定した文字列を区切りとして,文字列を分解する  substr(始点,長さ)  文字列の始点から長さ分,サブ文字列として抽出する  substring(始点,終端)  文字列の始点から終端の範囲をサブ文字列として抽出する 2019/5/4モダンFortran勉強会.f0135
  36. 36. Type-Bound Procedure  indexOf(検索文字列)  文字列中に検索文字列があるか,あれば何番目から始まる かを返す  lastIndexOf(検索文字列)  後ろから検索する  replace(検索文字列,置換後の文字列)  検索文字列を置換後の文字列に置換する 2019/5/4モダンFortran勉強会.f0136
  37. 37. Type-Bound Procedure  repeat(繰返し回数)  文字列を指定回数繰り返す  *演算子をオーバーロードしているのでstr*3と書ける  toLowerCase()  大文字アルファベットを小文字に変換する  toUpperCase()  小文字アルファベットを大文字に変換する 2019/5/4モダンFortran勉強会.f0137
  38. 38. Type-Bound Procedure  toInteger()  文字列を整数に変換する  toBinary{32|64}()  文字列を{単|倍}精度実数に変換する  toCharArray()  文字列を文字の配列に変換する  toCString()  文字列をC言語の文字列に変換する 2019/5/4モダンFortran勉強会.f0138
  39. 39. Type-Bound Procedure  trim()  文字列の左右の空白を除去する  trimLeft/trimStart()  文字列の左側の空白を除去する  trimRight/trimEnd()  文字列の右側の空白を除去する 2019/5/4モダンFortran勉強会.f0139

×