/* * 20191206-01-QQQQ.c * ライブラリ関数 strcpy と同じ振舞をする mystrcpy を作成しなさい */ #include <stdio.h> #include <string.h> /* strcpy を利用する */ #define EOS '\0' /* * mystrcpy * ライブラリ関数 strcpy と同じ振舞をする * strcpy ( char a[], char b[] ); * 配列 b に入っている文字列を配列 a にコピーする * => ライブラリの中身を自分で実装する事により、ライブラリ関数を理解する * * 0 1 2 3 4 * 文字列 "abc" 'a' 'b' 'c' EOS ?? * 配列 b b[0] b[1] b[2] b[3] ?? * | | | | * v v v v * 配列 a a[0] a[1] a[2] a[3] ?? */ char *mystrcpy(char *dest, char *src) { int i; for ( i = 0; src[i] != EOS; i++ ) { /* src には必ず EOS が含まれている */ /* 「文字列」としては EOS 以後は気にしない */ /* i 番目の文字を src から dest にコピーする */ dest[i] = src[i]; } dest[i] = EOS; /* 最後に EOS を忘れないように.. */ return dest; /* 関数値は、コピー先の配列の先頭アドレス */ /* dest には、src に記録されている文字列のデータが すべて記録できるサイズのものにする必要がある この約束を守らないと、「問題(メモリバイオレーション)」が起きる */ } /* * main */ #define STR_MAX 128 int main ( void ) { char s1[STR_MAX]; char s2[STR_MAX]; char s3[STR_MAX]; char s4[STR_MAX]; /* ライブラリ関数 strcpy の振舞 : 文字列から配列 */ printf ( "strcpy ( s1, \"%s\" ) = \"%s\"\n", "abc123", strcpy ( s1, "abc123" ) ); printf ( "s1 = \"%s\"\n", s1 ); /* mystrcpy の振舞 : 文字列から配列 */ printf ( "mystrcpy ( s2, \"%s\" ) = \"%s\"\n", "abc123", mystrcpy ( s2, "abc123" ) ); printf ( "s2 = \"%s\"\n", s2 ); /* ライブラリ関数 strcpy の振舞 : 配列から配列 */ printf ( "strcpy ( s3, \"%s\" ) = \"%s\"\n", s1, strcpy ( s3, s1 ) ); printf ( "s3 = \"%s\"\n", s3 ); /* mystrcpy の振舞 : 配列から配列 */ /* ** この部分を完成させなさい printf ( "strcpy ( s3, \"%s\" ) = \"%s\"\n", s1, strcpy ( s3, s1 ) ); を、ちょこちょことっと変更する.. */ printf ( "s4 = \"%s\"\n", s4 ); return 0; }
#include <stdio.h> /* 配列 : 複数の同じ型のデータを複数保持するもの これを、示すために、「配列名」が利用される 配列名は、「値」をもっているので、それを関数に渡す事できる => 関数の呼び元と呼び先で、「同じ配列の要素」を「共有」できる */ void sub ( int ia[] ) { /* ia という変数に、配列名 (iary) の持つ値が代入される */ int i; for ( i = 0; i < 10; i++ ) { /* i++ <-> i = i + 1 */ printf ( "ia[%d] = %d\n", i, ia[i] ); /* ia[i] => *(ia + (i)) */ /* => *(「なにかの値」+ (i)) */ /* => *(iary + (i)) <= 変数 ia には式 iary の値が代入されている */ /* => iary[i] => main 関数の配列の要素が参照されている */ } } int main(int argc, char *argv[]) { int iary[10]; /* 配列を宣言 ( 変数 10 個分の領域を確保 ) */ /* 個々の配列要素 iary[0], iary[1], .., iary[9] */ /* それをまとめる(?)名前(配列名:iary) が利用できる */ /* 配列名と「[式]」から、間接的に配列要素が参照できる */ int i; for ( i = 0; i < 10; i++ ) { iary[i] = i * i; /* 個々の配列要素(変数)を初期化 */ /* 「配列」そのものは、「変数」の集まり */ /* <=> 「『データ』の集まり」ではない */ /* => 個々の変数に望みデータを代入する事により、 */ /* 「『データ』の集まり」を記録する機能を担う */ } sub ( iary ); /* 関数の実引数に「配列名」を指定できる */ /* => (実引数には『式』を指定し、その式の「値」がコピーされる */ /* => 「配列名」が「なんかの『値(ポインタ値)』」を持っている */ /* 「『値』[式]」とすると「配列要素」が参照できるようなもの */ return 0; }
#include <stdio.h> /* 配列 : 複数の同じ型のデータを複数保持するもの これを、示すために、「配列名」が利用される 配列名は、「値」をもっているので、それを関数に渡す事できる => 関数の呼び元と呼び先で、「同じ配列の要素」を「共有」できる */ void square_init ( int ia[] ) { int i; for ( i = 0; i < 10; i++ ) { ia[i] = i * i; /* 「ia[i]」が「配列の要素そのもの」を参照しているので */ /* 実際に、「配列の要素(変数)」の値が変更される */ /* 配列の要素への代入ができる */ /* 呼び出し先で、呼び出し元の変数を変更する事ができる */ /* 「見えない代入」を作れ */ } } void sub ( int ia[] ) { /* ia という変数に、配列名 (iary) の持つ値が代入される */ int i; for ( i = 0; i < 10; i++ ) { /* i++ <-> i = i + 1 */ printf ( "ia[%d] = %d\n", i, ia[i] ); /* 配列要素の値を表示 */ /* 「配列要素(変数)」そのものを表示しているのか、 それとも、「配列要素(変数)」の値のコピーを表示しているかわからない */ } } int main(int argc, char *argv[]) { int iary[10]; int i; for ( i = 0; i < 10; i++ ) { iary[i] = i; } printf ( "square_init の呼び出し前\n" ); sub ( iary ); square_init ( iary ); /* 関数 square_init で配列要素を初期化 */ /* みかけ上 iary の要素に対する代入命令はみあたらない */ /* しかし、square_init の後には、要素の値が変化している */ printf ( "square_init の呼び出し後\n" ); sub ( iary ); return 0; }
#include <stdio.h> #define STR_MAX 1024 /* マクロ定義 : 以下、STR_MAX と出たら 1024 に置き換える */ /* 定数マクロの定義 : STR_MAX という「定数」を表現する */ #define EOS '\0' /* End Of String の定義 */ /* 0 1 2 3 4 ... 配列 ca ca[0] ca[1] ca[2] ca[3] ca[4] ... "abc" 'a' 'b' 'c' '\0', ?? 配列 ca に文字列 "abc" を収めるためには、 ca[0] = 'a' .. ca[3] = '\0' */ void set_abc( char ca[] ) { ca[0] = 'a'; ca[1] = 'b'; ca[2] = 'c'; ca[3] = EOS; /* (*) の時に、この部分はなにをするか ?? */ /* 不法なアクセス => 結果的に、「何か」を壊してしまう */ /* ... => 結果的にセキュリティホールの原因になり.. */ /* ... => ウィルス感染の原因 */ /* => windows update のような事が必要になる */ } int main(int argc, char *argv[] ){ char str[STR_MAX]; /* STR_MAX のサイズの文字型の配列 */ /* 配列宣言をしても、個々の要素の値は初期化されていない */ /* str に、文字列「"abc"」を設定する */ set_abc ( str ); /* 配列 str に、文字列 "abc" を代入したようにふるまう */ /* str[0] ? str[3] の四つの要素だけが変更される */ /* STR_MAX は 4 でよかった ( 4 以上であればよい ) */ /* !!! (*) STR_MAX が 3 だったら... 何がおきるか */ printf ( "str=%s\n", str ); /* 文字配列 str の中身を「文字列」として表示する */ return 0; }
#include <stdio.h> int main(int argc, char *argv[]) { int a; int b; int c; a = 10; b = 20; c = a + b; printf ( "%d + %d = %d\n", a, b, c ); return 0; }
#include <stdio.h> /* メモリモデル ? を利用して.. */ int main(int argc, char *argv[]) { int memory[10]; /* 適当なサイズ(>=3)の配列 */ /* int a; memory[0] int b; memory[1] int c; memory[2] */ memory[0] = 10; /* a = 10; */ memory[1] = 20; /* b = 20; */ memory[2] = memory[0] + memory[1]; /* c = a + b; */ printf ( "%d + %d = %d\n", memory[0], memory[1], memory[2] ); return 0; }
#include <stdio.h> /* メモリモデル ? を利用して.. */ /* 変数 a <=> memory[0] 変数 b <=> memory[1] 変数 c <=> memory[2] */ #define IA 0 #define IB 1 #define IC 2 int main(int argc, char *argv[]) { int memory[10]; /* 適当なサイズ(>=3)の配列 */ /* int a; memory[0] => memory[IA] int b; memory[1] => memory[IB] int c; memory[2] => memory[IX] */ memory[IA] = 10; /* a = 10; */ memory[IB] = 20; /* b = 20; */ memory[IC] = memory[IA] + memory[IB]; /* c = a + b; */ printf ( "%d + %d = %d\n", memory[IA], memory[IB], memory[IC] ); return 0; }
#include <stdio.h> /* メモリモデル ? を利用して.. */ /* 変数 a <=> memory[0] 変数 b <=> memory[1] 変数 c <=> memory[2] */ #define IA 0 #define IB 1 #define IC 2 #define VA memory[IA] #define VB memory[IB] #define VC memory[IC] int main(int argc, char *argv[]) { int memory[10]; /* 適当なサイズ(>=3)の配列 */ /* int a; memory[0] => memory[IA] => VA int b; memory[1] => memory[IB] => VB int c; memory[2] => memory[IX] => VC */ VA = 10; /* a = 10; */ VB = 20; /* b = 20; */ VC = VA + VB; /* c = a + b; */ printf ( "%d + %d = %d\n", VA, VB, VC ); return 0; }
#include <stdio.h> /* メモリモデル ? を利用して.. */ /* 変数 a <=> memory[0] 変数 b <=> memory[1] 変数 c <=> memory[2] */ #define IA 0 #define IB 1 #define IC 2 #define VA memory[IA] #define VB memory[IB] #define VC memory[IC] void print_number ( int v ) { printf ( "value = %d\n", v ); } int main(int argc, char *argv[]) { int memory[10]; /* 適当なサイズ(>=3)の配列 */ /* int a; memory[0] => memory[IA] => VA int b; memory[1] => memory[IB] => VB int c; memory[2] => memory[IX] => VC */ VA = 10; /* a = 10; */ VB = 20; /* b = 20; */ VC = VA + VB; /* c = a + b; */ printf ( "%d + %d = %d\n", VA, VB, VC ); print_number ( VA ); /* VA の値である 10 が渡される */ return 0; }
#include <stdio.h> /* メモリモデル ? を利用して.. */ /* 変数 a <=> memory[0] 変数 b <=> memory[1] 変数 c <=> memory[2] */ #define IA 0 #define IB 1 #define IC 2 #define VA memory[IA] #define VB memory[IB] #define VC memory[IC] void print_number ( int memory[], int index ) { printf ( "value = %d\n", memory[index] ); } int main(int argc, char *argv[]) { int memory[10]; /* 適当なサイズ(>=3)の配列 */ /* int a; memory[0] => memory[IA] => VA int b; memory[1] => memory[IB] => VB int c; memory[2] => memory[IX] => VC */ VA = 10; /* a = 10; */ VB = 20; /* b = 20; */ VC = VA + VB; /* c = a + b; */ printf ( "%d + %d = %d\n", VA, VB, VC ); print_number ( memory, IA ); /* IA = 0 : 変数 a のアドレス */ return 0; }
#include <stdio.h> /* メモリモデル ? を利用して.. */ /* 変数 a <=> memory[0] 変数 b <=> memory[1] 変数 c <=> memory[2] */ #define IA 0 #define IB 1 #define IC 2 #define VA memory[IA] #define VB memory[IB] #define VC memory[IC] void assign_value ( int memory[], int index, int value ) { memory[index] = value; /* 配列名と添え字で、配列要素が操作(代入)できる */ } void print_number ( int memory[], int index ) { printf ( "value = %d\n", memory[index] ); } int main(int argc, char *argv[]) { int memory[10]; /* 適当なサイズ(>=3)の配列 */ /* int a; memory[0] => memory[IA] => VA int b; memory[1] => memory[IB] => VB int c; memory[2] => memory[IX] => VC */ /* C 言語の普通の変数 a を メモリモデルの観点から表現すると memory[IA] になる */ VA = 10; /* a = 10; */ VB = 20; /* b = 20; */ VC = VA + VB; /* c = a + b; */ printf ( "%d + %d = %d\n", VA, VB, VC ); print_number ( memory, IA ); /* IA = 0 : 変数 a のアドレス */ /* assing_value (VA,100) という形ではない */ assign_value ( memory, IA, 100 ); /* 変数 VA に値(100)を代入 */ /* 結果的に、関数の中で memory[IA] の値が変更された */ /* 逆にメモリモデルを介して考える 変数 a の値が、関数の中で変更されている */ /* 関数の中で、「a」を変更しために、「a」の場所の情報 ( memory, IA ) を渡している */ print_number ( memory, IA ); /* IA = 0 : 変数 a のアドレス */ return 0; }
#include <stdio.h> int main(int argc, char *argv[]) { int a; /* 変数 a の宣言と同時に、メモリのある場所のセルが a の値を保持するために確保される */ /* 変数 a に対応する(メモリセルの)番地があるはず */ int b; int c; a = 10; b = 20; c = a + b; printf ( "%d + %d = %d\n", a, b, c ); printf ( "&a=%p, &b=%p, &c=%p\n", &a, &b, &c ); /* & は scanf */ /* %p は「ポインター値」の出力 */ return 0; }
#include <stdio.h> int main(int argc, char *argv[]) { int memory[10]; memory[0] = 10; memory[1] = 20; memory[2] = memory[0] + memory[1]; printf ( "%d + %d = %d\n", memory[0], memory[1], memory[2] ); printf ( "&a=%p, &b=%p, &c=%p\n", &memory[0], &memory[1], &memory[2] ); printf ( "memory=%p\n", memory ); /* memory の値は、実は、&memory[0] と同じもの */ /* 「配列名」は、「配列の先頭の要素のある場所を表す値」 */ /* memory = &memory[0] */ /* = &(*(memory+0)) */ /* = &(*(memory)) */ /* => & と * が逆演算 */ /* 変数 v */ /* *(&v) => v */ return 0; }
#include <stdio.h> void print_with_pointer ( int *pa ) { /* int *pa; という表現の意味 (pa は int 型へのポインター型 : int へのポインター型 (*pa) が int 型になる */ printf ( "pa = %p\n", pa ); /* 渡された値がそのまま表示 */ printf ( "*pa = %d\n", *pa ); /* pa は (int *) 型なので、 (*pa) は int 型 */ *pa = 100; /* ポインター値を経由して、ポインターが指す変数の値を変更できる */ } int main( int argc, char *argv[] ) { int a; a = 10; printf ( "a = %d, &a = %p\n", a, &a ); print_with_pointer ( &a ); /* 変数 a のある場所(ポインタ値)が渡される */ printf ( "a = %d, &a = %p\n", a, &a ); return 0; }
#include <stdio.h> /* 普通の変数を、サイズ 1 の配列だと思えば &, * 演算子を利用しなくても、まったく同じ事ができる [] -> * と同じ仕組み & -> 配列要素と、配列名 このようなメモリモデル的な扱いをするために、 普通の変数を、サイズ 1 の配列にするのは、面倒なので、 単純変数に対する、演算子が用意されている */ void access_with_address ( int ia[] ) { printf ( "ia[0] = %d, ia = %p\n", ia[0], ia ); ia[0] = 100; } void main(int argc, char *argv[] ) { int a[1]; a[0] = 10; printf ( "a[0] = %d, a = %p\n", a[0], a ); access_with_address ( a ); printf ( "a[0] = %d, a = %p\n", a[0], a ); }
[おしらせ] 1. 講義資料と講義内容がかなりずれる 内容の方が遅れている => 今回は、資料の更新はなくて => 以前の資料で 2. 学期末なので、そろそろ、残りのスケジュール => 次回、残りのスケジュールの話を予定している [前回(2019/11/29)の内容] 「配列名」の役割 (関数への実引数としての『関数名』) 関数の呼出し元と呼出し先の「『配列要素』の共有」 複数の値をまとめる 構造体は、いくつでも、それから、どんな組み合わせでもまとめる事ができる cf. 数学の「直積」は、「構造体」と同様な枠組み 一方 配列は、同じ型のデータの組み合わせしかできない => データの表現能力としては、構造体の方が優秀 # 構造体でも、「同じデータの組み合わせ」が可能 cf. 情報の立場 : なにか悪ければ、なにか良い(なにか良ければ、ないか悪い) => C 言語では、「配列」は、([]を利用した間接参照が可能になるので, それを直接利用して)関数の呼び元と呼び出し先が「変数(配列要素)」を「共有できる」 というメリットが生じる 「応用(配列名を利用して、配列要素を共有する)」 「文字列」の入力 # 入力 : (キーボードなどのプログラムの)外から値を得、それを変数に保持する # それを「関数で行う」=> 関数の呼び出し元の変数の値が変化する #「文字列」は、「文字配列」で表現されている # "abc" => char str[4] = { 'a', 'b', 'c', '\0' } # 関数に(文字列を保持する)文字配列を指定して、その文字配列の個々の要素に # 文字コードを代入する事に相当する 文字列の入力の典型的な方法は scanf ( "%s", str ); !!! str の前には & は不要 => scanf ( "%10s", str ); 入力する文字の個数を指定する gets ( str ); !!! あまりにも危険なので、利用不可になった fgets の利用 fgets( str, STR_MAX, stdin ); !!! stdin : 不問 !!! 比数に、配列サイズを指定する事により、 !!! 事前に用意した配列のサイズを超えて、入力がされない !!! # 入力されたデータを保持する変数が変更されないようにする [本日] # 前回(2019/11/29)の資料 「ポインター」とは何か ? メモリモデルの理解 メモリモデルにおけるアドレス(番地) => 「ポインター値」の定義 メモリ(コンピュータの主記憶) 一つのセルに 1 byte ( 8 bit ) の情報が記録できて そのセルが並んでいるものがメモリ 個々のセルには、そのセルを指す番号がつけられていて、それをそのセルの番地(アドレス)と呼ぶ 個々のセルは、(C 言語の変数と同様..) 値を保持できて 代入して、値を変更できる 代入によって、もともとセルが記録していた値は、失われる 参照して、値を取り出す事ができる 値を取り出しても、記憶されている値は失われない # 実は、「C 言語の変数」は、「(1 以上の)セル(の並び)」に対応している # char 型の変数は、1 つのセルと同じ # 32bit cpu の場合は 1 つの整数型の変数は、4 byte ( 4 x 8 = 32bit ) のセルに対応 # 「C 言語」では、整数型は、その言語が使われるシステムで、もっとも都合がよいサイズが使われるという約束 C 言語の「変数」は、メモリの「セル」 C 言語では、「変数名」を指定するが、メモリモデルでは、「番地」を指定する 代入文 (=) の 右に変数名が現れた場合は、その変数の値を意味する が、 左に変数名が現れた場合は、その変数の場所を意味して、 その場所(番地で指定された)の値を書き換えている !!! 関数の引数に「変数名」を書いても、 !!! これは、代入文の右に書いた場合と同じく、値が取り出される !!! => 変数の場所に関する情報は、関数に知る事はできない !!! => 呼び出し元の変数の値が変更できない !!! <= 変数の「場所」が分ければ、それを使って、変更可能なる メモリモデル立場 セルの内容を参照したり変更したりするためには、その番地がわかればよい C 言語の変数は、メモリのセルに対応 C 言語の変数への参照や代入は、「変数名」を経由して行われる # Cコンパイラが、「変数名」と「アドレス」の対応付けを行っている => 「変数名」が見えないところでは、「変数」を操作できない !!! 関数呼び出しでは、「変数」から「値を取り出」し、 !!! その「値」だけを、関数の仮引数変数に代入している !!! => 「変数名」がみえない関数内では、その変数を操作できない コンパイラは、 「変数名」だけでなく、「番地の情報」ももっているので、 番地から変数、変数から番地の相互の変換が可能で 「変数名」から「番地の情報」を得る演算子 : & 「番地の情報」から「変数名」を得る演算子 : * & * 「変数名」=> 「番地」 => メモリアクセス 配列名 : 配列の先頭要素のアドレスを表す 「番地」という考え方を(配列に限定せず)一般的に扱う事ができると より便利だという事ができる 変数の利用 :「変数名」=> メモリアクセス [まとめ] 「ポインター値」とは何か ? 変数のある場所(メモリモデルにおける番地)に対応している %p で表示されるのは、本当(コンピュータのメモリ)の番地が表示される + * を付けた時に、その場所にあるセル(の集まり)が参照できる cf. char * : char へのポインター型の場合 1 byte int * : int へのポインター型の場合は 4 byte だけ参照される !!! 単なるアドレスだけでなく、そのアドレスにあるデータ型に関する情報が付加されたもの !!! 同じアドレス値をもっていても、参照する型が異なれば、異なる振る舞いをする 「ポインター」とは何か ? ポインター値を持つ変数
課題プログラム内の「/*名前:ここ*/」の部分を書き換え「/*この部分を完成させなさい*/」の部分にプログラムを追加して、プログラムを完成させます。
Download : 20191206-01.c
/* * 20191206-01-QQQQ.c * ライブラリ関数 strcpy と同じ振舞をする mystrcpy を作成しなさい */ #include <stdio.h> #include <string.h> /* strcpy を利用する */ #define EOS '\0' /* * mystrcpy * ライブラリ関数 strcpy と同じ振舞をする */ char *mystrcpy(char *dest, char *src) { int i; for ( i = 0; src[i] != EOS; i++ ) { /* i 番目の文字を src から dest にコピーする */ /* ** この部分を完成させなさい */ } dest[i] = EOS; /* 最後に EOS を忘れないように.. */ return dest; /* 関数値は、コピー先の配列の先頭アドレス */ } /* * main */ #define STR_MAX 128 int main ( void ) { char s1[STR_MAX]; char s2[STR_MAX]; char s3[STR_MAX]; char s4[STR_MAX]; /* ライブラリ関数 strcpy の振舞 : 文字列から配列 */ printf ( "strcpy ( s1, \"%s\" ) = \"%s\"\n", "abc123", strcpy ( s1, "abc123" ) ); /* ** この部分を完成させなさい */ /* mystrcpy の振舞 : 文字列から配列 */ printf ( "mystrcpy ( s2, \"%s\" ) = \"%s\"\n", "abc123", mystrcpy ( s2, "abc123" ) ); printf ( "s2 = \"%s\"\n", s2 ); /* ライブラリ関数 strcpy の振舞 : 配列から配列 */ printf ( "strcpy ( s3, \"%s\" ) = \"%s\"\n", s1, strcpy ( s3, s1 ) ); printf ( "s3 = \"%s\"\n", s3 ); /* mystrcpy の振舞 : 配列から配列 */ /* ** この部分を完成させなさい */ printf ( "s4 = \"%s\"\n", s4 ); return 0; }
$ ./20191206-01-QQQQ.exe strcpy ( s1, "abc123" ) = "abc123" s1 = "abc123" mystrcpy ( s2, "abc123" ) = "abc123" s2 = "abc123" strcpy ( s3, "abc123" ) = "abc123" s3 = "abc123" mystrcpy ( s4, "abc123" ) = "abc123" s4 = "abc123" $