Upload
amaris
View
40
Download
5
Embed Size (px)
DESCRIPTION
モバイルプログラミング第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; - PowerPoint PPT Presentation
Citation preview
モバイルプログラミング第4回
Cプログラミングの基礎( 3 )
前回の復習
• ポインタ• 配列• 文字列
ポインタ
6
…
0 番地
1 番地
2 番地
3 番地
ptr
26
変数が入っているメモリ番地(アドレス)を表す変数
値 ptr
printf(“ptr: %x”, ptr);
ptr: 2
ポインタの使い方
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;
printf("%d\n", num);
}
出力結果
$./a.exe6
num のアドレスを num_ptr に代入
ポインタとして宣言
記述例
num_ptr の中身に値を代入
値
関数とポインタ• 値渡し call-by-value• ポインタ ( アドレス ) 渡し
関数
値
① ②
関数
値ポインタ複製
func(int a){…} func(int * a){…}
文字列や配列を関数に渡すために必要
配列
• char a[80];
• int num[20];
• int num[10][10];
// 配列の配列(二次元配列)
• char * argv[10];
// ポインタの配列
x
y
z
…
複数の要素を持つ変数
a[0]a[1]
a[2]
配列とポインタ
• 配列の名前はポインタ
• ポインタに加算すると次の要素、減算すると前の要素を指すようになる
ptr
ptr+3
char buf[4];char * ptr = buf;
buf bufptr+1ptr++
文字列1. 連続した文字の並び2. ヌル文字( \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にサンプルコード
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 解答例
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 の内容
\0l-sl
文字列を見ていって
空白に \0 を入れてポインタで次の文
字を指す
\0
argv[0] argv[1]
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 解答例
呼び出し側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 への引数
main(int argc, char *argv[]) と宣言することでmain 自体が引数を取れる
main(int argc, char * argv[]){
int i=argc;
char *cmd=argv[0]
}
main(){
fgets(…);
}
argc, argv
• どちらも自動的に計算されて main に渡される
argc = コマンドラインからの引数の数argv = 引数を表すポインタ配列argv[0] はプログラム名、 argv[1] は第一引数…
main(int argc, char * argv[])
$./a hello world
記述例#include <stdio.h>int i;
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 worldargc = 3argv[0] = ./aargv[1] = helloargv[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 未初期化データ
$nm a.exe00000000 A __dll__00401050 T _function00401055 T _main00402000 D __data_start__00402000 D _a00402004 D _b00402010 d _dw2_object_mutex.000402014 d _sjl_once.200402020 D __data_end__00403000 b .bss00403000 B __bss_start__00403010 b _sjl_fc_key.100403020 B _environ00403024 B __impure_ptr00403028 B __fmode00404114 i .idata$6
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 のモード
モード(これらを組み合わせて指定する)
r 読み込みr+ 書き込みもできるw 書き込み、ファイルの内容を初期化w+ 読み込みもできるa 追加書き込みa+ 読み込みもできるb バイナリファイルとしてオープンする
詳しくは jman の fopen を参照
FILE *fopen(char * ファイル名 , const char * モード );
記述例main(){ FILE *file; char buf[80]; file = fopen(“sample2.c", "r");
fgets(buf, sizeof(buf), file); fputs(buf, file); printf("%s", buf);
fclose(file);}
読み込み専用で開いている
書き込みは行われない
定義済みの名前
それぞれ FILE * 型変数プログラムを実行すると自動的に開かれる
stdin 標準入力(キーボード)stdout 標準出力(スクリーン)stderr 標準エラー出力(スクリーン)
ファイル記述子
• ファイルディスクリプターとも呼ぶ• 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 の構造体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’;
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ではなく、ファイルに書き込む