C言語でジャンケンゲームを作る

1.ジャンケンゲームはランダム関数でrand() % 3 + 1; としてグーチョキパーを出す。
2.srand((unsigned int)time(NULL));でランダム関数を初期化する

#include <stdio.h>
#include <stdlib.h> // rand()に必要

int main(void){
	int n, r;
	printf("ジャンケン....\n");
	printf("1.グー、2.チョキ、3.パー [数字を入力してください]\n");
	scanf("%d", &n);
	switch(n){
		case 1:
			printf("グー\n");
			break;
		case 2:
			printf("チョキ\n");
			break;
		case 3:
			printf("パー\n");
			break;
		default:
			printf("1〜3の何れかを入力してください。\n");
			return 1;
	}	

	srand((unsigned int)time(NULL));
	r = rand() % 3 + 1;

	switch(r){
		case 1:
			printf("相手はグー\n\n");
			break;
		case 2:
			printf("相手はチョキ\n\n");
			break;
		case 3:
			printf("相手はパー\n\n");
			break;
	}
	switch(n){
		case 1:
			switch(r){
				case 1:
					printf("あいこです。\n");
					break;
				case 2:
					printf("あなたの勝ちです。\n");
					break;
				case 3:
					printf("あなたの負けです。\n");
					break;
			}
			break;
		case 2:
			switch(r) {
				case 1:
					printf("あなたの負けです。\n");
					break;
				case 2:
					printf("あいこです。\n");
					break;
				case 3:
					printf("あなたの勝ちです。\n");
					break;
			}
			break;				
		case 3:
			switch(r) {
				case 1:
					printf("あなたの勝ちです。\n");
					break;
				case 2:
					printf("あなたの負けです。\n");
					break;
				case 3:
					printf("あいこです。\n");
					break;
			}
			break;
	}

	return 0;
}

$ ./app
ジャンケン….
1.グー、2.チョキ、3.パー [数字を入力してください]
1
グー
相手はチョキ

あなたの勝ちです。
$ ./app
ジャンケン….
1.グー、2.チョキ、3.パー [数字を入力してください]
3
パー
相手はパー

あいこです。

一応できたけど、勝ち負けの判定をswitch文の入れ子にすると冗長になるな。。
他の方がどうやって書いているか見てみると、、、
あ、swtichではなく、以下の条件式で収まるじゃん。。。
if (r == n) : あいこ
elseif (r – n == 1 || r – n == -2) : あなたの勝ち
else : 負け

C言語 + Ubuntu18.04でGUIを作りたい

C言語でもGUIを自由に作れれば、C言語のロジックをコーディングすれば、ほぼC言語でもアプリケーションを作れるようになるのでは? という事で、C言語のGUIについて考えたい。

GUIライブラリはOSによって異なる
– WindowsはWindows API や、GUIライブラリ
– LinuxはLinux GUI アプリ
– Android, iOSはCを使うアプリから

### UbuntuのGUI
– GUIツールキットにGTK+2.0を使用する
– GIMP, Gnome, Mozilla FirfoxなどはGTK+を使用して作られた

GTK+とは?
クロスプラットフォームのウィジェット・ツールキット(GUIツールキット)
gtk.org

### GTK+, Gladeのインストール
$ sudo apt-get install libgtk2.0-dev glade
$ pkg-config –cflags gtk+-2.0  // gtkを使うのに必要なものを表示
-pthread -I/usr/include/gtk-2.0 -I/usr/lib/x86_64-linux-gnu/gtk-2.0/include -I/usr/include/gio-unix-2.0/ -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/libpng16 -I/usr/include/pango-1.0 -I/usr/include/harfbuzz -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/freetype2 -I/usr/include/libpng16

$ gcc -o gtk window.c
window.c:1:10: fatal error: gtk/gtk.h: No such file or directory
#include
^~~~~~~~~~~
compilation terminated.

うーむ、なんか上手くいかんな。

$ gcc -o gtk window.c `pkg-config –cflags –libs gtk+-2.0`
$ ./gtk
(gtk:17204): Gtk-WARNING **: 02:38:25.145: cannot open display:

何故だ。
というか、GTKで開発ってしないのか?

C言語でMySQLにinsertする書き方

sql_strの箇所をselectからinsert文に変えるだけです。update, deleteも一緒です。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h>

