Perlでポインタの学習練習プログラム
ポインタの学習に役立ててください。
gccとcatの使用出来る環境で使ってください。
Linuxが良いと思います。
ptr_lesson.pl
#!/usr/bin/perl # # C言語プログラミング # ポインタの使い方の練習問題を自動生成するPerlスクリプト # # $filename = "ptr_lesson"; # 生成するCプログラムのファイル名 $offset = 0; # 仮想的なメモリアドレスの先頭番地 ###################################################################### # メイン処理 # my $program; srand(time|$$); # 乱数の初期化 open(OUT, ">$filename.c"); do { $program = &program; # 練習問題の作成 } while(&count($program) < 10); # ポインタアクセスの少なすぎる問題は作り直し print OUT $program; # 生成したプログラムをファイルに出力 close(OUT); system("cat $filename.c"); # ソースを画面表示 system("gcc -Wall -ansi -o $filename $filename.c"); # コンパイル print "実行結果を見ますか? [ENTER] "; $key = <>; # ENTERキー入力を待つ system("./$filename"); # 実行 ###################################################################### # 関数定義 # # Cプログラムソースを生成する関数 sub program { my $program = "\n"; my $expression, $previous, $left; &make_variable_name; # 使用できる変数の変数名を定義する &memory_allocate; # 使用できる変数全部にメモリを割り当てる # main関数本体を生成する $previous = ""; for(my $i = 0; $i < 10; $i++) { do { # 左辺が直前の行と同じにはならないようにする $expression = &expression; $expression =~ /([^ ]+) =/; $left = $1; } while($left eq $previous); &exec_expression($expression); # 変数の値を更新 $program .= $expression; #$program .= ✓ # 途中経過をプログラム中にコメントとして挿入 $previous = $left; # 左辺を記憶 } # 右辺の式が定数のみの演算である場合には、1個の定数に変換 $program =~ s/([\*\w]+) = ([\d\+\-\* ]+);/$1." = ".eval($2).";"/ge; # 「+=」の記述に変換可能な箇所は変換 $program =~ s/(\S+) = \1 \+ (\S+);/\1 += \2;/g; $program =~ s/(\S+) = (\S+) \+ \1;/\1 += \2;/g; # 「*=」の記述に変換可能な箇所は変換 $program =~ s/(\S+) = \1 \* (\S+);/\1 *= \2;/g; $program =~ s/(\S+) = (\S+) \* \1;/\1 *= \2;/g; # 「-=」の記述に変換可能な箇所は変換 $program =~ s/(\S+) = \1 \- (\S+);/\1 -= \2;/g; # 「++」や「--」の記述に変換可能な箇所は変換 $program =~ s/(\S+) \+= 1;/\1++;/g; $program =~ s/(\S+) \-= 1;/\1--;/g; $program =~ s/\*(\w+)(\+\+|\-\-);/(*\1)\2;/g; $program .= &output_printf; # printf文を出力する $program .= "\n"; $program .= " return 0;\n"; $program .= "}\n"; # プログラムの先頭部分を追加する $program = &declaration($program).$program; # 変数宣言を生成する $program = "#include <stdio.h>\nint main()\n{\n".$program; # 定型記述を追加する return $program; } # 使用できる変数名・演算子を定義する関数 sub make_variable_name { my $type = &rnd(0, 3); if($type == 0) { @val = ('a', 'b', 'c'); @ptr = ('p', 'q', 'r'); } elsif($type == 1) { @val = ('x', 'y', 'z'); @ptr = ('p', 'q', 'r'); } elsif($type == 2) { @val = ('x', 'y', 'z'); @ptr = ('ptr1', 'ptr2', 'ptr3'); } else { @val = ('a', 'c', 'e'); @ptr = ('b', 'd', 'f'); } @operator = ('+', '-', '*'); } # 変数宣言を生成する関数 sub declaration { my $program = $_[0]; # 値の代入はされていないがアドレス参照されている # 値変数を見つけるために、プログラム記述本体を使用する my $variable; # 値変数の宣言を生成 my $declaration = " int "; foreach $variable (sort @val) { # 実際に使用されている変数だけ変数宣言を行なう if($memory[$address{$variable}] ne "X" || $program =~ /&$variable/) { $declaration .= $variable.", "; } } $declaration =~ s/, $//; # 余分な最後の「, 」を除去 $declaration .= ";\n"; $declaration =~ s/ int ;\n//; # 万一、値変数が1つも無ければ、文全体を消去 # ポインタ変数の宣言を生成 foreach $variable (sort @ptr) { # 実際に使用されている変数だけ変数宣言を行なう if($memory[$address{$variable}] ne "X") { $declaration .= " int* ".$variable.";\n"; } } return $declaration; } # printf文を出力する関数 sub output_printf { my $variable; my $printfs = ""; # 値変数のprintf文 foreach $variable (sort @val) { if($memory[$address{$variable}] ne "X") { $printfs .= " printf(\"$variable = %d\\n\", $variable);\n"; } } # ポインタ変数のprintf文 foreach $variable (sort @ptr) { if($memory[$address{$variable}] ne "X" && $memory[$memory[$address{$variable}]] ne "X") { $printfs .= " printf(\"*$variable = %d\\n\", *$variable);\n"; } } return $printfs; } # プログラム中のポインタアクセスの数を数える関数 sub count { my $program = $_[0]; my $count = $program =~ s/[\&\*][a-z]\w*//g; # ポインタアクセスの数をカウント return $count; } # 代入式を生成する関数 sub expression { my $expression, $type; if(&exist_defined_val == 1) { # 既に値の設定されている変数が存在するならば $type = &rnd(0, 2); # 「++」「+=」などの記述も選択肢に含める } else { $type = &rnd(0, 1); } if($type == 0) { # ポインタ代入式を生成 $expression = &expression_ptr; } elsif($type == 1) { # 値代入式を生成 $expression = &expression_val; } else { # 特殊な形の値代入式を生成 $expression = &expression_vals; } return $expression; } # 特殊な形の値代入式を生成する関数 sub expression_vals { my $left, $right, $item, $op, $value; do { $left = &item_val_left_and_right; # 左辺にも右辺にも使用できる項 $op = &operator; # 演算子を生成 if(&rnd(0, 2) == 0) { # 確率約3分の1で「++」「--」の記述を生成する $item = "1"; } else { # それ以外の場合は、一般の右辺の項 $item = &item_val_right; } $right = $left." ".$op." ".$item; # 右辺 $value = &calc_expression($right); # 右辺の計算を実行し、結果を$valueに格納 } while($value > 100 || $value < -100 || $right =~ /\* 1$/ || $right =~ /\* 1 / || $right =~ /(\S+) .*\- \1$/ || $right =~ /\- (\S+) .*\+ \1$/); # 数値の絶対値が大きくなりすぎる式、1との乗算、同じもの同士の減算は不許可 return " ".$left." = ".$right.";\n"; } # 値代入式を生成する関数 sub expression_val { my $left, $right, $item, $op, $value, $num, $i, $r; do { $left = &item_val_left; # 左辺 $r = &rnd(0, 29); # 右辺の項数を決定するための乱数 if($r < 1) { # 項数が少なくなる傾向にあるのを補正する $num = 1; } elsif($r < 8) { $num = 2; } else { $num = 3; } $right = ""; # 右辺 for($i = 1; $i <= $num; $i++) { # num個の右辺項を生成 $item = &item_val_right; $right .= $item; if($i < $num) { $op = &operator; # 演算子を生成 $right .= " ".$op." "; } } $value = &calc_expression($right); # 右辺の計算を実行し、結果を$valueに格納 } while($left eq $right || $value > 100 || $value < -100 || $right =~ /\* 1$/ || $right =~ /\* 1 / || $right =~ / 1 \*/ || $right =~ /^1 \*/ || $right =~ /(\S+) .*\- \1$/ || $right =~ /(\S+) .*\- \1 / || $right =~ /\- (\S+) .*\+ \1$/ || $right =~ /\- (\S+) .*\+ \1 /); # 左辺と右辺が同じ式、数値の絶対値が大きくなりすぎる式、 # 1との乗算、同じもの同士の減算(の可能性がある記述)は不許可 return " ".$left." = ".$right.";\n"; } # ポインタ代入式を生成する関数 sub expression_ptr { my $left, $right; do { $left = &item_ptr_left; # 左辺 $right = &item_ptr_right; # 右辺 } while($left eq $right); return " ".$left." = ".$right.";\n"; } # 値代入式の左辺の項を生成する関数 sub item_val_left { my $item, $type; do { $type = &rnd(0, 2); if($type == 0) { $item = &val; } else { $item = "*".&ptr; } } while($item =~ /^\*([a-z]\w*)$/ && $memory[$address{$1}] eq "X"); # 「*ポインタ変数」に代入するときは、ポインタ変数に値が設定されている必要がある return $item; } # 値代入式の右辺の項を生成する関数 sub item_val_right { my $item, $type; do { $type = &rnd(0, 4); if($type == 0) { $item = &const; } elsif($type == 1) { $item = &val; } else { $item = "*".&ptr; } } while(&defined($item) == 0); # 右辺の値は“定義値”である必要がある return $item; } # 値代入式の左辺にも右辺にも使用できる項を生成する関数 sub item_val_left_and_right { my $item, $type; do { $type = &rnd(0, 1); if($type == 0) { $item = &val; } else { $item = "*".&ptr; } } while(&defined($item) == 0); # 値が設定されている変数である必要がある return $item; } # ポインタ代入式の左辺の項を生成する関数 sub item_ptr_left { my $item; $item = &ptr; # ポインタ代入式の左辺になるのはポインタ変数のみ return $item; } # ポインタ代入式の右辺の項を生成する関数 sub item_ptr_right { my $item, $type; do { $type = &rnd(0, 1); if($type == 0) { $item = &ptr; } else { $item = "&".&val; } } while(&defined($item) == 0); # 右辺の値は“定義値”である必要がある return $item; } # ランダムな定数を生成する関数 sub const { return &rnd(1, 10); # プログラム中に登場させる定数は1~10の範囲とする } # ランダムな値変数を生成する関数 sub val { my $val = &rnd(0, @val - 1); return "$val[$val]"; } # ランダムなポインタ変数を生成する関数 sub ptr { my $ptr = &rnd(0, @ptr - 1); return "$ptr[$ptr]"; } # ランダムな演算子を生成する関数 sub operator { my $operator = &rnd(0, @operator - 1); return "$operator[$operator]"; } # 変数のメモリ割り当てを行なう関数 sub memory_allocate { my $address = $offset; # メモリアドレスを初期値に設定 my $variable; %address = (); # アドレス変換テーブルを初期化 @memory = (); # (仮想的な)メモリを初期化 foreach $variable (sort (@val, @ptr)) { # 全変数のメモリ割り当てを行なう $address{$variable} = $address; # アドレス変換テーブルに登録 $memory[$address] = "X"; # そのアドレスのメモリを“未定義値”に $address++; # 割り当て用メモリアドレスを次の番地に } } # 代入式を実行する関数 sub exec_expression { $_[0] =~ /\s*([\*\w]+) = ([^;]+);/; # 式を左辺と右辺に分離する my $left = $1; # 左辺 my $right = $2; # 右辺 if($left =~ /^[a-z]\w*$/) { # 変数への代入 # 左辺の変数に右辺の計算結果を格納する $memory[$address{$left}] = &calc_expression($right); } elsif($left =~ /^\*([a-z]\w*)$/) {# ポインタ参照先への代入 # 左辺の参照先に右辺の計算結果を格納する $memory[$memory[$address{$1}]] = &calc_expression($right); } else { print "Error: function \"calc_expression\"\n"; die; } } # 式の値を求める関数 sub calc_expression { my $expression = $_[0]; $expression =~ s/([\*\&]?[a-z]\w*|\d+)/&calc_item($1)/ge; # 右辺の式中の変数を値に置換 return eval($expression); # 右辺の計算を実行 } # 項の値を求める関数 sub calc_item { if($_[0] =~ /^(\d+)$/) { # 数値定数は、その数値そのもの return $1; } elsif($_[0] =~ /^([a-z]\w*)$/) { # 「変数」は、それに格納されている値 return $memory[$address{$1}]; } elsif($_[0] =~ /^\*([a-z]\w*)$/) { # 「*変数」は、その参照先に格納されている値 return $memory[$memory[$address{$1}]]; } elsif($_[0] =~ /^&([a-z]\w*)$/) { # 「&変数」は、その変数のアドレス return $address{$1}; } else { print "Error: function \"value\"\n"; die; } } # “定義値”か“未定義値”かを判定する関数 # ※定義値 = 数値 # 未定義値 = "X" # # [引数] 数値定数 または 変数 または &変数 または *変数 # [返戻値] 1:定義値 0:未定義値 sub defined { if($_[0] =~ /^\d+$/) { # 数値定数 return 1; # 常に“定義値” } elsif($_[0] =~ /^([a-z]\w*)$/) { # 変数 if($memory[$address{$1}] ne "X") { return 1; } else { return 0; } } elsif($_[0] =~ /^&[a-z]\w*$/) { # &変数 return 1; # 常に“定義値” } elsif($_[0] =~ /^\*([a-z]\w*)$/) { # *変数 if($memory[$address{$1}] eq "X") { # そのポインタ変数自体が未定義 return 0; } elsif($memory[$memory[$address{$1}]] eq "X") { # そのポインタ変数が指している先の値が未定義 return 0; } else { return 1; } } else { print "Error: function \"defined\"\n"; die; } } # 既に値の設定されている変数が存在するかどうかを調べる関数 sub exist_defined_val { my $flag = 0; my $variable; foreach $variable (@val) { if(&defined($variable) == 1) { $flag = 1; } } return $flag; } # x以上y以下の範囲のランダムな整数を生成する関数 # # [引数] 第1引数: x # 第2引数: y # [返戻値] x以上y以下の範囲のランダムな整数 sub rnd { return int(rand() * ($_[1] - $_[0] + 1)) + $_[0]; } # 変数名・メモリアドレス・そのアドレスの内容の # 一覧を表示する関数 (デバッグor学習用) sub check { my $check_result = " /* "; foreach my $variable (sort keys %address) { $check_result .= $variable."="; $check_result .= "[".$address{$variable}."]="; $check_result .= $memory[$address{$variable}]." "; } #$check_result .= "\n "; #my $size = @memory; #for(my $address = $offset; $address < $offset + $size; $address++) { #$check_result .= "[".$address."]=".$memory[$address]." "; #} $check_result .= " */\n\n"; return $check_result; }
実行結果例