C言語入門
第11週
プログラミング言語Ⅰ(実習を含む。),
計算機言語Ⅰ・計算機言語演習Ⅰ,
復習
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)と呼ばれる
多言語化の仕組み
mintty の locale の設定
• mintty左上のアイコンからoption→Text
ここに
Locale: ja_JP
Cahaacter set: UTF-8
を設定して「OK」しておく
文末の「;」忘れ
• 文末に「;」を忘れると次の文字でエラーに
error1.c
int main() { printf("hello, ") printf("world¥n"); return EXIT_SUCCESS; } 4 5 6 7 8 9 10mintty + 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」が現れたことが
エラーの原因
ブロック開始終端の不整合
• 「{」が1つ多い
error2_1.c
int main() { { printf("hello, world¥n"); return EXIT_SUCCESS; } 4 5 6 7 8 9 10mintty + 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つ多い
error2_2.c
int main() { printf("hello, world¥n"); } return EXIT_SUCCESS; } 4 5 6 7 8 9mintty + 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ブロック開始終端の不整合
• 関数名のミススペル
error3.c
int main() { print("hello, world¥n"); return EXIT_SUCCESS; } 4 5 6 7 8 9mintty + 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 関数は用意されていない。
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 正:swapicall 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 と同じ符号を持つ
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 への * (ポインタ) にしておくと、この関数を使っても 与えた内容が変更されないことを ある程度保証することが出来る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.
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 * 型
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 型文字列操作とポインタ操作
ポインタを用いた文字列操作の例
• 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; }
ポインタを用いた文字列のコピーの例
• 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'
ポインタを用いた文字列のコピーの例
• 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' は 実行されない。
ポインタを用いた文字列の比較の例
• 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')になるか 異なる値が出てくるまで比較し 終了位置を比較すれば良い
ポインタを用いた文字列の比較の例
• 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文字に限定する
ポインタを用いた文字列の連結の例
• 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 をコピーする
ポインタを用いた文字列の連結の例
• 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バイト追記される
複雑なポインタの宣言
• *を付けると何になるか?
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]
要注意
ポインタ配列の初期化
• 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 型
メモリ上のどこかに配置された
文字列
配列とポインタの初期値と文字列
• 配列とポインタで扱いが異なることに注意
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セクションに用意されたデータは
書き変えてはいけない
一般にポインタに初期値として与えた
文字列定数は書き変えてはいけない
配列とポインタの初期値と文字列
• 配列とポインタで扱いが異なることに注意
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への不正な書き込みで
異常終了してしまう
演習: 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
演習: 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 から
切り出して関数化すれば良い
演習: 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
演習: 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
ポインタへのポインタ
• 関数の引数でポインタを返したい場合はポイ
ンタ変数へのポインタを用いる
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; }