Upload
sulwyn
View
52
Download
6
Embed Size (px)
DESCRIPTION
モバイルプログラミング第3回 Cプログラミングの基礎( 2 ). 前回まで: microshell. #include char command[80]; char *prompt="% "; main() { printf("%s", prompt); while(scanf("%s", command) != EOF){ if(fork() == 0){ execl(command, command,0); }else{ wait(0); - PowerPoint PPT Presentation
Citation preview
モバイルプログラミング第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); } 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 やパイプを実装するケースが多かった
今日やること 関数 ポインタ 配列 文字列
演習はこれらを応用した文字列分割とヒストリー機能
関数とは
頻繁に使うコードをまとめたもの
int func(int a, char b){
…
}
返り値(戻り値)の型
出力
関数名 引数の型と名前
入力
関数の定義int add(int a, int b){
int num;
num = a+b;
return num;
}
関数は return で正しい型の返り値を返さないといけない
呼び出し側が得られる唯一の結果
関数の宣言と呼び出し• int 型以外の返り値を持つ関数は、変数同
様宣言する必要がある
void func(int a, char b);
main(){
func(x, y);
}
void func(int a, char b){ … }
定義は別の場所でできる
宣言
宣言が無いと implicit( 暗黙に )int を返すと解釈する
返り値• int 型の返り値は定義で省略しても良い
main(){ … } 等
• 返り値は呼び出し側が無視しても OK
単に execvp(…); 等と呼び出して良い
(int が返ってるけど )
引数
int func(int a, int b){
…
}
main(){
func(x, y);
}
a, b 仮引数
(呼び出され側)
x, y 実引数(呼び出し側)
どんな値が渡されるか分からないから 「仮」
関数への入力
値
関数への引数の渡し方
• 値渡し 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 はその変数の値が変更されないことを保証する
ポインタ
ポインタ
6
…
0 番地
1 番地
2 番地
3 番地
メモリ
値 変数
a
26
変数が入っているメモリ番地を表す変数
ポインタ値
int a;
int * a_ptr;
*と&• 宣言
int * a_ptr;
• 使い方
a_ptr = & a; * a_ptr = 6 ;
&でアドレスを取得
*はポインタが指している中身を表す
宣言内の*は変数がポインタであることを表す
ポインタと中身の関係
&a
int a;
int * b;
代入できるb
* b 代入できる a
a代入不可b
&a* b 代入不可
ポインタと引数
値渡し call-by-value ポインタ ( アドレス ) 渡し
① ②func1(int a){
return a++;
}
func2(int * a){
a = “abc”;
}
実際に 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);}
出力結果
$./a.exe6622f06c$
num のアドレスを num_ptr に代入
num_ptr が指す値( =num )
ポインタとして宣言
ポインタの初期化int * a_ptr = &a; // アドレスの取得int a[10];
Int * a_ptr = a; // 配列の名前Int * a_ptr = &a[2]; // 要素のアドレス
初期化していないポインタを使うと多くの場合エラーになる
配列
char a[3];int num[20];int num[10][10]…;
a = { x, y, z };num = { {1, 2, 3, …, 10} ,
{1,2, 3, …, 10} }
x
y
z
……
複数の要素を持つ変数
a[0]a[1]
a[2]
配列の名前とポインタ
• 実際は最初の要素を指すポインタ• int * a[10] なら a == &a[0]
配列の名前の実体はポインタ
a a[0]
関数への受け渡し• 配列として受け渡せない• 配列名(ポインタ)を渡すInt buf[20];
func(buf);
int func(int * num){
}要素数などを渡す手段が必要
文字列の場合終端記号 \0 で終わりを判別
char a[3];
char b[3];
strcat(a, b);
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[]);
sum(num[1]);int sum(int * num){
for(;;)total += num[i];return total;
}
execvp(argv[o], argv);
配列とポインタへの演算
a_ptr
a_ptr+1
a_ptr-1
ポインタには加算と減算ができる
値を出すときは*( a_ptr+1 )
* a_ptr+1 だと中身に1が加わってしまう
加算の例文字列の中から特定の文字を探す
char buf[80];
char * ptr = buf;
while( * (ptr++)!=‘ 文字’ )
;
ptr が文字を指した状態でループを抜ける
ポインタの初期化
値を取り出し比較
加算の例2配列の x 番目の要素は名前+ x
char a[6];
char * ptr = a
a[0]
a[1]
a[2]
a[3]a[4]
a[5]a+4
文字列
char の配列として扱われる
1. 連続した文字の並び2. ヌル文字( \0 )で終わる
a b c \0例
宣言
char * buf = “abc”;
a b c \0
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 :オブジェクトの大きさを求めるのに使
う
a b c \0 x y z \0char * buf
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';改行文字も読んでしまうのでその前で終端させる
a b c \n \0buf
今日の演習• 課題1 一行の入力を空白で分割し、 char *argv[n] に格納する関
数 split( 名前はなんでも良い ) をつくろう。ただし、ポインタと文字列 ( 終端記号 ) の性質を利用すること。
• 課題2 実行したコマンドを配列に保存し、特定のキーを押したら過去のコマンドのリストを表示、実行できる機能 ( ヒストリー ) を microshell に付加しよう。
% h0) ls1) ls -l 2) gcc mshell.c% 0a.exe mshell.c%