モバイルプログラミング第4回 Cプログラミングの基礎( 3 )
前回の復習 ポインタ 配列 文字列
変数が入っているメモリ番地(アドレス)を表す変数 ポインタ 変数が入っているメモリ番地(アドレス)を表す変数 値 ptr 2 0番地 6 1番地 ptr 6 2番地 printf(“ptr: %x”, ptr); ptr: 2 3番地 …
ポインタの使い方 void func(int *a); main(){ int *a_ptr; a_ptr = &a; 宣言内の*は変数が ポインタであることを表す ポインタは型を持っていて その型の中身を持つ void func(int *a); main(){ int *a_ptr; a_ptr = &a; *a_ptr = 6; } &でアドレスを取得 *はポインタが 指している中身を表す
main() { int num = 3; int *num_ptr; num_ptr = # *num_ptr = 6; 記述例 main() { int num = 3; int *num_ptr; num_ptr = # *num_ptr = 6; printf("%d\n", num); } ポインタとして宣言 numのアドレスをnum_ptrに代入 num_ptrの中身に値を代入 出力結果 $./a.exe 6
関数とポインタ 値渡し call-by-value ポインタ(アドレス)渡し ① ② 値 ポインタ 値 値 関数 関数 func(int a){…} ② func(int *a){…} 複製 値 ポインタ 値 値 文字列や配列を関数に渡すために必要 関数 関数
配列 a[0] x a[1] y 複数の要素を持つ変数 a[2] z … char a[80]; int num[20]; // 配列の配列(二次元配列) char *argv[10]; // ポインタの配列 …
配列とポインタ ptr ptr++ ptr+1 ptr+3 buf buf char buf[4]; 配列の名前はポインタ char *ptr = buf; 配列の名前はポインタ ポインタに加算すると次の要素、減算すると前の要素を指すようになる ptr ptr++ ptr+1 buf buf ptr+3
文字列 連続した文字の並び ヌル文字(\0)で終わる 例 a b c \0
文字列の宣言 char *buf = “abc”; a b c \0 “buf”はこの要素へのポインタ
前回の課題 課題1. 文字列を空白で分割する関数をつくろう。 ただし、ポインタと文字列(終端記号)の性質を利用すること。 課題2. microshellにヒストリー機能をつけよう。 実行したコマンドを配列に保存し、特定のキーを押したらコマンドリストを表示、実行できるようにする。 http://www.ht.sfc.keio.ac.jp/mobile2004/lecture4.htm にサンプルコード
課題1 解答例 int split(); main() { … if(fork() == 0){ split(input, argv); // 入力を空白で分割し、argvに格納する if(execvp(argv[0], argv)<0) perror(argv[0]); } int split(char *input, char *argv[]){ int i; argv[0] = input; // 最初にinputの先頭をargv[0]に入れておく while(*(input++)) // inputの今の文字が終端(\0、つまり偽)で無い限り if(*input==' '){ // inputの今の文字が空白なら *input = '\0'; // 代わりに終端文字を入れる argv[++i] = input+1; // iを増やして、argv[i]にinputの次の文字列を入れる return i; 課題1 解答例
空白に\0を入れてポインタで次の文字を指す int split(char *input, char *argv[]){ int i; argv[0] = input; // 最初にinputの先頭をargv[0]に入れておく while(*(input++)) // inputの今の文字が終端(\0、つまり偽)で無い限り if(*input==' '){ // inputの今の文字が空白なら *input = '\0'; // 代わりに終端文字を入れる argv[++i] = input+1; // argvの次のポインタにinputの次の文字列を入れる } return i; splitの内容 argv[0] argv[1] l s - l \0 \0 空白に\0を入れてポインタで次の文字を指す 文字列を見ていって
課題2 解答例 char history[20][80]; int history_num = 0; void process_history(char *input); main() { … while(fgets(input, sizeof(input), stdin)){ if(input[0] == 'h'){ process_history(input); } else{ if(history_num < 20) strcpy(history[history_num++], input); if(fork() == 0) ... void process_history(char *input){ int i, j; char buf[80]; for(i=0; i<history_num; i++) printf("%d) %s \n", i, history[i]); printf("input: "); fgets(buf, sizeof(input), stdin); j = atoi(buf); strcpy(input, history[j]); 課題2 解答例
process_historyの内容 呼び出し側 char history[20][80]; if(!strcmp(input, “h”)){ process_history(input); // 入力がhなら、ヒストリーから選択させ、inputにコマンドを代入 } else{ if(history_num < 20) strcpy(history[history_num++], input); // そうでなければhistoryに追加 … inputの実行 関数 void process_history(char *input){ int i, j; char buf[80]; for(i=0; i<history_num; i++) printf(“%d) %s \n”, i, history[i]); // historyを表示 printf(“input: ”); fgets(buf, sizeof(input), stdin); //ユーザの入力を取る j = atoi(buf); // それを数字に直して strcpy(input, history[j]); // historyに代入 process_historyの内容
今日やること 講義 mainへの引数 ファイル 構造体 プリプロセッサ 演習 リダイレクション機能
main(int argc, char *argv[])と宣言することで int i=argc; char *cmd=argv[0] } main(){ fgets(…); }
argc, argv main(int argc, char *argv[]) $./a hello world argv[0]はプログラム名、argv[1]は 第一引数… どちらも自動的に計算されてmainに渡される
記述例 #include <stdio.h> 実行結果 int i; $gcc sample.c main(int argc, char *argv[]) { printf("argc = %d\n", argc); for(i=0; i<argc; i++) printf("argv[%d] = %d\n", i, argv[i]); } 実行結果 $gcc sample.c $./a.exe hello world argc = 3 argv[0] = ./a argv[1] = hello argv[2] = world $
練習問題 コマンドラインから引数として二つ文字列を取って それらをつなげるプログラムを作ろう 引数の数が異なったらエラーメッセージを出そう int strncmp(const char *s1, const char *s2, size_t n); 二つの文字列を比べる char *strncpy(char *dest, const char *src, size_t n); 文字列をコピーする char *strncat(char *dest, const char *src, size_t n); 二つの文字列を連結する nにはstrlenを使うと良い
nmコマンド nmを使ってa.exeを見る T テキスト D データ BSS 未初期化データ addr低 addr高 $nm a.exe 00000000 A __dll__ 00401050 T _function 00401055 T _main 00402000 D __data_start__ 00402000 D _a 00402004 D _b 00402010 d _dw2_object_mutex.0 00402014 d _sjl_once.2 00402020 D __data_end__ 00403000 b .bss 00403000 B __bss_start__ 00403010 b _sjl_fc_key.1 00403020 B _environ 00403024 B __impure_ptr 00403028 B __fmode 00404114 i .idata$6 nmを使ってa.exeを見る T テキスト D データ BSS 未初期化データ addr低 addr高
プログラムの中身を見る アセンブラコード $ gcc –S ファイル.c $ less ファイル.s gdb $ gdb a.exe … (gdb) disassemble main
ディスク上のデータ、端末などをファイルとして扱う バイトの羅列 ディスク上のデータ、端末などをファイルとして扱う ファイル型変数 FILE *(ファイルポインター)で扱える ファイルを扱うプログラムはstdio.hをincludeする必要がある ファイル
ファイル関連の関数 オープン・クローズ FILE *fopen(const char *path, const char *mode); int fclose(FILE *stream); 読み書き char *fgets(char *s, int size, FILE *stream); int fputs(const char *s, FILE *stream);
fopenのモード FILE *fopen(char *ファイル名, const char *モード); モード(これらを組み合わせて指定する) r 読み込み r+ 書き込みもできる w 書き込み、ファイルの内容を初期化 w+ 読み込みもできる a 追加書き込み a+ 読み込みもできる b バイナリファイルとしてオープンする 詳しくはjmanのfopenを参照
記述例 main() { FILE *file; char buf[80]; file = fopen(“sample2.c", "r"); fgets(buf, sizeof(buf), file); fputs(buf, file); printf("%s", buf); fclose(file); } 読み込み専用で開いている 書き込みは行われない
定義済みの名前 stdin 標準入力(キーボード) stdout 標準出力(スクリーン) stderr 標準エラー出力(スクリーン) それぞれFILE *型変数 プログラムを実行すると自動的に開かれる
ファイル記述子 ファイルポインターやアドレスの代わりにやり取りされる整数値 オープンされているファイルのテーブルに対するインデックス ファイルディスクリプターとも呼ぶ FILE *型の中にも含まれる stdin 0, stdout 1, stderr 2 int open(const char *pathname, int flags); int creat(const char *pathname, mode_t mode); int close(int fd);
dup関数 ファイル記述子の複製を作る int dup(int oldfd); 使用されていない最小の値のディスクリプタを新しいディスクリプタとして使用する int dup2(int oldfd, int newfd); oldfd の複製として newfd を作成する。必要ならば最初に newfd をクローズ (close) する
#include <stdio.h> char buf[80]; main(int argc, char *argv[]) { FILE *file = fopen("tmp", "w"); dup2(file->_file, 1); if(argc==3){ strncpy(buf, argv[1], strlen(argv[1])); strncat(buf, argv[2], strlen(argv[2])); printf("%s",buf); } else printf("usage: a string1 string2"); fclose(file); 記述例 標準出力の1を閉じて tmpのfdに割り当てる printfの内容は tmpに書き込まれる
pipe関数 パイプ用のファイル記述子の組を作る パイプ: 一つのプロセスの出力をもう一方の入力にする (例) ls | less int pipe(int filedes[2]); filedes[0] には読み出し用、 filedes[1] には書き込み用のファイル・ディスクリプターが格納される
構造体 複数の異なる要素を持った変数 配列は各要素が同じデータ型だったが、構造体は各要素のデータ型が異なる
構造体の宣言 構造体自体 struct NAME{ int one; char two; struct hoge three; } [変数名]; 変数 struct NAME a, b, c[10];
FILEの構造体 /usr/include/sys/reent.hに定義されている struct __sFILE { unsigned char *_p; /* バッファ内の現在のポジション */ int _r; /* getc()用に残っているスペース */ int _w; /* putc()用に残っているスペース */ short _flags; /* フラグ、0ならフリー*/ short _file; /* ファイル記述子 */ struct __sbuf _bf; /* バッファ */ … } /usr/include/sys/reent.hに定義されている
要素の参照 . 要素の実体を参照する -> 要素のポインタを参照する 例) a.one = 3; b.two = ‘x’; . 要素の実体を参照する -> 要素のポインタを参照する 例) a.one = 3; b.two = ‘x’; c->one = &(a.one) dup(file->_file, 1);
typedef データ型に別の名前を付ける typedef int suuji; typedef struct{ int a; char b; … } hoge; typedef __FILE FILE;
プリプロセッサ - ヘッダーファイルを読むために - プリプロセッサ - ヘッダーファイルを読むために - プログラムがコンパイルされる前に行われる前処理 #include<stdio.h> #define HOGE 100 など ヘッダーファイルを読み込んだり、変数やマクロが展開されたりする 単純な置き換え
プリプロセッサコマンドの書き方 # で始める ¥ で複数行に渡って書く ; は要らない 例) # で始める ¥ で複数行に渡って書く ; は要らない 例) #include<string.h> #define SIZE 100 for(i=0; i<SIZE; i++); ←SIZEが100に置き換わる
マクロ #define kakeru(a, b) ((a)*(b)) int x = kakeru(2, 3); と書くと、コンパイルする前に int x = ((2)*(3)); と置き換えられる
#ifdef 変数が#defineされていたらコードを有効にする 例) #define DEBUG 1 ←0ならDEBUGしない … #ifdef DEBUG printf(“input = %s”, input); #endif
ヘッダーファイルの定義もコンパイル前に展開される 通常 名前.h 関数プロトタイプやマクロ定義が含まれている ヘッダーファイルの定義もコンパイル前に展開される
今日の演習 コマンドラインから引数として複数のファイル名を取り、その内容をつなげるプログラムを作ろう Microshellを拡張し、リダイレクションを使えるようにしよう リダイレクション:コマンドの後ろに>をつけることで、コマンドの実行結果をstdoutではなく、ファイルに書き込む