Prolog:3x3の魔法陣の解を求める
gprologのFDの練習として、SPINで以前に書いた3x3の魔法陣の解を求めてみます。gprologのソースファイルにはサンプルが含まれています。その中にmagsq.plがあり、魔法陣のサイズを指定できるものがあります。
ソース ms.pl
/* 魔法陣 */ ms(MS) :- MS = [ V11, V12, V13, V21, V22, V23, V31, V32, V33 ], /* 各要素は1〜9である。*/ fd_domain(MS, 1, 9), /* 各要素の値は異なる。*/ fd_all_different(MS), /* 行の和は15である。*/ V11 + V12 + V13 #= 15, V21 + V22 + V23 #= 15, V31 + V32 + V33 #= 15, /* 列の和は15である。*/ V11 + V21 + V31 #= 15, V12 + V22 + V32 #= 15, V13 + V23 + V33 #= 15, /* 対角の和は15である。*/ V11 + V22 + V33 #= 15, V31 + V22 + V13 #= 15, fd_labeling(MS). /* 出力用の述語 */ pp([V11, V12, V13, V21, V22, V23, V31, V32, V33]) :- write(V11), write(' '), write(V12), write(' '), write(V13), nl, write(V21), write(' '), write(V22), write(' '), write(V23), nl, write(V31), write(' '), write(V32), write(' '), write(V33), nl.
実行
$ gprolog GNU Prolog 1.4.5 (64 bits) Compiled Mar 17 2020, 23:55:22 with gcc By Daniel Diaz Copyright (C) 1999-2020 Daniel Diaz | ?- ['ms.pl']. : | ?- ms(Ans),pp(Ans). 2 7 6 9 5 1 4 3 8 Ans = [2,7,6,9,5,1,4,3,8] ? ; 2 9 4 7 5 3 6 1 8 Ans = [2,9,4,7,5,3,6,1,8] ? ; 4 3 8 9 5 1 2 7 6 Ans = [4,3,8,9,5,1,2,7,6] ? ; 4 9 2 3 5 7 8 1 6 Ans = [4,9,2,3,5,7,8,1,6] ? ; 6 1 8 7 5 3 2 9 4 Ans = [6,1,8,7,5,3,2,9,4] ? ; 6 7 2 1 5 9 8 3 4 Ans = [6,7,2,1,5,9,8,3,4] ? ; 8 1 6 3 5 7 4 9 2 Ans = [8,1,6,3,5,7,4,9,2] ? ; 8 3 4 1 5 9 6 7 2 Ans = [8,3,4,1,5,9,6,7,2] ? ; (1 ms) no
Prolog:1〜9の2つの整数で和が10になる組み合わせを求める
gprologのマニュアルを見ていると「9 Finite domain solver and built-in predicates」という章がありました。「9.1 introduction」にも「constraint solver」という記述があるので、何らかの制約問題を解いてくれるのかと思い試して見ました。
簡単そうなサンプルとして、以下のような問題を考えてみました。
- A,B が1〜9の範囲の整数をとる。
- A+Bが10となる組み合わせを考える。
- A>=Bとする。
gplorgの起動
$ gprolog GNU Prolog 1.4.5 (64 bits) Compiled Mar 17 2020, 23:55:22 with gcc By Daniel Diaz Copyright (C) 1999-2020 Daniel Diaz | ?-
1〜9の範囲のA,Bを用意
| ?- fd_domain([A,B],1,9). A = _#0(1..9) B = _#17(1..9) yes
それぞれに値を代入してみる
| ?- fd_domain([A,B],1,9), fd_labeling([A,B]). A = 1 B = 1 ? ; A = 1 B = 2 ? ; A = 1 B = 3 ? yes
組み合わせがどんどん出てきます。上記の例では止めていますが、A=9,B=9の組み合わせまで出てきます。
A+Bが10となる制約
| ?- fd_domain([A,B],1,9), A + B #= 10, fd_labeling([A,B]). A = 1 B = 9 ? a A = 2 B = 8 A = 3 B = 7 A = 4 B = 6 A = 5 B = 5 A = 6 B = 4 A = 7 B = 3 A = 8 B = 2 A = 9 B = 1 (1 ms) yes
A + B = 10 となる組み合わせが出てきますが、3+7=10, 7+3=10 のような組み合わせを省くため、大小関係の制約を追加します。
大小関係の制約 A >= Bの追加
| ?- fd_domain([A,B],1,9), A + B #= 10, A #>= B, fd_labeling([A,B]). A = 5 B = 5 ? ; A = 6 B = 4 ? ; A = 7 B = 3 ? ; A = 8 B = 2 ? ; A = 9 B = 1 (1 ms) yes
Prolog:アローダイアグラム クリティカルパスを求める問題(基本情報処理技術者試験)を解いてみる
基本情報技術者試験で出てくるアローダイヤグラムでクリティカルパスを求める問題ですが、prologを使うとスッキリかけるのではないかと思い挑戦してみました。使用したprologの処理系はgprologとなります。
問題は以下のようなものでした。
問題の情報をprologで表現したものが以下となります。
ブランチ情報
% ブランチ情報 % branchInfo(ブランチ名, 元ノード, 先ノード, コスト). branchInfo(a, n1, n2, 1). branchInfo(b, n1, n3, 5). branchInfo(c, n2, n3, 3). branchInfo(d, n2, n4, 3). branchInfo(e, n3, n5, 4). branchInfo(dmy, n5, n4, 0). branchInfo(f, n5, n6, 5). branchInfo(g, n4, n6, 4).
ブランチ情報からパス情報を得る述語を次のように定義しました。
パス
% パス %path(元ノード, 先ノード, [ブランチ情報]). path(StartNode, EndNode, [branchInfo(PathName, StartNode, EndNode, Weight)]) :- branchInfo(PathName, StartNode, EndNode, Weight). path(StartNode, EndNode, [branchInfo(PathName, StartNode, NextStart, Weight)|T]) :- branchInfo(PathName, StartNode, NextStart, Weight), path(NextStart, EndNode, T).
これでStartNodeからEndNodeに至るブランチのリストが得られます。
例としてn1からn3のパスを確認してみます。
パスの確認(n1->n3)
| ?- path(n1,n3,P). P = [branchInfo(b,n1,n3,5)] ? ; P = [branchInfo(a,n1,n2,1),branchInfo(c,n2,n3,3)] ? ; no
続いてブランチの名前とコストを集める述語を定義しました。
% pathCost(元ノード, 先ノード, result(パス, コスト)). pathCost(StartNode, EndNode, result(Rout, Cost)) :- % パスを得る。 path(StartNode, EndNode, R), % パスの経路名を集める。 maplist(branchName, R, Rout), % パスのコストを得る。 maplist(weight, R, Ws), sum_list(Ws, Cost). branchName(branchInfo(Name,_,_,_), Name). weight(branchInfo(_, _, _, Weight), Weight).
prologならではなのですが、pathCost(n1,n6,R)で問い合わせて「;」や「a」でn1からn6に至る各ルートのコストが得られるのでほぼ、答えは得ているような感じになりました。
パスのコスト(n1->n6)
| ?- pathCost(n1,n6,R). R = result([a,c,e,f],13) ? ; R = result([a,c,e,dmy,g],12) ? a R = result([a,d,g],8) R = result([b,e,f],14) R = result([b,e,dmy,g],13) no
クリティカルパスを求める述語を定義します。
クリティカルパス
% クリティカルパスを得る。 criticalPath(StartNode, EndNode, CriticalPath) :- % 各ルートとコスト情報を得る。 findall(D,pathCost(StartNode,EndNode,D),Ds), % 最大コストの場所を得る。 maplist(cost,Ds,Cs), max_list(Cs,Max), nth(Pos,Cs,Max), % クリティカルパスの情報を得る。 nth(Pos,Ds,CriticalPath). cost(result(_,C), C).
クリティカルパス(n1->n6)
クリティカルパスを問い合わせてみます。
| ?- criticalPath(n1,n6,Cp). Cp = result([b,e,f],14) ? ; no
一応全体は以下のようになります。
コード全体
% ブランチ情報 % branchInfo(ブランチ名, 元ノード, 先ノード, コスト). branchInfo(a, n1, n2, 1). branchInfo(b, n1, n3, 5). branchInfo(c, n2, n3, 3). branchInfo(d, n2, n4, 3). branchInfo(e, n3, n5, 4). branchInfo(dmy, n5, n4, 0). branchInfo(f, n5, n6, 5). branchInfo(g, n4, n6, 4). % パス %path(元ノード, 先ノード, [パス情報]). path(StartNode, EndNode, [branchInfo(PathName, StartNode, EndNode, Weight)]) :- branchInfo(PathName, StartNode, EndNode, Weight). path(StartNode, EndNode, [branchInfo(PathName, StartNode, NextStart, Weight)|T]) :- branchInfo(PathName, StartNode, NextStart, Weight), path(NextStart, EndNode, T). % pathCost(元ノード, 先ノード, result([ブランチ名], コスト)). pathCost(StartNode, EndNode, result(BranchNames, Cost)) :- % ルートを得る。 path(StartNode, EndNode, R), % ルートの経路名を集める。 maplist(branchName, R, BranchNames), % ルートのコストを得る。 maplist(weight, R, Ws), sum_list(Ws, Cost). branchName(branchInfo(Name,_,_,_), Name). weight(branchInfo(_, _, _, Weight), Weight). % クリティカルパスを得る。 criticalPath(StartNode, EndNode, CriticalPath) :- % 各ルートとコスト情報を得る。 findall(D,pathCost(StartNode,EndNode,D),Ds), % 最大コストの場所を得る。 maplist(cost,Ds,Cs), max_list(Cs,Max), nth(Pos,Cs,Max), % クリティカルパスの情報を得る。 nth(Pos,Ds,CriticalPath). cost(result(_,C), C).
SPIN:1信号(赤青黃)の遷移サンプル
「SPINによる設計モデル検証」のNever Claimの2信号サンプルがあるのですが、自身の勉強としてもっと簡単なサンプルから作成してみました。モデルとしては、赤から青になり、青から黄、黄から赤になる信号のモデルと、Never Claimを記述します。
記述したファイルは以下となります。
signal3.pml
/* 信号の色 */ mtype = { BLUE, YELLOW, RED } mtype light = RED; active proctype signal() { do :: atomic { light == RED -> if :: light = BLUE; :: light = RED; fi } :: atomic { light == BLUE -> if :: light = YELLOW; :: light = BLUE; /* バグを仕込むと検出される。*/ //:: light = RED; fi } :: atomic { light == YELLOW -> if :: light = RED :: light = YELLOW; fi } od } never n01 { /* 信号:赤からの遷移 */ LIGHT_RED: if :: light == RED -> goto LIGHT_RED :: light == BLUE -> goto LIGHT_BLUE :: else -> goto accept fi /* 信号:青からの遷移 */ LIGHT_BLUE: if :: light == BLUE -> goto LIGHT_BLUE :: light == YELLOW -> goto LIGHT_YELLOW :: else -> goto accept fi /* 信号:黃からの遷移 */ LIGHT_YELLOW: if :: light == YELLOW -> goto LIGHT_YELLOW :: light == RED -> goto LIGHT_RED :: else -> goto accept fi accept: skip; goto accept }
検証器の作成と実行
$ spin -a -run -a signal3.pml warning: for p.o. reduction to be valid the never claim must be stutter-invariant (never claims generated from LTL formulae are stutter-invariant) (Spin Version 6.5.0 -- 17 July 2019) + Partial Order Reduction Full statespace search for: never claim + (n01) assertion violations + (if within scope of claim) acceptance cycles + (fairness disabled) invalid end states - (disabled by never claim) State-vector 20 byte, depth reached 11, errors: 0 6 states, stored 7 states, matched 13 transitions (= stored+matched) 6 atomic steps hash conflicts: 0 (resolved) Stats on memory usage (in Megabytes): 0.000 equivalent memory usage for states (stored*(State-vector + overhead)) 0.287 actual memory usage for states 128.000 memory used for hash table (-w24) 0.534 memory used for DFS stack (-m10000) 128.730 total actual memory usage unreached in proctype signal signal3.pml:32, state 22, "-end-" (1 of 22 states) unreached in claim n01 signal3.pml:63, state 27, "-end-" (1 of 27 states) pan: elapsed time 0 seconds
バグを仕込む
signal3.pmlのコメントアウトしている「//:: light = RED;」をコメント解除すると、エラーが検出されてきます。
$ spin -a -run -a signal3.pml warning: for p.o. reduction to be valid the never claim must be stutter-invariant (never claims generated from LTL formulae are stutter-invariant) pan:1: acceptance cycle (at depth 12) pan: wrote signal3.pml.trail (Spin Version 6.5.0 -- 17 July 2019) Warning: Search not completed + Partial Order Reduction Full statespace search for: never claim + (n01) assertion violations + (if within scope of claim) acceptance cycles + (fairness disabled) invalid end states - (disabled by never claim) State-vector 20 byte, depth reached 20, errors: 1 10 states, stored 6 states, matched 16 transitions (= stored+matched) 10 atomic steps hash conflicts: 0 (resolved) Stats on memory usage (in Megabytes): 0.000 equivalent memory usage for states (stored*(State-vector + overhead)) 0.286 actual memory usage for states 128.000 memory used for hash table (-w24) 0.534 memory used for DFS stack (-m10000) 128.730 total actual memory usage pan: elapsed time 0 seconds
trailの確認
信号が赤(初期値)から青、赤と遷移した際にNever claimのaccetpにジャンプすることになり、最終的にエラーとして検出されています。
$ spin -t -p -g signal3.pml starting claim 1 Never claim moves to line 39 [((light==RED))] 2: proc 0 (signal:1) signal3.pml:9 (state 1) [((light==RED))] 3: proc 0 (signal:1) signal3.pml:11 (state 2) [light = BLUE] light = BLUE Never claim moves to line 40 [((light==BLUE))] 5: proc 0 (signal:1) signal3.pml:16 (state 7) [((light==BLUE))] 6: proc 0 (signal:1) signal3.pml:19 (state 9) [light = BLUE] Never claim moves to line 47 [((light==BLUE))] 8: proc 0 (signal:1) signal3.pml:16 (state 7) [((light==BLUE))] 9: proc 0 (signal:1) signal3.pml:21 (state 10) [light = RED] light = RED Never claim moves to line 49 [else] 11: proc 0 (signal:1) signal3.pml:9 (state 1) [((light==RED))] 12: proc 0 (signal:1) signal3.pml:11 (state 2) [light = BLUE] light = BLUE <<<<<START OF CYCLE>>>>> Never claim moves to line 61 [(1)] 14: proc 0 (signal:1) signal3.pml:16 (state 7) [((light==BLUE))] 15: proc 0 (signal:1) signal3.pml:18 (state 8) [light = YELLOW] light = YELLOW 17: proc 0 (signal:1) signal3.pml:25 (state 14) [((light==YELLOW))] 18: proc 0 (signal:1) signal3.pml:27 (state 15) [light = RED] light = RED 20: proc 0 (signal:1) signal3.pml:9 (state 1) [((light==RED))] 21: proc 0 (signal:1) signal3.pml:11 (state 2) [light = BLUE] light = BLUE spin: trail ends after 21 steps #processes: 1 light = BLUE 21: proc 0 (signal:1) signal3.pml:7 (state 20) 21: proc - (n01:1) signal3.pml:61 (state 25) 1 processes created
GCC:関数重複定義(Multiple Definition)を-z muldefsで許可する
同僚の方が一時的な確認のために同名の関数を静的リンクした実行ファイルを作成しようとしてエラーになっていました。C言語ではできないんじゃないかな。と思っていたのですが、「HPではできたのに〜」と言っていたのを聞き、ならばGCCでもオプションでどうにかなるのではと思って調べてみました。
結果、リンクオプション -z muldefs を利用することで可能でした。
使用したファイルは以下となります。
func01.c
#include "func.h" int func(int x) { return x + 10; }
func02.c
#include "func.h" int func(int x) { return x + 100; }
main.c
#include <stdio.h> #include "func.h" int main(void) { int x = func(10); printf("func(10) = %d\n", x); return 0; }
通常のコンパイル
普通にコンパイルしてみます。
$ gcc -Wall main.c func01.c func02.c /tmp/ccKwhV9A.o: 関数 `func' 内: func02.c:(.text+0x0): `func' が重複して定義されています /tmp/ccGuOvnL.o:func01.c:(.text+0x0): ここで最初に定義されています collect2: error: ld returned 1 exit status
funcが重複してリンクが失敗しています。
マニュアルにあたる
ldのマニュアルを見ると -zオプションのキーワードを指定すると重複定義を許可してくれそうです。(ld --helpでも見つけられました。)
$ man ld : -z keyword The recognized keywords are: : muldefs Allow multiple definitions.
リンクオプション -z muldefsを試してみる
リンク時のエラーが解消されています。また、今回試した例では、左側の定義が有効になっているようです。
$ gcc -Wall -z muldefs main.c func01.c func02.c $ ./a.out func(10) = 20 $ gcc -Wall -z muldefs main.c func02.c func01.c $ ./a.out func(10) = 110
C言語:GDBによるデバッグ
0からnまでの整数の和を求める例を題材にデバッガ(gdb)の使用例を紹介したいと思います。バグがある状態から始めます。
main.c(バグ有り)
int sum(int n) { int acc = 0; int i; for (i = 0; i < n; i++) { acc += i; } return acc; } int main(void) { int n = 10; int wa = sum(n); return wa; }
GDB配下でのプログラム実行
gdbは起動中のプログラムにアタッチす方法も有りますが、今回はgdbの引数にプログラムを指定して起動します。
$ gdb -q main Reading symbols from main...done. (gdb) run Startping program: /home/makoto/blog/22_Cdebug/main [Inferior 1 (process 4924) exited with code 055] (gdb) p $_exitcode $1 = 45
1から10までの和なので55になってほしいのですが、45になっています。
ステップ実行していく
ひとまず関数のトップであるmainから調べていくことにします。
mainで止まるようにブレークポイントを設定します。
(gdb) break main Breakpoint 1 at 0x555555554630: file main.c, line 15. (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x0000555555554630 in main at main.c:15 breakpoint already hit 1 time
main.cの15行目にブレークポイントが設定されています。再度プログラム実行します。
(gdb) run Starting program: /home/makoto/blog/22_Cdebug/main
main関数でプログラムが止まります。
Breakpoint 1, main () at main.c:15 15 int n = 10;
まだ、「int n = 10」は実行されていません。試しに変数nの値を調べてみます。
(gdb) print n $2 = 0
1文進めます。($Xの部分は気にしなくて大丈夫です。)
(gdb) next 16 int wa = sum(n);
今度はnが10で初期化されているはずです。
(gdb) print n $3 = 10
もう1文進めます。
(gdb) next 18 return wa; (gdb) print wa $4 = 45
結果が45になっています。sumが悪そうです。mainのブレークポイントを削除して、今度はsumに設定してみます。
(gdb) delete 1 (gdb) break sum Breakpoint 2 at 0x555555554601: file main.c, line 3. (gdb) info breakpoints Num Type Disp Enb Address What 2 breakpoint keep y 0x0000555555554601 in sum at main.c:3
再度最初から実行します。
(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/makoto/blog/22_Cdebug/main Breakpoint 2, sum (n=10) at main.c:3 3 int acc = 0;
今度はsum(main.cの3行目)で止まります。1文実行してみます。
(gdb) next 6 for (i = 0; i < n; i++) (gdb) print acc $8 = 0
accが0で初期化されています。for文のなかまで進めてみます。
(gdb) next 8 acc += i;
最初のループを終え、行が戻ります。
(gdb) next 6 for (i = 0; i < n; i++)
ここで各種値を確認してみます。
(gdb) print acc $5 = 0 (gdb) print i $6 = 0
このタイミングではまだiはインクリメントされていません。継続条件も確認してみます。
(gdb) print i < n $3 = 1
成立しています。もう一文進めてみます。
(gdb) next 8 acc += i;
i++が実行されているはずです。
(gdb) print i $5 = 1
iが1なので今度はaccに1が加えられるはずです。
(gdb) next 6 for (i = 0; i < n; i++) (gdb) print acc $6 = 1
加えられています。ループを一気に進めてみます。
(gdb) until 10 return acc; (gdb) print acc $7 = 45
accが45となっていて10足りません。この時のiと各種値を見てみます。
(gdb) print i $10 = 10 (gdb) print n $11 = 10 (gdb) print i < n $12 = 0
iは10になっていますが、i < n が成立せずにループを終了していそうです。もう少し詳しく見てみます。再度実行します。
(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/makoto/blog/22_Cdebug/main Breakpoint 2, sum (n=10) at main.c:3 3 int acc = 0;
直前のiが9の部分で止まるようにして、そこから詳しく見てみたいと思います。
(gdb) watch i == 9 Hardware watchpoint 3: i == 9
実行を進めます。
(gdb) continue Continuing. Hardware watchpoint 3: i == 9 Old value = 0 New value = 1 0x000055555555461b in sum (n=10) at main.c:6 6 for (i = 0; i < n; i++)
値を確認します。
(gdb) print i $10 = 9 (gdb) print acc $11 = 36
i++が実行された直後ですね。
(gdb) next 8 acc += i; (gdb) next 6 for (i = 0; i < n; i++) (gdb) print acc $13 = 45
36に9が加えられて45になっています。更に実行を継続してみます。
(gdb) continue Continuing. Hardware watchpoint 7: i == 9 Old value = 1 New value = 0 0x000055555555461b in sum (n=10) at main.c:6 6 for (i = 0; i < n; i++) (gdb) print i $29 = 10 (gdb) print n $30 = 10 (gdb) print i < n $31 = 0
i == 9の条件が破れたところで再度止まってくれました。各種値を確認してみると、i,nが10となりi < nの継続条件が成り立っていません。C言語からはインクリメントと継続条件のどちらが先か見えませんが、このような場合は逆アセンブルしてみると見えてきます。(私はアセンブラの理解が怪しいので誤っていたらすみません。)
(gdb) disassemble Dump of assembler code for function sum: 0x00005555555545fa <+0>: push %rbp 0x00005555555545fb <+1>: mov %rsp,%rbp 0x00005555555545fe <+4>: mov %edi,-0x14(%rbp) 0x0000555555554601 <+7>: movl $0x0,-0x8(%rbp) 0x0000555555554608 <+14>: movl $0x0,-0x4(%rbp) 0x000055555555460f <+21>: jmp 0x55555555461b <sum+33> 0x0000555555554611 <+23>: mov -0x4(%rbp),%eax 0x0000555555554614 <+26>: add %eax,-0x8(%rbp) 0x0000555555554617 <+29>: addl $0x1,-0x4(%rbp) => 0x000055555555461b <+33>: mov -0x4(%rbp),%eax 0x000055555555461e <+36>: cmp -0x14(%rbp),%eax 0x0000555555554621 <+39>: jl 0x555555554611 <sum+23> 0x0000555555554623 <+41>: mov -0x8(%rbp),%eax 0x0000555555554626 <+44>: pop %rbp 0x0000555555554627 <+45>: retq End of assembler dump.
+29でインクリメントされていて+36の部分で継続条件を判定しているようです。
rbp-0x4の場所が変数iの場所ですね。
(gdb) print *(int *)($rbp-0x4) $32 = 10
nが10の場合はaccに加えたいので、継続条件をi <= nに修正することにします。
int sum(int n) { int acc = 0; int i; for (i = 0; i <= n; i++) { acc += i; } return acc; }
コンパイルして再度実行してみます。(コンパイルば別のターミナルから実行してgdbは別ターミナルで実行したままにしています。)
(gdb) run Starting program: /home/makoto/blog/22_Cdebug/main warning: Probes-based dynamic linker interface failed. Reverting to original interface. Breakpoint 2, sum (n=10) at main.c:3 3 int acc = 0; (gdb) until 6 for (i = 0; i <= n; i++) (gdb) 8 acc += i; (gdb) 6 for (i = 0; i <= n; i++) (gdb) 10 return acc; (gdb) print acc $33 = 55
うまく行っていそうです。
C言語:型に性質をもたせる?(コンパイル時の警告まで)
[増補改訂]関数プログラミング実践入門 ──簡潔で、正しいコードを書くために (WEB+DB PRESS plus)の「1.5 型に性質をもたせる HTMLの文字列エスケープ」を読んで、C言語の場合はどんな風にできるのかと思い実装してみました。おそらくコンパイル時のチェックで警告されるところまではいくのかな、と思いつつ。
ソース以下となります。
サンプルソースとコンパイル
HTMLEscapedString.h
ヘッダファイルでは構造体HTMLEscapedStringのメンバを利用者に公開しないようにしています。
#ifndef __HTML_ESC_STRING__ #define __HTML_ESC_STRING__ typedef struct HTMLEscapedString HTMLEscapedString; const HTMLEscapedString * const escape(const char *); void putHTMLEscapedStrLn(const struct HTMLEscapedString * const); #endif
HTMLEscapedString.c
構造体HTMLEscapedStringのメンバはヘッダファイルで利用者に公開しないようにしていますが、今回のケースでは欲しいメンバがなかったので、実際のメンバもありません。メンバを持たない構造体って今まで考えたことはなかったのですが定義できるのですね。本のサンプルではescape定義時に使用する関数escapeAmpとescapeOtherを補助として定義していますが、以下ではそれらを使用せずに処理を真似て実装しています。
#include <stdlib.h> #include <stdio.h> #include "HTMLEscapedString.h" /* 型 */ struct HTMLEscapedString { }; /* メモリを確保して返すことにする。*/ static HTMLEscapedString * const createHTMLEscapedString(int size) { return malloc(size); } const HTMLEscapedString * const escape(const char *src) { /* 簡易的にサイズは固定とする。*/ HTMLEscapedString *const dst = createHTMLEscapedString(128); char *dstp = (char *)dst; while(*src) { if('&' == *src) { *(dstp++) = '&'; *(dstp++) = 'a'; *(dstp++) = 'm'; *(dstp++) = 'p'; } else if('<' == *src) { *(dstp++) = '&'; *(dstp++) = 'l'; *(dstp++) = 't'; } else if('>' == *src) { *(dstp++) = '&'; *(dstp++) = 'g'; *(dstp++) = 't'; } else if('"' == *src) { *(dstp++) = '&'; *(dstp++) = 'q'; *(dstp++) = 'u'; *(dstp++) = 'o'; *(dstp++) = 't'; } else { *(dstp++) = *src; } src++; } return dst; } void putHTMLEscapedStrLn(const struct HTMLEscapedString *str) { printf("%s\n", (char *)str); }
簡単にするためエスケープ後の文字列は適当にサイズ128で確保しています。実際にこのような作りにする場合は、変換後の文字列の領域がどれぐらい必要になるかを知るためにサイズを求める関数を別途用意するなど必要になると思います。
main.c
#include <stdio.h> #include "HTMLEscapedString.h" int main(void) { const char *src = "Amp:&,<include>,Quote:\""; const HTMLEscapedString * const dst = escape(src); printf("%s\n", src); putHTMLEscapedStrLn(dst); /* 警告が出る */ printf("%s\n", dst); return 0; }
Makefile
all : runAll runAll : main01 main02 ./main01 @echo "" LD_LIBRARY_PATH=. ./main02 # 共有ライブラリ使用版 main02 : main.o libHTMLEscapedString.so gcc -Wall main.o -L . -lHTMLEscapedString -o main02 # オブジェクトファイルリンク版 main01 : main.o HTMLEscapedString.o gcc -Wall main.o HTMLEscapedString.o -o main01 main.o : main.c HTMLEscapedString.h gcc -Wall -c main.c libHTMLEscapedString.so : HTMLEscapedString.c HTMLEscapedString.h gcc -Wall -shared -fPIC HTMLEscapedString.o -o libHTMLEscapedString.so HTMLEscapedString.o : HTMLEscapedString.c HTMLEscapedString.h gcc -Wall -c HTMLEscapedString.c clean : rm -f *.o *.so main01 main02
実行
オプジェクトファイルをリンク時に指定する方法(main01)と共有ライブラリを使用する方法で実行してみました。実行すると型が合わない旨の警告が出ています。
$ make gcc -Wall -c main.c main.c: In function ‘main’: main.c:13:12: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘const HTMLEscapedString * const {aka const struct HTMLEscapedString * const}’ [-Wformat=] printf("%s\n", dst); ~^ gcc -Wall -c HTMLEscapedString.c gcc -Wall main.o HTMLEscapedString.o -o main01 gcc -Wall -shared -fPIC HTMLEscapedString.o -o libHTMLEscapedString.so gcc -Wall main.o -L . -lHTMLEscapedString -o main02 ./main01 Amp:&,<include>,Quote:" Amp:&,<include>,Quote:" Amp:&,<include>,Quote:" LD_LIBRARY_PATH=. ./main02 Amp:&,<include>,Quote:" Amp:&,<include>,Quote:" Amp:&,<include>,Quote:"