/*
* 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" $