ぶろぐ

日記です

Cまとめ


C言語勉強したよ

LinuxC言語を勉強しました。次はJavaをやるので、安心して忘れるためにも、適当に書き出しておく。
へっぽこ初心者丸出しです(笑)

困ったときはmanコマンド

標準関数なんかは、manコマンドに乗っているので、引数、返り値の確認とかマッハでできて便利。ググるより高速。これ見てわからなければ、ググる

man

日本語が出ないときは、-aオプションを付けてみる。

man -a

変数は、宣言と同時に初期化すること。

配列はmemsetで初期化

char str[255] = {'\0'};
では、gdbで見た感じ初期化されていない感じ…(確か)

char str[255];
memset(str, 0x00, sizeof(str));

こんなコードが好かれるみたい。

プログラムは構造化すること

  • エラー処理をばっちりやること
  • 出口が一つのプログラムにすること
  • return が一つにすること
  • 途中でreturn飛ばして終了するのはNG。最後まで処理を流して、出口は一つにすること。
  • 処理が追い易いコードを心がけること

マジックナンバーは定数にすること

  • エラー番号や、定数など
  • コンパイル時に、文字を置き換えられる
#define PORT_NO 1234

gdb使った

C言語のデバッガツール、プログラムのトレースを行う

使い方(流れ)
  • gcc hello.c -o hello -g -Wall
  • gdb hello
  • break
  • info break
  • delete (番号)
  • run (コマンドライン引数)
  • list
  • next
    • プログラムを一行進める
  • step
    • プログラムを一行進める&関数があれば、関数の中まで入っていく。
  • cont
    • ブレークしているプログラムの再開
  • print (変数名)
    • 変数の中身を見れる。*strみたいに、C言語と同じように使える
  • set (変数名=値)
    • 変数に値をセットできる
  • bt
    • コール元の関数hogehoge.コアダンプしたときhogehoge.

そんな感じ。
if文では処理を直接書かずに、変数を評価するようにしたほうが、トレースしやすい。gdbのsetコマンドで変数を変えてif文の中に入れるため。

Socketプログラムの流れ

ネットワークを流れるTCPヘッダはビックエンディアンで送ることが決められている(ネットワークバイトオーダー)
IPアドレス、ポート番号などの情報は、ビックエンディアンに変換して構造体に格納するので注意。
「ソケット」はIPv4専用ではないので、いろいろと設定がややこしかったり、くどかったりするけど、手順覚えてコネクション張るまでの処理を関数化しとけば楽ちん。
ソケットは、プログラムでcloseした後もしばらく開かれたままになり、次回起動時にソケット生成エラーが出ちゃったりするので、オプションですぐにソケットを捨てるように設定しておく。

/* ソケットを、すぐに再利用できるようオプション設定 */
const int one = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int));
サーバー側の流れ
  • socket()
    • ソケットを生成
  • bind()
    • ソケットに接続を許可するクライアントのIPアドレス、ポート番号などの情報を結びつける。普通はall allowな感じのINADDR_ANYを使う。変わるのはポート番号ぐらい。
  • listen()
    • 接続受付専用のポートを作成する。ここで作成されたファイルディスクリプタでは通信は行わず、acceptので接続受付用として使う。
  • accept()
    • クライアントからの接続要求を受け付ける。acceptが完了すると、コネクションが張られた状態になる。クライアントからの接続要求があるまで、次の処理に移れないブロック関数なので注意。複数クライアントを持つ場合は、ここをselect()でノンブロックにするか、acceptのみを行うスレッドを生成するなどして工夫する。引数で渡した構造体に、クライアントの情報が格納され、返り値には、クライアントとの通信を行うためのファイルディスクリプタが返る。
  • send()
    • クライアントにデータを送信する。unsigned charとかに構造体のデータをmemcpyで入れて、バイナリデータでやり取りを行う、なんていう荒業(?)もあり。クライアントとのコネクションが切れたときは0が返り値、異常時は-1
  • recv()
    • クライアントからデータを受信する。バイナリデータで受け取ったなら、構造体にmemcpyでコピーして展開して使うなど。バイナリデータ使うと、telnetでデバックできなくなるという難点が…。これもsendと同じでコネクションが切れると0を返し、異常時は-1を返す。
  • close()
    • でコネクションを切断する。ソケットを閉じる。
クライアント側流れ
  • socket()
  • connect()
  • send()
  • recv()
  • close()
リッスンポートを作成する関数の例

