プログラミングなどなど

プログラミングに関係しそうな内容を

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となります。

問題は以下のようなものでした。

f:id:hihdrum:20200330231536p:plain
問題のダイアグラム

問題の情報を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 を利用することで可能でした。
使用したファイルは以下となります。

func.h

簡易的にするためインクルードガードは省いています。

int func(int x);

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;
}

コンパイル

デバッグ用にオプションをつけておきます。

$ gcc -Wall -g3 main.c -o main

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:&amp,&ltinclude&gt,Quote:&quot
Amp:&amp,&ltinclude&gt,Quote:&quot

LD_LIBRARY_PATH=. ./main02
Amp:&,<include>,Quote:"
Amp:&amp,&ltinclude&gt,Quote:&quot
Amp:&amp,&ltinclude&gt,Quote:&quot