• 検索結果がありません。

C言語入門

N/A
N/A
Protected

Academic year: 2021

シェア "C言語入門"

Copied!
33
0
0

読み込み中.... (全文を見る)

全文

(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)と呼ばれる

多言語化の仕組み

(4)

mintty の locale の設定

• mintty左上のアイコンからoption→Text

ここに

Locale: ja_JP

Cahaacter set: UTF-8

を設定して「OK」しておく

(5)

文末の「;」忘れ

• 文末に「;」を忘れると次の文字でエラーに

error1.c

int main() { printf("hello, ") printf("world¥n"); return EXIT_SUCCESS; } 4 5 6 7 8 9 10

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つ多い

error2_1.c

int main() { { printf("hello, world¥n"); return EXIT_SUCCESS; } 4 5 6 7 8 9 10

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 }

^

(7)

ブロック開始終端の不整合

• 「}」が1つ多い

error2_2.c

int main() { printf("hello, world¥n"); } return EXIT_SUCCESS; } 4 5 6 7 8 9

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行目は、何もないところにいきなり以下のように書いたのと同じ。

return EXIT_SUCCESS; } 8 9

(8)

ブロック開始終端の不整合

• 関数名のミススペル

error3.c

int main() { print("hello, world¥n"); return EXIT_SUCCESS; } 4 5 6 7 8 9

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)
(10)

call by pointer (ポインタ渡し)

• 呼び出し元の変数の内容を変更したい場合

swapi.c

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); 訂正2014-07-11 誤:swap 正:swapi

(11)

call by pointer (ポインタ渡し)

• 2つ以上の値を返したい場合

• 戻り値は1つしかないので、関数の引数にポイン

タを渡して、値を返す

modf.c

double modf(double x, double *iptr) {

*iptr = x < 0 ? ceil(x) : floor(x); return x < 0 ? *iptr - x : x - *iptr; }

講義資料 第4週pp.42-44., 第9週p.6., 教科書p.171.

double modf(double x, double *iptr); 関数

戻り値:

x の小数部を戻り値に

x の整数部を *iptr に返す

戻り値は共に x と同じ符号を持つ

(12)

call by pointer (ポインタ渡し)

• 任意の長さの配列を渡したい場合

• 例えば文字列等

strlen.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'で終端された文字列の長さを返す

strlen_test.c

char s[1024] = ""; fprintf(stderr, "s = ? "); scanf("%1023[^¥n]", &s); printf("strlen(s) = %d¥n", strlen(s)); 値が書き変えられては困る場合 const char への * (ポインタ) にしておくと、この関数を使っても 与えた内容が変更されないことを ある程度保証することが出来る

(13)

const 修飾子

• const 型: 変数の値を変更出来なくなる

const_test1.c

const int i = 0; // 初期化は出来る

int const j = 0; // const int も int const も同じ意味 i = 1; // const を付けた変数には代入出来ない j = 1; // const を付けた変数には代入出来ない 6 7 8 9

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 ^ const 修飾子は型修飾子(type-qualifier)の一種 型修飾子は型名の前後どちらにつけても良い 変更しようとするとコンパイル時に エラーになるので 本来書き変えてはいけない値を 書き変えてしまうことで生じるバグを 未然に防げる

教科書p.308,310,314., [1] pp.240,257,261-262.

(14)

const 修飾子

• const char 型へのポインタ

const_test2.c

char s[] = "hello, world";

const char *p; // ポインタの場合 *p が const になる p = s; // ポインタへは代入出来る

*p = 'H'; // ポインタ指し示す先の変数には代入出来ない p[7] = 'W'; // ポインタ指し示す先の変数には代入出来ない

*(char *) p = 'H'; // const のない型へ cast してやると代入出来る 6 7 8 9 10 11

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 を付けているということは 変更してはいけない、 または変更しないことを前提としているのであるから 普通は、特別に理由がない限り無理矢理書き変えてはいけない char const * 型 const char * 型

(15)

const 修飾子

• char 型への const ポインタ

const_test3.c

char s[] = "hello, world";

char *const p = s; // p が const で *p が char になる

p = s; // p が const なのでポインタへは代入出来ない *p = 'H'; // ポインタ指し示す先の変数には代入出来る p[7] = 'W'; // ポインタ指し示す先の変数には代入出来る 6 7 8 9 10

mintty + bash + GNU C

$ gcc const_test3.c const_test3.c: 関数 ‘main’ 内: const_test3.c:8:3: エラー: 読み取り専用変数 ‘p’ への代入です p = s; // Error: p is const ^ const char *p と char const *p は同じだが char * const p は意味が異なる 前者は *p が const char つまり p は変数で *p が定数 後者は *const p が char つまり p は定数で *p は変数 である * がどこに係っているか よく考えるましょう