センス無いコードなので、あくまでも例として…

/*
 * 機能  :リッスンポートを作成する 
 * 引数  :ポート番号とキューの最大数
 * 戻り値:リッスンポートのファイルディスクリプタ
 */

int create_socket(int port_no, int listen_num) {

    /* リッスンポートのファイルディスクリプタを保持する変数 */
    int listen_fd = 0;
    int result = 0;

    /* サーバー情報の構造体を初期化 */
    struct sockaddr_in serv_addr;
    memset(&serv_addr,0x00,sizeof(struct sockaddr_in));

    /* ソケットの用意 */
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    /* ソケットのバインド処理 */
    if (listen_fd == -1) {

        result = -1;

    } else {

        /* ソケットの設定 */
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(port_no);
        serv_addr.sin_addr.s_addr = INADDR_ANY;

        /* ソケットを、すぐに再利用できるようオプション設定 */
        const int one = 1;
        setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int));

        /* バインド処理 */
        if (bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
            result = -1;

        } else {
            /* リッスン処理 */
            if ((listen(listen_fd, listen_num) == -1)) {
                result = -1;
            
			/* 返り値をリッスンポートのファイルディスクリプタとする */
            } else {
                result = listen_fd;
            }
        }

    }

    return result;

}

今疑問に思っていること

ブロックスコープで変数を局所化

よくループで使う int i=0; とかは、狭いブロック内で宣言して、狭いスコープで宣言したほうがいいのか?
それとも、関数の頭で使用する変数はすべて宣言したほうがいいのか…。

例外処理やりだすと、if文がごちゃごちゃ…

センスがなさすぎる構造化…

main(){

	if(!処理成功) {
		// 失敗時の処理
	} else {
		// 成功時の処理
		
		if (!処理成功) {
			// 失敗時の処理
		} else {
			//成功時の処理
		}
	}
	
	return 結果;

}

ネストが少ない例

main() {

	if(!前処理成功) {
		// 失敗時の処理
	} else {
		// 成功時の処理
	}

	if(!前処理成功) {
		// 失敗時の処理
	} else {
		// 成功時の処理
	}

	return 結果;

}

とか

main() {

	if(前処理成功) {
		// 成功時の処理
	}

	if(前処理成功) {
		// 成功時の処理
	}

	return 結果;

}


と、前処理が成功なら、処理ブロックに入っていく、のほうがまだネストが少なくなるので見やすい。goto使って例外は飛ばしたほうがいい気がするけど、gotoがコーディング規約で禁止されていたら駄目だし、構造化は守らないといけないし…どーしましょ。

utf-8はマルチバイトコード

printf使ったとき、%30sとかやっても表示が合わない。utf-8はマルチバイトコード(だっけ?)なので、日本語は3バイトで表現されていたりする。日本語が出力されると、一文字ずつ表示がずれちゃったりするので、必要。
ワイド文字?とか解決する方法はありそうなんだけど、戦えなかった…orz
文字列を比較して「utf8の文字があれば、printfの書式を一つ増やす」という方法でやっつけな解決。

/* 書式を動的に変更する方法 */
printf("%*s",35+i,str);
ナイスなコーディング

どういうのがいいコーディングなんだろう…
コーディングの本買おう。いい本ないかな?
「良いコードを書く技術」とかいいのかな?

後は・・・

後は、pthreadとMySQLのC APIをやったけど、もーいーやー(´・ω・`)

C API - MySQLを操作するためのAPI

C APIは、関数化する時にポインタの取り扱いで躓いたぐらいで、APIの使い方はそれほど難しくない印象を受けた。気分はDBIPHPと一緒で。

pthread - マルチスレッドのための仕組み

pthreadは

  • pthread_create
    • スレッドの生成、関数をスレッドとして動かす
  • pthread_detach
    • スレッドを切り離す
  • pthread_join
    • スレッドの終了を待つ

って感じ。
メイン関数が終了すれば、スレッドも解放されるので、特にゾンビプロセスなどの問題はなさそうだけど、正常にスレッドを終了させる必要があると、プログラムが大変になるかも。
スレッド間のやり取りとか、スレッド管理、スレッドセーフ
そこまではできなかった。
その辺は、次回への課題ということで

次はJava

単体テストは、gdb使ったけど、イケメンはCUnitでテストの自動化するみたい
テスト駆動開発…これは次のJavaで学ぼう。
SVNも使うみたいで、ちょっとwktk