プログラミング演習2 Tips¶
ここでは,プログラミング演習にとどまらない,先を見据えたプログラミング環境構築のためのTipsを紹介する.
※受講生の皆さんへ,TAは,必ずしもこのページの内容を熟知していない可能性があります.
環境設定¶
Emacsのカスタマイズ(2)¶
まずは,基本設定を確認しておこう.プログラミング演習1 Tips
Makefileを作ろう¶
Makefileを作成しておくと,cプログラムのコンパイルやLaTeXのコンパイルが手軽になる.
基本のMakefileを手に入れよう.wgetコマンドなどでダウンロードしたほうがよい.
wget -nc --no-cache https://edu2024.sp.cs.okayama-u.ac.jp/eop/p2/_static/p2/Makefile
以下では,参考のためファイルの中身を表示している.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | # Example Makefile for EoP 2
# 提出物に関する prefix
# - $(USER)は,通常,自分のユーザ名に置換される
# - 自習環境で,学生番号以外のユーザ名の場合は,書き換えが必要
PREFIX = eop2_$(USER)
# (1) 最終版のプログラムファイル名
# - プログラムは,同名の .c ファイルから作成される
# - 完成後のファイルを指定するので,拡張子 .c を付け"ない"
TARGET_PG = $(PREFIX)
# (2) レポートのPDFファイル名
# - PDFは,同名の .tex ファイルから作成される
# - 完成後のファイルを指定するので,拡張子 .pdf を付け"る"
TARGET_PDF = $(PREFIX).pdf
# 環境固有の設定:初期値は演習室を想定
LATEX = uplatex -interaction=nonstopmode -file-line-error -synctex=1
# ターゲットの列挙 ※ただし,ファイルを生成しないもの
.PHONY: all check-syntax clean pg pdf test test1 test2
# 提出物に関係するファイルの作成
all: pg pdf
pg: $(TARGET_PG)
pdf: $(TARGET_PDF)
$(TARGET_PG): $(TARGET_PG).c
gcc -O0 -g -fsanitize=address -Wall -Wextra -o $@ $<
$(TARGET_PDF).pdf: $(PREFIX).tex
@echo -e '\n**** upLaTeX (1st time)'
$(LATEX) -halt-on-error $< > /dev/null || (cat $*.log; exit 1)
@echo -e '\n**** upLaTeX (2nd time)'
$(LATEX) $< > /dev/null; grep -e written -e Warning -e Error $*.log
@echo -e '\n**** DVI --> PDF'
dvipdfmx -o $@ $*.dvi
# または,以下1行
# edu-uplatex $<
# Emacs flymake 用の設定
check-syntax:
gcc -Wall -Wextra -fsyntax-only $(CHK_SOURCES)
#
# その他,個別のビルド設定
#
## Practice 6
cmdF-ex1: cmdF-ex1.c
gcc -O0 -g -fsanitize=address -Wall -Wextra -o cmdF-ex1 cmdF-ex1.c
# テストの実行
test: test1 test2
test1: $(TARGET_PG) test1.txt
./dbsample < test1.txt > result-test1-dbsample.log
./$(TARGET_PG) < test1.txt > result-test1-mydbsample.log
diff -q result-test1-dbsample.log result-test1-mydbsample.log
test2: $(TARGET_PG) test2.txt
./dbsample < test2.txt > result-test2-dbsample.log
./$(TARGET_PG) < test2.txt > result-test2-mydbsample.log
diff -q result-test2-dbsample.txt result-test2-mydbsample.log
clean:
rm -f *.aux *.log *.dvi
|
Emacs - Flymake を設定しよう¶
先ほどのMakefileにも記載されているが,flymakeを設定すると,EmacsでのC言語ソースファイル編集中に,文法エラーを示してくれる.
設定は以下の通り.
~/.emacs.d/init.elに,追記.
(require 'flymake nil t)
(add-hook 'c-mode-common-hook 'flymake-mode)
(add-hook 'latex-mode-hook 'flymake-mode)
(defun flymake-get-tex-args (file-name)
(list "uplatex" (list "-file-line-error" "-interaction=nonstopmode" file-name)))
以降,Emacs でCソースファイルや tex ファイルを編集する際に,flymake モードが有効になる.GUIではなくCLIで編集している時は,警告のハイライトがやや見づらいかもしれない.
プログラム開発(初級編)¶
基本¶
プログラミング演習1を通して,以下の習慣づけができているなら,初級達成と言えるかも?
コンパイル時&リンク時のエラーメッセージを読んでいる.
(もう一歩上へ!)gccのオプションに
-Wallだけでなく-Wextraもつけて,Warningも出ないように心がけている.
プログラムで怪しい挙動を見かけたら,
printfで変数表示デバッグをしている.(もう一方上へ!)
fprintf(stderr, ...)を使い,プログラムとして必要な表示と,デバッグとしての表示を使い分けている. cf. 課題説明書の仕様
こんなのはどうでしょう?¶
Linuxのシェルコマンドを活用する
例えば,
head,tail,grepなどのコマンドを用いれば,sample.csvのサブセットを作ることができる例えば,
sedコマンドを用いれば,文字の置換ができる.例えば,/と-の置換とか・・・ちょっとしたファイルの閲覧には,
lessコマンドで十分である.
プログラム開発(初~中級編)¶
Emacs でコメントトグル(切り替え)¶
ソースコードの一部分をコメントアウトしたり,解除したり,という操作は,プログラムの開発でもレポート執筆でも,よく行いますね.
マウスでソースコードを選択,あるいは,Shiftキー+矢印キーで選択したのちに,M-;でコメントトグルが実現できます.(オンオフの切り替え操作をトグルと呼びます.)
Emacsで検索&ジャンプをしてみよう¶
お手軽検索¶
C-sカーソル位置から前方向に(インクリメンタル)検索
編集中のファイルの検索&ジャンプ (occur)¶
Emacs のoccurコマンドを使ってみよう.
例えば,C言語のソースファイルを開いて,
M-x occur RET
struct profile RET # ⇐ occur実行後の入力待ちの欄に記入
を実行する.すると,struct profileを含む全ての行が,Emacs下部に表示されるだろう.
検索結果の行にキーボードカーソルを合わせてRETを押せば,ソースコード中の該当行にジャンプする.
参考:
C-x oカーソルがフォーカスするウィンドウの切り替え
C-x 1カーソルがフォーカスしてるウィンドウ以外を閉じる
C-x 0カーソルがフォーカスしてるウィンドウを閉じる
任意のファイルからの検索&ジャンプ (grep)¶
Emacs でgrepコマンドを使ってみるとよい.これは,実際には,コマンドラインで利用できるgrepそのものである.
Emacs内で実行することで,検索結果を見やすくし,容易に見つけた行に移動することができる.
詳しくは教育用計算機リファレンスを読んでほしい.
Makefileでコンパイルしてみよう¶
先述のMakefileを用意しておけば,以下のコマンドで,それぞれコンパイルできる.(makeコマンドは,自動的にカレントディレクトリのMakefileを読み込んでいる.)
実際にどのようなコマンドが実行されているかは,Makefileの中をよく読んでみよう.
新しいソースファイルを利用したいなら,Makefileの中で,cmdF-ex1 のために書かれている箇所を読んでみるとよい.
# 名簿管理プログラムのコンパイル
make pg
# レポートPDFの作成
make report
# プログラム動作テストの実行(test1.txt, test2.txtが正常動作するかどうか)
make test1
make test2
# Practice 6-2 で作る cmdF-ex1.c から cmdF-ex1 を作成
make cmdF-ex1
Makefileの書き方として,以下の3点を押さえておけば,ある程度カスタマイズすることができるだろう.
「
:(コロン)」の左にターゲット名(Cの関数みたいなもの)があり,右にそのターゲットに関係するファイルを書く ※注:「;(セミコロン)」ではないターゲット名を書いた行以下には,その関数で実行するLinuxコマンドを,TAB文字でインデントしつつ(複数)書く※注:
Makefileは,行頭にスペースがある箇所は,必ずTAB記号を入れなければならないLinuxのmakeコマンド実行時に,ターゲット名を引数に与えれば,そのターゲット名で定義されたコマンド群が実行される(例:
make pg)
詳しくは,自分で調べよう.3年次の実験科目の先取りとなるが,渡邊先生(3年次実験A)や笹倉先生(3年次実験C)の資料も参考になる.
Emacsでコンパイルしてみよう¶
C言語のコンパイル on Emacs¶
先述のMakefileを用意しておけば,Emacs内においても,以下のコマンドで Makefile を介したコンパイルができる.
参考:3.3. C言語によるコンパイル - 教育用計算機リファレンスマニュアル
M-x compile RET
pg RET # ⇐ 自動入力も含めると `make -k pg` としてから `RET`
コンパイルエラーの行にカーソルを合わせてRETすれば,エラー行にジャンプする.
参考:
C-x oカーソルがフォーカスするウィンドウの切り替え
C-x 1カーソルがフォーカスしてるウィンドウ以外を閉じる
C-x 0カーソルがフォーカスしてるウィンドウを閉じる
LaTeXによる組版 on Emacs¶
Emacsの設定ファイルを編集する必要がある.編集すべき項目は,冒頭の環境設定で示した通りである.
コンパイル(タイプセット)だけなら,以下のコマンド.
C-c C-f
プログラム開発(中~上級編)¶
Segmentation Fault なんてぶっ飛ばせ - AddressSanitizerの利用¶
gccのコンパイルオプションを以下のようにすると,Segmentation Fault の原因発見が容易になる.
gcc -O0 -g -fsanitize=address -Wall -Wextra -o cmdF-ex1 cmdF-ex1.c
コンパイル後の実行方法は,従来と同じである.(念のため,上記の例の場合,作成される実行ファイルはcmdF-ex1である.)
ためしに,Practice 6の cmdF-ex1.c を使って試してみよう.同ソースファイルのうち,forループの条件式の個所で,わざと>を>=と誤った記述をおこない,コンパイルと実行をしてみよう.(制御変数iが,宣言した配列のサイズを超えることになり,不正なアドレスへのアクセスが発生する.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | hara@D001:~/eop2$ gcc -O0 -g -fsanitize=address -Wall -o cmdF-ex1 cmdF-ex1.c
hara@D001:~/eop2$ ./cmdF-ex1
i = 0
Word:'01955 641225 Primary 25 2.6 Open' equals Id:'5100046'? --> No
Word:'01955 641225 Primary 25 2.6 Open' equals Comment:'SEN Unit 2.0 Open'? --> No
i = 1
Word:'01955 641225 Primary 25 2.6 Open' equals Id:'5100127'? --> No
Word:'01955 641225 Primary 25 2.6 Open' equals Comment:'01955 641225 Primary 25 2.6 Open'? --> No
i = 2
=================================================================
==602==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff5c986f10 at pc 0x558beb9bf4fa bp 0x7fff5c986d70 sp 0x7fff5c986d60
READ of size 4 at 0x7fff5c986f10 thread T0
#0 0x558beb9bf4f9 in main /home/users/ecs/hara/eop2/cmdF-ex1.c:31
#1 0x7f2335d3f0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x240b2)
#2 0x558beb9bc4ed in _start (/home/users/ecs/hara/eop2/cmdF-ex1+0x24ed)
(以下,省略)
|
ハイライト部分に注目してみると,
9行目:
i = 2をprintfで表示し,止まった.少なくとも,ループの制御変数iは,数値の2のようである.(この時点でおかしい,と思えたら素晴らしい)13行目:
#0から始まる行を見ると,main関数の中で止まったようだ.ファイルは,..../cmdF-ex1.cで31行目だな...
といったように,デバッグを進めることができる.
デバッガを使ってみよう¶
より詳しく見ていく場合は,デバッガgdbを利用するとよい.
なお,先述のオプションのうち-fsanitize=addressを消さないと,デバッグできない.
参考:演習室の環境であるならば,GUIを備えた
DDDの方が使いやすいかもしれない(7.6. DDDの使い方)
利用方法は,やや難しい.以下に,実行例を示す.
cmdF-ex1 の例においては,およそ以下のような実行で,問題にたどり着くことができよう.(gdb)から始まる行は,gdbのプロンプトであり,キーボードからの入力例となる.また,入力のうち,#以降は,読みやすくするためのコメントとして書いている.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | $ gcc -O0 -g -o cmdF-ex1 cmdF-ex1.c
$ gdb ./cmdF-ex1
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./cmdF-ex1...
(gdb) r # コマンドを実行
Starting program: /home/users/ecs/hara/eop2/cmdF-ex1
i = 0
Word:'01955 641225 Primary 25 2.6 Open' equals Id:'5100046'? --> No
Word:'01955 641225 Primary 25 2.6 Open' equals Comment:'SEN Unit 2.0 Open'? --> No
i = 1
Word:'01955 641225 Primary 25 2.6 Open' equals Id:'5100127'? --> No
Word:'01955 641225 Primary 25 2.6 Open' equals Comment:'01955 641225 Primary 25 2.6 Open'? --> No
i = 2
Word:'01955 641225 Primary 25 2.6 Open' equals Id:'892940592'? --> No
Program received signal SIGSEGV, Segmentation fault.
__strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:65
65 ../sysdeps/x86_64/multiarch/strlen-avx2.S: No such file or directory.
(gdb) bt # Segmentaion fault が起きたぞ.どこで止まったかな?
#0 __strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:65
#1 0x00007ffff7e45d45 in __vfprintf_internal (s=0x7ffff7fba6a0 <_IO_2_1_stdout_>, format=0x555555557228 "Word:'%s' equals Comment:'%s'? --> ",
ap=ap@entry=0x7fffffffd8e0, mode_flags=mode_flags@entry=0) at vfprintf-internal.c:1688
#2 0x00007ffff7e2ed6f in __printf (format=<optimized out>) at printf.c:33
#3 0x0000555555556bc4 in main () at cmdF-ex1.c:39
(gdb) f 3 # #0~#3 を順に見ると main関数で,cmdF-ex1.cの39行目で止まったのか.
# #3をもっと深く見てみよう.
#3 0x0000555555556bc4 in main () at cmdF-ex1.c:39
39 printf("Word:'%s' equals Comment:'%s'? --> ", word, p->comment);
(gdb) p i # ああ,この行で止まったのか.ループの制御変数 i はいくつかな?
$1 = 2
(gdb) p word # なるほど2か.ん?あれ?怪しいな...ちなみに,変数 word はどうかな?
$2 = 0x7fffffffdb30 "01955 641225 Primary 25 2.6 Open"
(gdb) p p # まぁ,大丈夫だよね.ポインタ p は,そもそも問題ないかな?
$4 = (struct profile *) 0x7fffffffdb30
(gdb) p p->comment # アドレスは大丈夫??かな.i=2から考えると,怪しそうだけどなぁ.
# p からメンバを見たらどうなるかな.例えば,p->commentは,,,
$3 = 0x800000007 <error: Cannot access memory at address 0x800000007>
(gdb) q # なるほど,この原因は・・(省略)・・かな.よし,デバッガを終了しよう.
A debugging session is active.
Inferior 1 [process 659] will be killed.
Quit anyway? (y or n) y
|