教科書p.308,310,314., [1] pp.240,257,261-262.

char * const 型

(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; }

strcpy_with_ptr.c

char *strcpy(char *dst, const char *src) { char *dst0 = dst; while ((*(dst++) = *(src++)) != '¥0') ; return dst0; } 文字列のコピーは 先頭から終端文字('¥0')までを コピーすれば良い 訂正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]; }

strcmp_with_ptr.c

int strcmp(const char *s1, const char *s2) {

while (*s1 != '¥0' && *s2 != '¥0' && *s1 == *s2) { s1++; s2++; } return *s1 - *s2; } どちらかが終端文字('¥0')になるか 異なる値が出てくるまで比較し 終了位置を比較すれば良い

(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_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; } strncmp は strcmp の 比較文字数を最大n文字に限定する

(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; }

strcat_with_ptr.c

char *strcat(char *dst, const char *src) { char *dst0 = dst; dst += strlen(dst); while ((*(dst++) = *(src++)) != '¥0') ; return dst0; } dst の終端位置に src をコピーする

(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_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; } strncat は strcat の 連結文字を最大n文字に限定する ただしsrcがn文字以上の場合 終端文字が+1文字され 合計n+1バイト追記される

(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]

要注意

(25)

ポインタ配列の初期化

• char * の配列の初期化

char *s[] = {"one", "two", "three"};

教科書.pp.235-239., [1]pp.148-153.

0x~00 o 0x~01 n 0x~02 e 0x~03 ¥0 0x~04 t 0x~05 w 0x~06 o 0x~07 ¥0 0x~08 t 0x~09 h 0x~0a r 0x~0b e 0x~0c e 0x~0d ¥0 0x~00 00 0x~01 ~ 0x~02 ~ 0x~03 ~ 0x~04 04 0x~05 ~ 0x~06 ~ 0x~07 ~ 0x~08 08 0x~09 ~ 0x~0a ~ 0x~0b ~

s[0]

s[1]

s[2]

s[0] は "one"

s[1] は "two"

s[0][0] は 'o'

s[0][1] は 'n'

s[0][2] は 'e'

s[0][3] は '¥0'

s は char* 型で要素数3の配列

s[x] は char* 型

*s[x] は char 型

メモリ上のどこかに配置された

文字列

(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) ...

sへは"hello"の文字コード

68,65,6c,6c,6fが代入されているが

pへは.rdataセクションに予め

用意してある文字列"world"の

アドレス403060が代入されている

.rdataセクションに用意されたデータは

書き変えてはいけない

一般にポインタに初期値として与えた

文字列定数は書き変えてはいけない

(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; }

教科書.pp.235-239., [1]pp.148-153.

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

(29)

演習: print_str.c

• 文字列を表示する関数print_str(s)を実装せよ

• 与えられた文字列末尾で改行すること

• 関数のプロトタイプ宣言はmyfunc_week11.hに作成

せよ

• print_str_test.c と共にコンパイルして動作を確

認すること

• 引数

• const char *s : 文字列へのポインタ

• 戻り値

• なし(void)

mintty + bash + GNU C

$ gcc print_str_test.c print_str.c && ./a s = ? hello, world

hello, world

ヒント:

print_str_with_ptr.c から

切り出して関数化すれば良い

(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

(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

(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; printf("strtoui(s, &endp, 16); この例だと endp に "z" へのポインタ つまり&s[2]が返ってくる

(33)

参考文献

• [1] B.W.カーニハン/D.M.リッチー著 石田晴久

訳、プログラミング言語C 第2版 ANSI 規格準

拠、共立出版(1989)

参照

関連したドキュメント

それぞれの絵についてたずねる。手伝ってやったり,時には手伝わないでも,&#34;子どもが正

明治33年8月,小学校令が改正され,それま で,国語科関係では,読書,作文,習字の三教

噸狂歌の本質に基く視点としては小それが短歌形式をとる韻文であることが第一であるP三十一文字(原則として音節と対応する)を基本としへ内部が五七・五七七という文字(音節)数を持つ定形詩である。そ

管理画面へのログイン ID について 管理画面のログイン ID について、 希望の ID がある場合は備考欄にご記載下さい。アルファベット小文字、 数字お よび記号 「_ (アンダーライン)

[r]

本論文での分析は、叙述関係の Subject であれば、 Predicate に対して分配される ことが可能というものである。そして o

自然言語というのは、生得 な文法 があるということです。 生まれつき に、人 に わっている 力を って乳幼児が獲得できる言語だという え です。 語の それ自 も、 から

NPO 法人 ユーアンドアイ NPO 法人 結城まちづくり研究会 NPO 法人 よつ葉ナーサリー NPO 法人 らぽーる朋 NPO 法人 リーブルの会 NPO 法人