C言語入門

Similar documents
C言語入門

Microsoft PowerPoint - 5Chap15.ppt

Microsoft PowerPoint - CproNt02.ppt [互換モード]

kiso2-03.key

プログラミング実習I

02: 変数と標準入出力

02: 変数と標準入出力

RX ファミリ用 C/C++ コンパイラ V.1.00 Release 02 ご使用上のお願い RX ファミリ用 C/C++ コンパイラの使用上の注意事項 4 件を連絡します #pragma option 使用時の 1 または 2 バイトの整数型の関数戻り値に関する注意事項 (RXC#012) 共用

gengo1-11

PowerPoint プレゼンテーション

Microsoft PowerPoint - 09.pptx

バイオプログラミング第 1 榊原康文 佐藤健吾 慶應義塾大学理工学部生命情報学科

ファイル入出力

memo

Microsoft Word - Cプログラミング演習(12)

C 言語の式と文 C 言語の文 ( 関数の呼び出し ) printf("hello, n"); 式 a a+4 a++ a = 7 関数名関数の引数セミコロン 3 < a "hello" printf("hello") 関数の引数は () で囲み, 中に式を書く. 文 ( 式文 ) は

C プログラミング演習 1( 再 ) 2 講義では C プログラミングの基本を学び 演習では やや実践的なプログラミングを通して学ぶ

情報処理 Ⅱ 2007 年 11 月 26 日 ( 月 )

char int float double の変数型はそれぞれ 文字あるいは小さな整数 整数 実数 より精度の高い ( 数値のより大きい より小さい ) 実数 を扱う時に用いる 備考 : 基本型の説明に示した 浮動小数点 とは数値を指数表現で表す方法である 例えば は指数表現で 3 書く

コマンドラインから受け取った文字列の大文字と小文字を変換するプログラムを作成せよ 入力は 1 バイトの表示文字とし アルファベット文字以外は変換しない 1. #include <stdio.h> 2. #include <ctype.h> /*troupper,islower,isupper,tol

/*Source.cpp*/ #include<stdio.h> //printf はここでインクルードして初めて使えるようになる // ここで関数 average を定義 3 つの整数の平均値を返す double 型の関数です double average(int a,int b,int c){

Microsoft Word - Cプログラミング演習(11)

Taro-ファイル処理(公開版).jtd

プログラミング実習I

PowerPoint プレゼンテーション

プログラミング基礎

PowerPoint プレゼンテーション

問 2 ( 型変換 ) 次のプログラムを実行しても正しい結果が得られない 何が間違いかを指摘し 正しく修正せよ ただし int サイズが 2 バイト long サイズが 4 バイトの処理系での演算を仮定する #include <stdio.h> int main( void ) { int a =

JavaプログラミングⅠ

Cプログラミング1(再) 第2回

PowerPoint Presentation

Microsoft PowerPoint pptx[読み取り専用]

Java講座

PowerPoint プレゼンテーション

第1回 プログラミング演習3 センサーアプリケーション

PowerPoint プレゼンテーション

<4D F736F F D20438CBE8CEA8D758DC F0939A82C282AB2E646F63>

C言語入門

PowerPoint プレゼンテーション

プログラミングI第10回

Microsoft PowerPoint - lec10.ppt

Microsoft PowerPoint - prog04.ppt

ガイダンス

Transcription:

1 C 言語入門 第 11 週 プログラミング言語 Ⅰ( 実習を含む ), 計算機言語 Ⅰ 計算機言語演習 Ⅰ, 情報処理言語 Ⅰ( 実習を含む )

2 コンパイル時のエラーのパターン 復習

3 gcc のエラーメッセージ 環境変数 LANG を設定すると言語が変わる mintty + bash + GNU C $ LANG=C gcc error1.c error1.c: In function 'main': error1.c:7:3: error: expected ';' before 'printf' printf("world n"); ^ mintty + bash + GNU C $ LANG=ja_JP.UTF-8 gcc error1.c error1.c: 関数 main 内 : error1.c:7:3: エラー : expected ; before printf printf("world n"); ^ ロケール (locale) と呼ばれる多言語化の仕組み JM: locale (7)