int main(){
	MYSQL *conn = NULL;
	MYSQL_RES *resp = NULL;
     // MYSQL_ROW row;
	char sql_str[255];
	char *sql_serv = "localhost";
	char *user = "root";
	char *passwd = "hogehoge";
	char *db_name = "test";

	memset( &sql_str[0], 0x00, sizeof(sql_str));

	conn = mysql_init(NULL);
	if(!mysql_real_connect(conn, sql_serv, user, passwd, db_name, 0, NULL, 0)){ 
		 exit(-1);
	}

	snprintf(&sql_str[0], sizeof(sql_str)-1, "insert into test (id,name) values (4, 'ddd')"); 
	if(mysql_query(conn, &sql_str[0])){ 
		mysql_close(conn);
		exit(-1);
	}

	mysql_free_result(resp);
	mysql_close(conn);

	return 0;
}

### コンパイル&実行
$ gcc -Wall -o dev hello.c -lmysqlclient -L/usr/lib/x86_64-linux-gnu/
$ ./dev

### 動作確認
mysql> select * from test;
+——+——+
| id | name |
+——+——+
| 1 | aaa |
| 2 | bbb |
| 3 | ccc |
| 4 | ddd |
+——+——+
4 rows in set (0.00 sec)

なるほど、DBとの接続はわかった。次はGUIか。

C言語でMySQLのレコードからSelectする方法

### ソースコード

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h>

int main(){
	MYSQL *conn = NULL;
	MYSQL_RES *resp = NULL;
	MYSQL_ROW row;
	char sql_str[255];
	char *sql_serv = "localhost";
	char *user = "root";
	char *passwd = "hogehoge";
	char *db_name = "test";

	memset( &sql_str[0], 0x00, sizeof(sql_str)); // memsetはメモリに指定バイト数分の値をセット, 0x00はヌル文字

	conn = mysql_init(NULL);
	if(!mysql_real_connect(conn, sql_serv, user, passwd, db_name, 0, NULL, 0)){ // サーバ接続開始 mysql_real_connect(mysql, host, user,passwd,db, port,  unix_socket, client_flag)
		 exit(-1);
	}

	// クエリ実行
	snprintf(&sql_str[0], sizeof(sql_str)-1, "select * from test");  // snprintfは指定文字数分だけ文字配列に書き込む
	if(mysql_query(conn, &sql_str[0])){  // sql_str によって指示される SQL ステートメントを実行
		mysql_close(conn);
		exit(-1);
	}

	// レスポンス
	resp = mysql_use_result(conn);  // 結果セットの取得を開始
	while((row = mysql_fetch_row(resp)) != NULL){   // 結果を添字配列として取得する
		printf("%d: %s\n", atoi(row[0]), row[1]);
	}

	mysql_free_result(resp);  // 結果保持用メモリを開放
	mysql_close(conn); // MySQL 接続を閉じる

	return 0;
}

### ライブラリのlibmysqlclient.soの配置場所を確認
$ find / -name “libmysqlclient.so”
/usr/lib/x86_64-linux-gnu/libmysqlclient.so

### コンパイル、実行
// -Wは警告メッセージ抑制, -Lはオブジェクトライブラリ
$ gcc -Wall -o dev hello.c -lmysqlclient -L/usr/lib/x86_64-linux-gnu/
$ ./dev
1: aaa
2: bbb
3: ccc

おいおい、マジかよ。。。
感動した。

C言語でmysqlを使う前準備

### MySQLにデータ挿入
$ mysql -u root -p
> use test;
> create table test(id int,name varchar(256));
> insert into test(id,name) values(1,”aaa”);
> insert into test(id,name) values(2,”bbb”);
> insert into test(id,name) values(3,”ccc”);
> select * from test;

### ubuntu18.04にmysql/mysql.hが入っているか確認
hello.c

#include <stdio.h>
#include <mysql/mysql.h>


int main(){

	return 0;
}

$ gcc -o dev hello.c
hello.c:2:10: fatal error: mysql/mysql.h: No such file or directory
#include
^~~~~~~~~~~~~~~
compilation terminated.
-> 入っていない

### libmysqlclient-devをインストール
$ sudo apt-get -y install libmysqlclient-dev
$ gcc -o dev hello.c
-> 無事コンパイル

trustyのlibmysqlclient-dev.installのgithub見たが、includeしてるって事か?

