390 likes | 494 Views
モバイルプログラミング第3回 Cプログラミングの基礎( 2 ). 前回まで: microshell. #include <stdio.h> char command[80]; char *prompt="% "; main() { printf("%s", prompt); while(scanf("%s", command) != EOF){ if(fork() == 0){ execl(command, command,0); }else{ wait(0);
E N D
前回まで:microshell #include <stdio.h> char command[80]; char *prompt="% "; main() { printf("%s", prompt); while(scanf("%s", command) != EOF){ if(fork() == 0){ execl(command, command,0); }else{ wait(0); } printf("%s", prompt); } }
前回の課題 • 課題1. lsコマンドを呼び出して実行できるようにしよう。 Hint. %/bin/ls と実行すれば実行できますが、 %ls だけでは実行できません。 パスをサーチするには? execl ⇒ execlp • 課題2. 知らないコマンドがでたらエラーを返すようにしよう。 Hint.大抵のシステムコール・関数は正常に実行できたかどうか確認するため返り値という値を返します。 if( ( execlp(command, command, 0) ) <0 ) printf(“error\n”);等 • 課題3. ls -lやcatコマンドのように、第2引数を処理するようにしよう。 第2引数をchar *argv[]等に入れて execvp(argv[0], argv)のようにする
解答例 #include <stdio.h> char *prompt="% "; char *argv[20], input[80]; main() { printf("%s", prompt); while( fgets(input, sizeof(input), stdin) != NULL ){ if(fork() == 0){ /* inputを空白で分割し“ls -l” ⇒ “ls” “-l” argvに格納する */ if(execvp(argv[0], argv)<0) perror(argv[0]); }else{ wait(0); } printf("%s", prompt); } }
傾向 • 第3課題でつまるケースが多かった char *argv[]を整える方法 • 応用ではcdやパイプを実装するケースが多かった
今日やること 関数 ポインタ 配列 文字列 演習はこれらを応用した文字列分割と ヒストリー機能
返り値(戻り値)の型 出力 関数名 引数の型と名前 入力 関数とは 頻繁に使うコードをまとめたもの intfunc(int a, char b){ … }
関数の定義 intadd(int a, int b){ int num; num = a+b; return num; } 関数はreturnで正しい型の 返り値を返さないといけない 呼び出し側が得られる 唯一の結果
関数の宣言と呼び出し • int型以外の返り値を持つ関数は、変数同様宣言する必要がある void func(int a, char b); main(){ func(x, y); } voidfunc(int a, char b){ … } 宣言 宣言が無いとimplicit(暗黙に)intを返すと解釈する 定義は別の場所でできる
返り値 • int型の返り値は定義で省略しても良い main(){… } 等 • 返り値は呼び出し側が無視してもOK 単にexecvp(…); 等と呼び出して良い (intが返ってるけど)
引数 関数への入力 main(){ func(x, y); } x, y 実引数(呼び出し側) intfunc(int a, int b){ … } a, b 仮引数 (呼び出され側) どんな値が渡されるか分からないから 「仮」
関数への引数の渡し方 • 値渡し call-by-value • ポインタ(アドレス)渡し ① func(int a){…} ② func(int *a){…} 複製 値 ポインタ 値 値 関数 関数
ポインタ これまでに利用した関数 From jman page • int execlp(const char *file, const char *arg, ...); • int execvp(const char *file, char *const argv[]); • int scanf(const char *format, ...); • char *strcpy(char *dest, const char *src); …(ピリオド三つ)は可変個の引数を表す constはその変数の値が変更されないことを 保証する
ポインタ 変数が入っているメモリ番地を表す変数 値 ポインタ 変数 メモリ 値 2 6 0番地 1番地 6 a 2番地 int a; int *a_ptr; 3番地 …
*と& • 宣言 int *a_ptr; • 使い方 a_ptr = &a; *a_ptr = 6; 宣言内の*は変数が ポインタであることを表す &でアドレスを取得 *はポインタが 指している中身を表す
ポインタと中身の関係 int a; int *b; 代入不可 a b &a 代入できる b *b 代入できる a *b 代入不可 &a
ポインタと引数 値渡し call-by-value ポインタ(アドレス)渡し ② func2(int *a){ a = “abc”; } ① func1(int a){ return a++; } 実際にaは渡さない 実際にaを処理できる
具体例 main(){ int a, b; int *a_ptr; b = func1(a); func2(a_ptr) } aの複製が渡され、値が返る a_ptrの指す中身が直接処理される、配列などでよく使われる
それぞれの違い • 値渡し 長所: 渡した値に何されても平気 短所: 適切な値をreturnし、処理しないといけない • ポインタ渡し 長所: 関数内で全て処理が行われるから便利 配列を渡す場合は必然 短所: 変数の中身が変更される可能性がある
ポインタの型 • ポインタは型を持つ a_ptrは特定の型の変数を指し *a_ptrはその型の中身を持つ Int, char, long…それぞれのポインタは異なる
ポインタの出力 • printf(“%x”, ptr); 16進数(Hexadecimal) 例:22f064 • printf(“%p”, ptr); アドレスを出力 出力例:0x22f064
ポインタの例 ポインタとして宣言 main() { int num = 3; int *num_ptr; num_ptr = # *num_ptr = 6; printf("%d\n", num); printf(“%d\n”, *num_ptr); printf("%x\n", num_ptr); } numのアドレスをnum_ptrに代入 出力結果 $./a.exe 6 6 22f06c $ num_ptrが指す値(=num)
ポインタの初期化 int *a_ptr = &a;// アドレスの取得 int a[10]; Int *a_ptr = a;// 配列の名前 Int *a_ptr = &a[2];// 要素のアドレス 初期化していないポインタを使うと多くの場合エラーになる
… 配列 a[0] x a[1] y 複数の要素を持つ変数 char a[3]; int num[20]; int num[10][10]…; a = { x, y, z }; num = { {1, 2, 3, …, 10} , {1,2, 3, …, 10} } a[2] z …
配列の名前とポインタ • 実際は最初の要素を指すポインタ • int *a[10] なら a == &a[0] 配列の名前の実体はポインタ a[0] a
関数への受け渡し • 配列として受け渡せない • 配列名(ポインタ)を渡す Int buf[20]; func(buf); intfunc(int *num){ } char a[3]; char b[3]; strcat(a, b); 要素数などを渡す手段が必要 文字列の場合終端記号\0で終わりを判別
execlでの例 int execl(const char *path, const char *arg, ...); char command[80]; main() { … execl(command, command,0); … 配列の名前なので渡せている
多次元配列とポインタの配列 • 多次元配列: num[2][3] • ポインタの配列: char *argv[6] 配列 多次元 配列 全ての要素がポインタ 配列
それぞれ関数への受け渡し ポインタが渡るように注意する execvpなどはポインタの配列を引数に取る int execvp(const char *file, char *const argv[]); execvp(argv[o], argv); sum(num[1]); int sum(int *num){ for(;;) total += num[i]; return total; }
配列とポインタへの演算 ポインタには加算と減算ができる a_ptr-1 a_ptr a_ptr+1 値を出すときは*(a_ptr+1) *a_ptr+1だと中身に1が加わってしまう
加算の例 文字列の中から特定の文字を探す char buf[80]; char *ptr = buf; while(*(ptr++)!=‘文字’) ; ptrが文字を指した状態でループを抜ける ポインタの初期化 値を取り出し比較
加算の例2 配列のx番目の要素は名前+x a[0] char a[6]; char *ptr = a a[1] a[2] a[3] a[4] a+4 a[5]
a b c \0 文字列 charの配列として扱われる • 連続した文字の並び • ヌル文字(\0)で終わる 例
a b c \0 宣言 char *buf = “abc”; bufはこの要素へのポインタ
‘ ’ と “ ” の違い • “a”は 01100001 00000000に自動変換される • ‘a’は 01100001 • 終端記号 ‘\0’ は8つの0
文字列の検査 char *buf= “abc\0xyz”; char *ptr = buf, *ptr2; While(*(ptr++) ) ; ptr2 = ptr; printf(“%s”, ptr2); 出力結果はxyzになる ‘\0’は偽なので、文字列の終端で自動的にwhileを抜ける
strlenとsizeof • strlen: 最初の\0までの長さ • sizeof:オブジェクトの大きさを求めるのに使う char *buf a b c \0 x y z \0 strlen(buf) == 3 sizeof(buf) == 8
文字列の取得 char *fgets(char *s, int size, FILE *stream); char buf[80]; … fgets(buf, sizeof(buf), stdin); buf[strlen(buf) -1] = '\0'; 改行文字も読んでしまうのでその前で終端させる buf a b c \n \0
今日の演習 • 課題1 一行の入力を空白で分割し、char *argv[n]に格納する関数split(名前はなんでも良い)をつくろう。 ただし、ポインタと文字列(終端記号)の性質を利用すること。 • 課題2 実行したコマンドを配列に保存し、特定のキーを押したら過去のコマンドのリストを表示、 実行できる機能(ヒストリー)をmicroshellに付加しよう。 % h 0) ls 1) ls -l 2) gcc mshell.c % 0 a.exe mshell.c %