4 mintty の locale の設定 mintty 左上のアイコンから option Text ここに Locale: ja_jp Cahaacter set: UTF-8 を設定して OK しておく

5 文末の ; 忘れ 文末に ; を忘れると次の文字でエラーに 4 5 6 7 8 9 10 error1.c int main() printf("hello, ") printf("world n"); return EXIT_SUCCESS; mintty + bash + GNU C $ gcc error1.c error1.c: 関数 main 内 : error1.c:7:3: エラー : expected ; before printf printf("world n"); ^ エラーが生じたのは 7 行目の 3 文字目だが エラーの原因は 6 行目の行末 C 言語では スペースや改行は人間に読み易くするための位置調整に過ぎないので printf("hello, ") printf("world n"); は printf("hello, ")printf("world n"); と同じ意味 ここに ; があるべきだが ; の前に p が現れたことがエラーの原因

6 ブロック開始終端の不整合 が 1 つ多い 4 5 6 7 8 9 10 error2_1.c int main() printf("hello, world n"); return EXIT_SUCCESS; mintty + bash + GNU C $ gcc error2_1.c error2_1.c: 関数 main 内 : error2_1.c:10:1: エラー : expected declaration or statement at end of input ^ が 1 つ足らない状態で ファイルの終端 ( 入力の終端 ) に達している