usr/include/mysql/*.h
usr/include/mysql/mysql/*.h
usr/include/mysql/mysql/*.h.pp
usr/include/mysql/mysql/psi/*.h
usr/lib/*/libmysqlclient.a
usr/lib/*/libmysqlclient.so
usr/lib/*/libmysqlservices.a
usr/bin/mysql_config
usr/bin/mysql_config_editor
usr/share/man/man1/mysql_config.1
usr/share/man/man1/mysql_config_editor.1
# legal
usr/share/doc/libmysqlclient-dev/COPYING
usr/share/doc/libmysqlclient-dev/README

.aファイルはStaticライブラリの事らしい。
あまり見ないな。

Ubuntu18.04 mysql5.7でAccess denied for user ‘root’@’localhost’

ubuntu18.04でtasksel install lamp-serverでmysqlを入れ、パスワードの設定をしたが、アクセスできない時

$ sudo mysql -u root
mysql> update user set authentication_string=password(“password”) where User=’root’;
$ mysql -u root -p
Enter password:
ERROR 1698 (28000): Access denied for user ‘root’@’localhost’

むむむむ?

MySQL 5.7では認証にauth_socket plugin(MySQL :: Security in MySQL :: 6.1.11 Socket Peer-Credential Pluggable Authentication) というものを使っていて、これを使っているとパスワードではアクセスできず、MySQLのユーザー名と、システムのユーザー名が一致しているときのみアクセスできる

mysql> SELECT User, Host, plugin FROM mysql.user;
+——————+———–+———————–+
| User | Host | plugin |
+——————+———–+———————–+
| root | localhost | auth_socket |
| mysql.session | localhost | mysql_native_password |
| mysql.sys | localhost | mysql_native_password |
| debian-sys-maint | localhost | mysql_native_password |
+——————+———–+———————–+
4 rows in set (0.00 sec)

mysql> UPDATE user SET plugin=’mysql_native_password’ WHERE User=’root’;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

$ mysql -u root -p

C言語でfreeコマンドを書きたい

メモリを調べるにはfree, top, ps, vmstat, /proc/meminfoなどがある

$ free
total used free shared buff/cache available
Mem: 1008620 312640 186604 2200 509376 542844
Swap: 0 0 0

– 単位はバイト
– メモリ8Gのマシンなら、8G

■freeコマンドの項目
total: 全メモリ
free: 全く利用されていないメモリ
buff/cache: キャッシュとして利用されているけど、解放できる可能性のあるメモリ
used: = total – free – buff/cache

■swapとは
メモリ-の一部をディスク(HDDなど)に退避させて計算を続ける事ができる。この様にしてメモリ-の容量を実際よりも大きく見せるメカニズムを仮想メモリという。そのためにディスクにメモリを退避させるための領域をswapという

メモリ消費量を見積もるには /proc/pid/status の VmHWM

#include <stdio.h>


int main(){

	char command[128];

	sprintf(command, "grep VmHWM /proc/%d/status", getpid()); // getpid():プロセスのIDを知る
	system(command);

	return 0;
}

$ ./dev
VmHWM: 720 kB

■物理メモリ使用量の調べ方
$ ps
PID TTY TIME CMD
459 pts/0 00:00:00 bash
687 pts/0 00:00:00 bash
2490 pts/0 00:00:00 bash
4458 pts/0 00:00:00 ps
18043 pts/0 00:00:00 dev
18140 pts/0 00:00:00 dev
18499 pts/0 00:00:00 dev
18505 pts/0 00:00:00 dev
32396 pts/0 00:00:00 bash
32437 pts/0 00:00:00 bash
32556 pts/0 00:00:00 bash
32600 pts/0 00:00:00 bash

$ cat /proc/18043/status
Name: dev
Umask: 0002
State: T (stopped)
Tgid: 18043
Ngid: 0
Pid: 18043
PPid: 2490
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 256
Groups: 1000
NStgid: 18043
NSpid: 18043
NSpgid: 18043
NSsid: 2490
VmPeak: 4508 kB
VmSize: 4508 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 704 kB
VmRSS: 700 kB
RssAnon: 60 kB
RssFile: 640 kB
RssShmem: 0 kB
VmData: 176 kB
VmStk: 132 kB
VmExe: 4 kB
VmLib: 2112 kB
VmPTE: 56 kB
VmSwap: 0 kB
HugetlbPages: 0 kB
CoreDumping: 0
Threads: 1
SigQ: 0/3838
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 0000000000000000
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
NoNewPrivs: 0
Seccomp: 0
Speculation_Store_Bypass: vulnerable
Cpus_allowed: 3
Cpus_allowed_list: 0-1
Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 3
nonvoluntary_ctxt_switches: 2

