Perl Hackers Hub

第53回 Cを用いたPerl拡張入門―Inline::Cで体験してみよう!(3)

この記事を読むのに必要な時間:およそ 4.5 分

(1)こちら⁠2)こちらから。

Perlのスタックを使った引数や戻り値の操作

(3)では,整数値を複数受け取り,その中から最大値,最小値,平均値を求めて返す次のPerlサブルーチンに該当するコードを,Inline::Cを用いて作成します。

my ($min, $max, $avg) = get_score_info(160, 230, 120, 210, 300);
print "min: $min, max: $max, avg: $avg\n";

sub get_score_info {
    my @args = @_; ―(1)
    my $n = @args; ―(2)
    my $min = 0;
    my $max = 0;
    my $sum = 0;
    for (my $i = 0; $i < $n; $i++) {
        $sum += $args[$i]; ―(3)
        if ($i == 0) {
            $min = $args[$i];
            $max = $args[$i];
        } else {
            if ($min > $args[$i]) {
                $min = $args[$i];
            }
            if ($max < $args[$i]) {
                $max = $args[$i];
            }
        }
    }
    my $avg = $sum / $n;
    return ($min, $max, $avg); ―(4)
}

実行結果は次のとおりです。

min: 120, max: 300, avg: 204

上記のコードで注目すべきポイントとして,以下が挙げられます。

  • (1)可変長の引数を受け取ることができる
  • (2)受け取った引数の数を取得する
  • (3)引数の値を1つずつ取得して計算を行う
  • (4)複数値を返す

Perlのサブルーチンは可変長の引数を受け取ることができ,さまざまな値をリストとして返す機能を持っています。これらの操作はPerlの内部ではスタックをベースとして機能しています。

CでPerlの拡張コードを記述する場合,スタックを操作するようなPerlのAPIに触れることになるでしょう。Inline::Cで上記のサブルーチンをCの関数として作成するために,スタック操作について解説していきます。

可変長の引数を受け取る

Inline::C上では可変長の引数であることを,...を用いて表現します。...を用いると引数内で型や変数名を引数へ渡された値と関連付けることができないため,自ら引数のスタックへアクセスして,値の取り出しや型の操作を行わなければなりません。Inline::Cでは引数スタックを操作するためのAPIがCのマクロ注1として提供されています。

それらのマクロを用いて可変長の引数を操作するソースコードは,次のとおりです。

use Inline C;

get_score_info(160, 230, 120, 210, 300);

__DATA__
__C__
void get_score_info(SV* num, ...) {
  int i, n;
  int min, max;
  double sum, avg;

  Inline_Stack_Vars; ―(1)
  n = Inline_Stack_Items; ―(2)
  for (i = 0; i < n; i++) {
    SV* sv = Inline_Stack_Item(i); ―(3)
    int tmp = (int)SvIV(sv); ―(4)
    sum += (double)tmp;
    if (i == 0) {
      min = tmp;
      max = tmp;
    } else {
      if (min > tmp) {
        min = tmp;
      }
      if (max < tmp) {
        max = tmp;
      }
    }
  }
  avg = sum / (double)n;
  printf("min: %d, max: %d, avg: %.0f\n", min, max, avg);
}

ポイントを見ていきましょう。

  • (1)CでPerlのサブルーチンと同様に可変長の引数を受け取るためにスタックを操作する必要があり,このマクロではその準備を行う。ほかのInline_Stack関連のマクロよりも必ず先に宣言しなければならない
  • (2)可変長の引数の数を返すマクロ。ここでは引数を5つ渡しているので5が返ってくる
  • (3)$_[i]に相当するマクロ。このマクロを用いることで引数へ渡した値に該当するインデックスを指定して値を取り出す
  • (4)(3)で取り出したSV*からIVを取り出し,intへキャストしてtmpへ代入する

最後にminmaxavgの値をprintf関数で出力できます。

注1)
Cにおけるマクロとは,ソースコード中の文字列を,あらかじめ定義しておいた規則に従ってコンパイル時に置換する機能のことを指します。

複数の値を返す

先ほどの関数をベースに,今度は複数の値を返す関数を定義してみましょう。

CはPerlと違い,1つの値しか返せない言語です。つまりこの場合でも,Inline_Stack関連のマクロを使用し,Perl側へ複数値を返すためにスタックを操作する必要があります。

use Inline C;

my ($min, $max, $avg) = get_score_info(160, 230, 120, 210, 300);
print "min: $min, max: $max, avg: $avg\n";

__DATA__
__C__
void get_score_info(SV* num, ...) {
  int i, n;
  int min, max;
  double sum, avg;

  Inline_Stack_Vars;
  n = Inline_Stack_Items;
  for (i = 0; i < n; i++) {
    SV* sv = Inline_Stack_Item(i);
    int tmp = (int)SvIV(sv);
    sum += (double)tmp;
    if (i == 0) {
      min = tmp;
      max = tmp;
    } else {
      if (min > tmp) {
        min = tmp;
      }
      if (max < tmp) {
        max = tmp;
      }
    }
  }
  avg = sum / (double)n;
  Inline_Stack_Reset; ―(1)
  Inline_Stack_Push(newSViv((IV)min)); ―(2)
  Inline_Stack_Push(newSViv((IV)max));
  Inline_Stack_Push(newSVuv((UV)avg));
  Inline_Stack_Done; ―(3)
}

ポイントを見ていきましょう。

  • (1)宣言することで引数のために使用していたスタックが,戻り値の操作のために使用可能になる
  • (2)戻り値のスタックへ値をpushする
  • (3)複数戻り値を確定させるとき,つまりこれ以上スタックへpushを行わないことを示すために宣言する必要がある

このコードを実行すると次の結果が得られます。

min: 120, max: 300, avg: 204

今回定義したPerlのサブルーチンと同じ実行結果を,Inline::Cでも得ることができました。つまり,PerlのAPIを用いた処理を行うコードをCでも記述できるようになりました。

著者プロフィール

上川慶(かみかわけい)

1995年,沖縄生まれPerl育ち。2018年に株式会社メルカリへ新卒入社し,現在はメルカリのマイクロサービスを開発している。大好きな言語はPerlとGoであり,特性としてよく沖縄に帰ることが多い。

学生時代は沖縄のPerlユーザーコミュニティであるOkinawa.pmを運営しており,一応今でも中の人の一人。

コメント

コメントの記入