7 ブロック開始終端の不整合 が 1 つ多い 4 5 6 7 8 9 error2_2.c int main() printf("hello, world n"); return EXIT_SUCCESS; mintty + bash + GNU C $ gcc error2_2.c error2_2.c:8:3: エラー : expected identifier or ( before return return EXIT_SUCCESS; ^ error2_2.c:9:1: エラー : expected identifier or ( before token ^ エラーが生じたのは 8 行目の 3 文字目だが エラーの原因は 7 行目 が 1 つ多いので main 関数の定義が 7 行目で終わっている つまり 8~9 行目は 何もないところにいきなり以下のように書いたのと同じ 8 9 return EXIT_SUCCESS;

8 ブロック開始終端の不整合 関数名のミススペル 4 5 6 7 8 9 error3.c int main() print("hello, world n"); return EXIT_SUCCESS; mintty + bash + GNU C $ gcc error3.c /tmp/ccopikhp.o:error3.c:(.text+0x15): `print' に対する定義されていない参照です /tmp/ccopikhp.o:error3.c:(.text+0x15): 再配置がオーバーフローしないように切り詰められました : R_X86_64_PC32 ( 未定義シンボル `print' に対して ) /usr/lib/gcc/x86_64-pc-cygwin/4.8.2/../../../../x86_64-pc-cygwin/bin/ld: /tmp/ccopikhp.o: 誤った再配置アドレス 0x0 がセクション `.pdata' 内にあります /usr/lib/gcc/x86_64-pc-cygwin/4.8.2/../../../../x86_64-pc-cygwin/bin/ld: 最終リンクに失敗しました : 無効な操作です collect2: エラー : ld はステータス 1 で終了しました コンパイルではなくリンクに失敗したと言われている 分割コンパイルによる 外部関数のリンクの際 print が見つからなかった printf のつもりが print とミススペルしている 標準ライブラリに print 関数は用意されていない

ポインタ 9

講義資料第 4 週 pp.42-44., 第 9 週 p.6., 教科書 p.171. 10 call by pointer ( ポインタ渡し ) 呼び出し元の変数の内容を変更したい場合 swapi.c 訂正 2014-07-11 誤 :swap 正 :swapi void swapi(int *a, int *b) int c; c = *a; *a = *b; *b = c; void swapi(int *a, int *b); 関数 *a と *b の値を入れ替える swapi_test.c int a, b; fprintf(stderr, "a =? "); scanf("%d", &a); fprintf(stderr, "b =? "); scanf("%d", &b); swapi(&a, &b); printf("a = %d n", a); printf("b = %d n", b);

講義資料第 4 週 pp.42-44., 第 9 週 p.6., 教科書 p.171. 11 call by pointer ( ポインタ渡し ) 2 つ以上の値を返したい場合 modf.c 戻り値は 1 つしかないので 関数の引数にポインタを渡して 値を返す double modf(double x, double *iptr) *iptr = x < 0? ceil(x) : floor(x); return x < 0? *iptr - x : x - *iptr; double modf(double x, double *iptr); 関数戻り値 : x の小数部を戻り値に x の整数部を *iptr に返す戻り値は共に x と同じ符号を持つ JM: modf (3)

講義資料第 4 週 pp.42-44., 第 9 週 p.6., 教科書 p.171. 12 call by pointer ( ポインタ渡し ) 任意の長さの配列を渡したい場合 例えば文字列等 strlen.c strlen_test.c size_t strlen(const char *s) size_t len = 0; while(s[len]!= ' 0') len++; return len; size_t strlen(const char *s); 関数 ' 0' で終端された文字列の長さを返す char s[1024] = ""; fprintf(stderr, "s =? "); scanf("%1023[^ n]", &s); printf("strlen(s) = %d n", strlen(s)); 値が書き変えられては困る場合 const char への * ( ポインタ ) にしておくと この関数を使っても与えた内容が変更されないことをある程度保証することが出来る

教科書 p.308,310,314., [1] pp.240,257,261-262. 13 const 修飾子 const 型 : 変数の値を変更出来なくなる 6 7 8 9 const_test1.c const int i = 0; // 初期化は出来る int const j = 0; // const int も int const も同じ意味 i = 1; // const を付けた変数には代入出来ない j = 1; // const を付けた変数には代入出来ない const 修飾子は型修飾子 (type-qualifier) の一種型修飾子は型名の前後どちらにつけても良い mintty + bash + GNU C $ gcc const_test1.c const_test1.c: 関数 main 内 : const_test1.c:8:3: エラー : 読み取り専用変数 i への代入です i = 1; // Error: i is const ^ const_test1.c:9:3: エラー : 読み取り専用変数 j への代入です j = 1; // Error: j is const ^ 変更しようとするとコンパイル時にエラーになるので本来書き変えてはいけない値を書き変えてしまうことで生じるバグを未然に防げる

教科書 p.308,310,314., [1] pp.240,257,261-262. 14 const 修飾子 const char 型へのポインタ 6 7 8 9 10 11 const_test2.c char s[] = "hello, world"; const char *p; // ポインタの場合 *p が const になる p = s; // ポインタへは代入出来る *p = 'H'; // ポインタ指し示す先の変数には代入出来ない p[7] = 'W'; // ポインタ指し示す先の変数には代入出来ない *(char *) p = 'H'; // const のない型へ cast してやると代入出来る char const * 型 const char * 型 mintty + bash + GNU C $ gcc const_test2.c const_test2.c: 関数 main 内 : const_test2.c:9:3: エラー : 読み取り専用位置 *p への代入です *p = 'H'; // Errpr: *p is const ^ const_test2.c:10:3: エラー : 読み取り専用位置 *(p + 7u) への代入です p[7] = 'W'; // Errpr: p[x] is const ^ ただし わざわざ const を付けているということは変更してはいけない または変更しないことを前提としているのであるから普通は 特別に理由がない限り無理矢理書き変えてはいけない

教科書 p.308,310,314., [1] pp.240,257,261-262. 15 const 修飾子 char 型への const ポインタ 6 7 8 9 10 const_test3.c char s[] = "hello, world"; char *const p = s; // p が const で *p が char になる p = s; // p が const なのでポインタへは代入出来ない *p = 'H'; // ポインタ指し示す先の変数には代入出来る p[7] = 'W'; // ポインタ指し示す先の変数には代入出来る mintty + bash + GNU C $ gcc const_test3.c const_test3.c: 関数 main 内 : const_test3.c:8:3: エラー : 読み取り専用変数 p への代入です p = s; // Error: p is const ^ char * const 型 const char *p と char const *p は同じだが char * const p は意味が異なる前者は *p が const char つまり p は変数で *p が定数後者は *const p が char つまり p は定数で *p は変数である * がどこに係っているかよく考えるましょう

16 標準ライブラリ関数を例にした実例 文字列操作とポインタ操作

17 ポインタを用いた文字列操作の例 strlen 関数の大まかな仕組み strlen_with_idx.c size_t strlen(const char *s) size_t len = 0; while (s[len]!= ' 0') len++; return len; 文字列の長さは先頭から終端文字 (' 0') の手前までの文字数 strlen_with_ptr1.c size_t strlen(const char *s) const char *s0 = s; while (*s!= ' 0') s++; return s - s0; strlen_with_ptr2.c size_t strlen(const char *s) const char *s0 = s; while (*(s++)!= ' 0') ; return s - s0-1;

18 ポインタを用いた文字列のコピーの例 strcpy 関数の大まかな仕組み strcpy_with_idx.c char *strcpy(char *dst, const char *src) int i; for (i = 0; (dst[i] = src[i])!= ' 0'; i++) ; return dst; 文字列のコピーは先頭から終端文字 (' 0') までをコピーすれば良い strcpy_with_ptr.c char *strcpy(char *dst, const char *src) char *dst0 = dst; while ((*(dst++) = *(src++))!= ' 0') ; return dst0; 訂正 2014-07-04 誤 :'0' 正 :' 0'

19 ポインタを用いた文字列のコピーの例 strncpy 関数の大まかな仕組み strncpy_with_idx.c char *strncpy(char *dst, const char *src, size_t n) size_t i = 0; for (; i < n && (dst[i] = src[i])!= ' 0'; i++) ; for (; i < n; i++) dst[i] = ' 0'; return dst; strncpy_with_ptr.c char *strncpy(char *dst, const char *src, size_t n) char *dst0 = dst; while(0 < n-- && (*(dst++) = *(src++))!= ' 0') ; while(0 < n--) *(dst++) = ' 0'; return dst0; strncpy は strcpy に加えて終端文字 (' 0') 以降を ' 0' で埋める 論理演算は左から右に評価され 真偽値が確定すると評価を終了する つまり i < n や dst < dst0 + n が偽なら そこで真偽値が確定するのでそれより右にある (dst[i] = src[i])!= ' 0' や (*(dst++) = *(src++))!= ' 0' は実行されない

20 ポインタを用いた文字列の比較の例 strcmp 関数の大まかな仕組み strcmp_with_idx.c int strcmp(const char *s1, const char *s2) size_t i; for (i = 0; s1[i]!= ' 0' && s2[i]!= ' 0' && s1[i] == s2[i]; i++) ; return s1[i] - s2[i]; どちらかが終端文字 (' 0') になるか異なる値が出てくるまで比較し終了位置を比較すれば良い strcmp_with_ptr.c int strcmp(const char *s1, const char *s2) while (*s1!= ' 0' && *s2!= ' 0' && *s1 == *s2) s1++; s2++; return *s1 - *s2;

21 ポインタを用いた文字列の比較の例 strncmp 関数の大まかな仕組み strncmp_with_idx.c int strncmp(const char *s1, const char *s2, size_t n) size_t i; if (n <= 0) return 0; for (i = 0; i < n - 1 && s1[i]!= ' 0' && s2[i]!= ' 0' && s1[i] == s2[i]; i++) ; return s1[i] - s2[i]; strncmp は strcmp の比較文字数を最大 n 文字に限定する strncmp_with_ptr.c int strncmp(const char *s1, const char *s2, size_t n) if (n <= 0) return 0; while (0 < --n && *s1!= ' 0' && *s2!= ' 0' && *s1 == *s2) s1++; s2++; return *s1 - *s2;

22 ポインタを用いた文字列の連結の例 strcat 関数の大まかな仕組み strcat_with_idx.c char *strcat(char *dst, const char *src) int i, len = strlen(dst); for (i = 0; (dst[len + i] = src[i])!= ' 0'; i++) ; return dst; dst の終端位置に src をコピーする strcat_with_ptr.c char *strcat(char *dst, const char *src) char *dst0 = dst; dst += strlen(dst); while ((*(dst++) = *(src++))!= ' 0') ; return dst0;

23 ポインタを用いた文字列の連結の例 strncat 関数の大まかな仕組み strncat_with_idx.c char *strncat(char *dst, const char *src, size_t n) int i, len = strlen(dst); for (i = 0; i < n && src[i]!= ' 0'; i++) dst[len + i] = src[i]; dst[len + i] = ' 0'; return dst; strncat は strcat の連結文字を最大 n 文字に限定するただし src が n 文字以上の場合終端文字が +1 文字され合計 n+1 バイト追記される strncat_with_ptr.c char *strncat(char *dst, const char *src, size_t n) char *dst0 = dst; dst += strlen(dst); while (0 < n-- && *src!= ' 0') *(dst++) = *(src++); *dst = ' 0'; return dst0;

教科書.pp.235-239., [1]pp.148-153. 24 複雑なポインタの宣言 * を付けると何になるか? pointertest5.c int *(p1[7]); // int *p1[7]; と同義 int (*p2)[7]; printf("sizeof( p1)=%2d n", sizeof( p1)); printf("sizeof( p2)=%2d n", sizeof( p2)); printf("sizeof(*p1)=%2d n", sizeof(*p1)); printf("sizeof(*p2)=%2d n", sizeof(*p2)); mintty + bash + GNU C $ gcc pointertest5.c &&./a sizeof( p1)=56 sizeof( p2)= 8 sizeof(*p1)= 8 sizeof(*p2)=28 [] は * よりも優先順位の高い演算子 p1[x] に * が付く つまり *p1[x] が int 型になる従って p1[x] は int* 型 つまり p1 は要素数 7 の int* 型配列 p2 に * が付く つまり *p2 が要素数 7 の int 型配列になる従って p2 は 要素数 7 の int 型配列 へのポインタ 宣言の読み方 int *(p1[7]); *(p1[x]) が int int (*p2)[7]; *p2 が int[7] 要注意

教科書.pp.235-239., [1]pp.148-153. 25 ポインタ配列の初期化 char * の配列の初期化 メモリ上のどこかに配置された文字列 char *s[] = "one", "two", "three"; 0x~00 00 0x~00 o s は char* 型で要素数 3の配列 s[x] は char* 型 *s[x] は char 型 s[0] 0x~01 ~ 0x~02 ~ 0x~03 ~ 0x~04 04 0x~01 n 0x~02 e 0x~03 0 0x~04 t s[0] は "one" s[1] は "two" s[1] 0x~05 0x~06 0x~07 ~ ~ ~ 0x~05 w 0x~06 o 0x~07 0 s[0][0] は 'o' s[0][1] は 'n' s[0][2] は 'e' s[0][3] は ' 0' s[2] 0x~08 08 0x~09 ~ 0x~0a ~ 0x~0b ~ 0x~08 0x~09 0x~0a 0x~0b 0x~0c t h r e e 0x~0d 0

教科書.pp.235-239., [1]pp.148-153. 26 配列とポインタの初期値と文字列 配列とポインタで扱いが異なることに注意 pointertest6.c void sub() char s[] = "hello"; char *p = "world";... objdump の結果 セクション.rdata の内容 :... 403060 776f726c 64007320 3d202225 73220a00 world.s = "%s"..... void sub(void)... char s[] = "hello"; 401196: c7 45 ee 68 65 6c 6c movl $0x6c6c6568,-0x12(%ebp) 40119d: 66 c7 45 f2 6f 00 movw $0x6f,-0xe(%ebp) char *p = "world"; 4011a3: c7 45 f4 60 30 40 00 movl $0x403060,-0xc(%ebp)... 一般にポインタに初期値として与えた文字列定数は書き変えてはいけない.rdata セクションに用意されたデータは書き変えてはいけない s へは "hello" の文字コード 68,65,6c,6c,6f が代入されているが p へは.rdata セクションに予め用意してある文字列 "world" のアドレス 403060 が代入されている

教科書.pp.235-239., [1]pp.148-153. 27 配列とポインタの初期値と文字列 配列とポインタで扱いが異なることに注意 pointertest6.c void sub(void) char s[] = "hello"; char *p = "world"; printf("s = "%s " n", s); printf("p = "%s " n", p); s[0] = 'H'; p[0] = 'W'; int main() sub(); sub(); return EXIT_SUCCESS; Cygwin + GNU C $ gcc pointertest6.c &&./a s = "hello" p = "world" Segmentation fault ( コアダンプ ) Borland C++ >bcc32 pointertest6.c && pointertest6 Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland pointertest6.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland s = "hello" p = "world" s = "hello" p = "World" sub() が実行された際 s は毎回 "hello" だが p は 2 回目以降 "World" になってしまうもしくは.rdata への不正な書き込みで異常終了してしまう

28 演習 : print_str_with_ptr.c char 型へのポインタ変数 pを用いて char 型配列 sに保存された文字列を表示せよ 文字列末尾で改行すること 用いて良い変数はpのみとする 表示にはputchar 関数を使用すること printf 関数は使用しないこと 用いて良いループ構造はwhile 文のみとする print_str_with_ptr_tmp.c を元に指定箇所に作成せよ mintty + bash + GNU C $ gcc print_str_with_ptr.c &&./a s =? hello, world hello, world JM: putchar (3)

29 演習 : print_str.c 文字列を表示する関数 print_str(s) を実装せよ 与えられた文字列末尾で改行すること 関数のプロトタイプ宣言は myfunc_week11.h に作成せよ print_str_test.c と共にコンパイルして動作を確認すること 引数 const char *s : 文字列へのポインタ 戻り値 なし (void) mintty + bash + GNU C ヒント : print_str_with_ptr.c から切り出して関数化すれば良い $ gcc print_str_test.c print_str.c &&./a s =? hello, world hello, world

30 演習 : base36toint.c 36 進数で用いられる 0~9,A~Z,a~z までの文字を int 型の数値 0~35 に変換する関数 base36toint(c) を実装せよ 関数のプロトタイプ宣言は myfunc_week11.h に作成せよ エラーの際 DEBUG マクロが定義されていたら 標準エラー出力に警告メッセージを表示せよ base36toint_test.c と共にコンパイルして動作を確認する事 引数 int c : 0~9,A~Z,a~z までの文字コード (0x30~0x39,0x41~0x5a,0x61~0x7a) 戻り値 c で与えられた文字コードに対応する数値 0~35 を int 型で返す エラーの場合は -1 を返す mintty + bash + GNU C $ gcc base36toint_test.c base36toint.c &&./a c =? z 第 35 13 週へ移動 + 改訂

31 演習 : basetoint.c 0~9,A~Z,a~z の文字を N 進数表現の 1 桁として int 型の数値に変換する関数 basetoint(c,base) を実装せよ 関数のプロトタイプ宣言は myfunc_week11.h に作成せよ エラーの際 DEBUG マクロが定義されていたら 標準エラー出力に警告メッセージを表示せよ basetoint_test.c と共にコンパイルして動作を確認する事 引数 int c : N 進数表現の 1 桁を表す文字 0~9,A~Z,a~z の文字コード (0x30~0x39,0x41~0x5a,0x61~0x7a) int base : N 進数表現の基数 ( つまり base 進数 ) 最大 36 戻り値 c で与えられた文字コードに対応する数値 0~ 最大 35 を int 型で返す エラーの場合は -1 を返す mintty + bash + GNU C $ gcc basetoint_test.c basetoint.c &&./a c =? z base =? 10 第 -1 13 週へ移動 + 改訂

教科書 pp.243-250. 32 ポインタへのポインタ 関数の引数でポインタを返したい場合はポインタ変数へのポインタを用いる strtoui.c unsigned int strtoui(const char *s, char **endp, int base) int v; unsigned int r = 0; while (0 <= (v = basetoint(*(s++), base))) r = r * base + v; if (endp!= NULL) *endp = (char *) s; return r; main.c char s[] = "ffz"; char *endp; この例だと endp に "z" へのポインタつまり &s[2] が返ってくる printf("strtoui(s, &endp, 16);

33 参考文献 [1] B.W. カーニハン /D.M. リッチー著石田晴久訳 プログラミング言語 C 第 2 版 ANSI 規格準拠 共立出版 (1989)