less, more, cat, tailの違い

cat
L 標準出力

more
L ファイル末尾まで行くと終了

less
L ファイル内容を閲覧、 qで終了

tail
L ファイル末尾だけ表示

実際にコマンドを打って確認しましょう。
$ cat hello.c
$ more hello.c
$ less hello.c

lessだと次の操作に移るのに[q]を打たないといけないので、catの方を多用していますが、行が長いファイルをじっくり確認したい場合はlessの方が使いやすい、と言ってる方もいます。

C言語でaproposコマンドを書きたい

■要求定義
– manページの内容を検索する
– whatisと名付けられたいくつかのファイルを検索している

e.g.
$ apropos passwd
chgpasswd (8) – update group passwords in batch mode
chpasswd (8) – update passwords in batch mode
fgetpwent_r (3) – get passwd file entry reentrantly
getpwent_r (3) – get passwd file entry reentrantly
gpasswd (1) – administer /etc/group and /etc/gshadow
grub-mkpasswd-pbkdf2 (1) – generate hashed password for GRUB
htpasswd (1) – Manage user files for basic authentication
openssl-passwd (1ssl) – compute password hashes
pam_localuser (8) – require users to be listed in /etc/passwd
passwd (1) – change user password
passwd (1ssl) – compute password hashes
passwd (5) – the password file
passwd2des (3) – RFS password encryption
update-passwd (8) – safely update /etc/passwd, /etc/shadow and /etc/group

キーワード検索の原理はregexと同じか。。

C言語でmanコマンドを書きたい

■要求定義
– コマンドのマニュアルを表示する

$ man 調べる語句

/usr/share/man 直下にソースコードがある
man1 ユーザプログラム
man2 システム呼び出し
man3 ライブラリ呼び出し
man4 特殊ファイル
man5 ファイルフォーマット
man6 ゲーム
man7 その他
man8 システム管理者

e.g.
/usr/share/man/man1/ln.1.gz を解凍する

.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.47.3.
.TH LN "1" "January 2018" "GNU coreutils 8.28" "User Commands"
.SH NAME
ln \- make links between files
.SH SYNOPSIS
.B ln
[\fI\,OPTION\/\fR]... [\fI\,-T\/\fR] \fI\,TARGET LINK_NAME   (1st form)\/\fR
.br
.B ln
[\fI\,OPTION\/\fR]... \fI\,TARGET                  (2nd form)\/\fR
.br
.B ln
[\fI\,OPTION\/\fR]... \fI\,TARGET\/\fR... \fI\,DIRECTORY     (3rd form)\/\fR
.br
.B ln
[\fI\,OPTION\/\fR]... \fI\,-t DIRECTORY TARGET\/\fR...  \fI\,(4th form)\/\fR
.SH DESCRIPTION
.\" Add any additional description here
.PP
In the 1st form, create a link to TARGET with the name LINK_NAME.
// 省略

$ man ln
make links between files と書かれているのがわかります。

■検討
– gzファイルを配置して、解凍して中身を読み取るプログラムを書いていく
$ ls -l
total 20
-rwxrwxr-x 1 vagrant vagrant 8344 May 18 08:34 dev
-rw-rw-r– 1 vagrant vagrant 267 May 18 08:34 hello.c
-rw-rw-r– 1 vagrant vagrant 1728 May 18 10:53 ln.1.gz

#include <stdio.h>
#include <stdlib.h>

int main(){

	char cmd[256];
	char str[256];
	char filename[] = "./ln.1.gz"; //  /usr/share/man/man1/ln.1.gz

	sprintf(cmd, "gzip -d %s", filename); // gzip -d(ファイル展開)が指す書式文字列に従って cmdが指す文字配列へ書き込み
	
	FILE *fp;
	fp = popen(cmd, "r"); // popenはプロセスをオープンする
	while(fgets(str, 256, fp)){
		printf("%s", str);
	}
	pclose(fp);

	return 0;
}

zipオープンまではいくが、fgetsの出力がうまくいかんな。