3
第
3
回
トピック
1. ビット演算 1 (K&R 2.9) (~, &, |, >>, <<) 2. 多次元配列 (K&R 5.7)
3. ポインタ対多次元配列 (K&R 5.9)
4. コマンド行の引数 (K&R 5.10) (argc と argv.atoi(), atof() の利用) 5. ファイルへのアクセス (K&R 7.5) とエラー処理 (K&R 7.6)
- FILE, stdin, stdout, stderr
- fopen(), fclose(), getc(), putc(), fprintf(), fscanf(),...
3.1
ビット演算
1 (K&R 2.9) (~, &, |, >>, <<)
プログラム bit.c 1 #include <stdio.h> 2 3 int main(){ 4 unsigned int x, r1, r2, r3, r4, r5; 5 6 scanf("%d", &x); 7 r1 = x & 0177; 8 r2 = x | 0xb; 9 r3 = x & ~077; 10 r4 = x << 1; 11 r5 = x >> 2; 1213 printf("x: %u\nx & 0177: %u\nx | 0xb: %u\n" 14 "x & ~077: %u\nx << 1: %u\nx >> 2: %u\n", 15 x, r1, r2, r3, r4, r5); 16 return 0; 17 } 符号を無視して,bit の操作をす る場合,unsigned を宣言. 0 は 8 進数,0x は 16 進数を表す (K&R p.45). 実行結果 % echo 129 | bit x: 129 x & 0177: 1 x | 0xb: 139 x & ~077: 128 x << 1: 258 x >> 2: 32
3.2
多次元配列
(K&R 5.7)
プログラム multiarray1.c 1 #include <stdio.h> 2 3 int main(){ 4 5 int test[2][3] 6 = {{5, 10, 15}, {100, 200, 300}}; 7 8 int (*pt)[3] = test; 9 int i, j; 1011 for(i=0; i<2; i++) 12 for(j=0; j<3; j++)
13 printf("test[%d][%d] = %d at %p\n", 14 i, j, test[i][j], &test[i][j]); 15
16 putchar(’\n’); 17 for(i=0; i<2; i++) 18 for(j=0; j<3; j++) 19 printf("*(*(test+%d)+%d) = %d at %p\n", 20 i, j, *(*(pt+i)+j), *(pt+i)+j); 21 22 return 0; 23 } int test[][3] でもよい. 実行結果 test[0][0] = 5 at 0xbffffba0 test[0][1] = 10 at 0xbffffba4 test[0][2] = 15 at 0xbffffba8 test[1][0] = 100 at 0xbffffbac test[1][1] = 200 at 0xbffffbb0 test[1][2] = 300 at 0xbffffbb4 *(*(test+0)+0) = 5 at 0xbffffba0 *(*(test+0)+1) = 10 at 0xbffffba4 *(*(test+0)+2) = 15 at 0xbffffba8 *(*(test+1)+0) = 100 at 0xbffffbac *(*(test+1)+1) = 200 at 0xbffffbb0 *(*(test+1)+2) = 300 at 0xbffffbb4
プログラム multiarray2.c
1 #include <stdio.h> 2
3 int element(int array[][3], int, int); 4 void initialize(int (*array)[3]); 5 6 int main(){ 7 8 int test[][3] 9 = { {5, 10, 15}, {100, 200, 300}}; 10 int i, j; 11 12 for(i = 0; i < 2; i++){ 13 for(j = 0; j < 3; j++) 14 printf("%d\t", element(test, i, j)); 15 printf("\n"); 16 } 17 18 initialize(test); 19 for(i = 0; i < 2; i++){ 20 for(j = 0; j < 3; j++) 21 printf("%d\t", element(test, i, j)); 22 printf("\n"); 23 } 24 return 0; 25 } 26
27 int element(int (*multi_array)[3], int x, int y) 28 {
29 return multi_array[x][y]; 30 }
31
32 void initialize(int multi_array[][3]) 33 { 34 int i,j; 35 for(i = 0; i < 2; i++) 36 for(j = 0; j < 3; j++) 37 multi_array[i][j] = 0; 38 } f(int array[2][13]) { ... } f(int array[][13]) { ... } f(int (*array)[13]) { ... } は同等. (参考: K&R p.121, p.137) 実行結果 5 10 15 100 200 300 0 0 0 0 0 0
3.3
ポインタ対多次元配列
(K&R 5.9)
プログラム pointer-marray1.c (K&R 5.9) 1 #include <stdio.h> 2 3 int main(){ 4 5 char *name[]6 = { "Illegal month", "Jan", "Feb", "Mar" }; 7 char aname[][15]
8 = { "Illegal month", "Jan", "Feb", "Mar" }; 9 int i;
10
11 for(i = 0; i < 4; i++) 12 printf("%s, %s (%p)\n",
13 name[i], *(name + i), name + i); 14
15 for(i = 0; i < 4; i++) 16 printf("%s, %s (%p)\n",
17 aname[i], *(aname + i), aname + i); 18 19 return 0; 20 } name: Illegal month¥0 Jan¥0 Feb¥0 Mar¥0 char *name[]
={ “Illegal month“, “Jan“, “Feb“, “Mar
aname[][15]
={ “Illegal month“, “Jan“, “Feb“, “Mar aname:
Illegal month Jan Feb Mar 0123456789012345678901234567890123456789012345678
¥0 ¥0 ¥0 ¥
実行結果
Illegal month, Illegal month (0xbffffbb0) Jan, Jan (0xbffffbb4)
Feb, Feb (0xbffffbb8) Mar, Mar (0xbffffbbc)
Illegal month, Illegal month (0xbffffb70) Jan, Jan (0xbffffb7f)
Feb, Feb (0xbffffb8e) Mar, Mar (0xbffffb9d) プログラムpointer-marray2.c 1 #include <stdio.h> 2 3 int main(){ 4 5 char *name[]
6 = { "Illegal month", "Jan", "Feb", "Mar" }; 7 int i; char **ip;
8 9 ip = name; 10 for(i = 0; i < 4; i++) 11 printf("%s\n", *ip++); 12 13 return 0; 14 } name: Illegal month¥0 Jan¥0 Feb¥0 Mar¥0 ip: 実行結果 Illegal month Jan Feb Mar
3.4
コマンド行の引数
(K&R 5.10)
プログラム argument1.c
1 #include <stdio.h> 2
3 int main(int argc, char *argv[]) 4 {
5 int i; 6
7 for(i = 0; i < argc; i++)
8 printf("argv[%d] = %s\n", i, argv[i]); 9 printf("\n");
10
11 for(i = 1; i < argc; i++)
12 printf("%s%s", argv[i], (i < argc -1) ? " " : ""); 13 printf("\n");
14
15 for(i = 1; argv[i] != NULL; i++)
16 printf("%s%s", argv[i], (i < argc -1) ? " " : ""); 17 printf("\n"); 18 19 while(--argc > 0) 20 printf((argc > 1) ? "%s " : "%s", *++argv); 21 printf("\n"); 22 return 0; 23 } 演算子? K&R p.64 ヌルポインタ(null pointer) K&R p.125 「ポインタと整数は相互交換可能 ではない.ゼロ(0) は唯一の例外 である.定数ゼロはポインタへ代 入してよく,ポインタは定数ゼロ と比較することができる.記号定 数NULL は,これがポインタに対 する特別な値であることをさらに 明確に示す記号として,ゼロの代 わりによく使われる.」 実行結果
% argument1 abc ABC argv[0] = argument1 argv[1] = abc argv[2] = ABC abc ABC abc ABC abc ABC
例.% echo hello, world
argv: echo¥0 hello,¥0 world¥0 0 (NULL) プログラム argument2.c 1 #include <stdio.h> 2 #include <stdlib.h>
3 #include <math.h> /* -lm may be required. */ 4
5 int main(int argc, char *argv[]){ 6 int x, y;
7
8 if (argc != 3)
9 exit(1);
10 x = atoi(argv[1]); y = atoi(argv[2]); 11 printf("%d to the %dth power = %.0f\n", 12 x, y, pow((double) x, (double) y)); 13 return 0; 14 } キャスト(K&R p.56) 明示的な型変換のこと (型名) 式 実行結果 % argument2 2 8 2 to the 8th power = 256
3.5
ファイルへのアクセス
(K&R 7.5) とエラー処理 (K&R 7.6)
プログラム cat1.c (K&R 7.5,p.197. 一部不適切) 1 /* K&R Chapter 7.5 */ 2 #include <stdio.h> 3 4 /* cat: version 1 */ 56 int main(int argc, char *argv[]) 7 {
8 FILE *fp; /* file pointer */ 9 void filecopy(FILE *, FILE *); 10 11 if (argc == 1) 12 filecopy(stdin, stdout); 13 else 14 while (--argc > 0) 15 if ((fp = fopen(*++argv, "r")) == NULL) { 16 printf("cat: can’t open %s\n", *argv);
17 return 1; 18 } else { 19 filecopy(fp, stdout); 20 fclose(fp); 21 } 22 return 0; 23 } 24 25 /* filecopy */
26 void filecopy(FILE *ifp, FILE *ofp) 27 {
28 int c;
29 while ((c = getc(ifp)) != EOF) 30 putc(c, ofp);
31 }
stdin 標準入力 stdout 標準出力
getc(stdin) は getchar() と同じ. putc(c, stdout) は putchar(c) と 同じ.
機能
• コマンド行の引数なし … 標準入力を標準出力へ書き出す.
プログラム cat2.c (K&R 7.6,p.199) 1 /* K&R Chapter 7.6 */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 /* cat: version 2 */ 6
7 int main(int argc, char *argv[]) 8 {
9 FILE *fp; /* file pointer */ 10 void filecopy(FILE *, FILE *); 11 char *prog = argv[0];
12 13 if (argc == 1) 14 filecopy(stdin, stdout); 15 else 16 while (--argc > 0) 17 if ((fp = fopen(*++argv, "r")) == NULL) { 18 fprintf(stderr, "%s: can’t open %s\n",
19 prog, *argv); 20 exit(1); 21 } else { 22 filecopy(fp, stdout); 23 fclose(fp); 24 } 25 if (ferror(stdout)) { 26 fprintf(stderr,
27 "%s: error writing stdout\n", prog);
28 exit(2); 29 } 30 exit(0); 31 } 32 33 /* filecopy */
34 void filecopy(FILE *ifp, FILE *ofp) 35 {
36 int c;
37 while ((c = getc(ifp)) != EOF) 38 putc(c, ofp); 39 } エラーメッセージは標準エラー stderr へ. ferror(fp) ストリームfp のエラーをチェック 演習のヒント - 標準出力ではなく,ファイルに出力したい. ファイルを読込み(”r”) ではなく,書出し (”w”) モードでオープンする.(資料の 4 節に例がある.) - ファイルを 1 行ずつ読み込みたい. 標準ライブラリのfgets() (K&R 7.7, p.200) を使う.(資料の 4 節に例がある.) - 文字配列を操作したい.
文字関数全般(K&R B3, p.313) を使う.特に,strtok().(新しいバージョンの strtok r() につい
ては,資料の4 節に例がある.) また,sscanf(), sprintf() 等の関数も有用.
4
第
4
回
トピック
1. ビット演算 2 (K&R 2.9) (~, &, |, >>, <<) 2. 条件付き取込み (K&R 4.11.3) (#ifdef, #if, #endif) 3. 通用範囲 (K&R 4.4),静的変数 (K&R 4.6)
4. 分割コンパイル(separate compilation),ヘッダ・ファイル (K&R 4.5) 5. ファイルのアクセス(書き出し)
6. 字句の読み込み
7. その他で特に重要なこと(後半で説明)
動的なメモリの確保malloc(), 文字列のコピー strdup(), 構造体 struct
4.1
ビット演算 (~, &, |, >>, <<)
プログラム getbits.c (K&R 2.9, p.61)
1 #include <stdio.h> 2 #include <stdlib.h>
3 void error_message(char *);
4 unsigned getbits(unsigned , int , int); 5
6 int main(int argc, char *argv[]) 7 {
8 unsigned int x; 9 int position, n; 10
11 if (argc != 4) {
12 fprintf(stderr, "usage: %s x p n\n", argv[0]);
13 exit(1); 14 } 15 16 x = atoi(*++argv); 17 position = atoi(*++argv); 18 n = atoi(*++argv);
19 printf("result: %u\n", getbits(x, position, n)); 20 return 0;
21 } 22
23 /* getbits: get n bits from position p */ 24 unsigned getbits(unsigned x, int p, int n) 25 { 26 return (x >> (p+1-n)) & ~(~0 << n); 27 } 用例:コマンド名x p n x について,ビット位置 p から右 側(小さい方) へ n ビットを取る. 右端のビット位置が0.
実行結果 % getbits1 93 0 1 result: 1 % getbits1 93 1 1 result: 0 % getbits1 93 2 1 result: 1 % getbits1 93 4 3 result: 7 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 % getbits 93 4 3 1 1 0 1 0 1 0 1 (x >> (p+1-n)) & ~(~0 << n) 0 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 93 ~0 << 3 >> 2 0 1 1 1 0 0 0 0 ~ 0 1 1 1 0 0 0 0 &
p
x
n
4.2
条件付き取込み
(K&R 4.11.3, p.111)
- #ifdef, #if, #endif, プログラムpreprocessing1.c 1 #include <stdio.h> 2 #define DEBUG 3 4 int main() 5 { 6 #ifdef DEBUG 7 printf("DEBUG mode \n"); 8 #else 9 printf("normal mode \n"); 10 #endif 11 return 0; 12 } プログラムpreprocessing2.c 1 #include <stdio.h> 2 /* #define DEBUG */ 3 4 int main() 5 { 6 #ifdef DEBUG 7 printf("DEBUG mode \n"); 8 #else 9 printf("normal mode \n"); 10 #endif 11 return 0; 12 } 参考: cc -DDEBUG DEBUG という記号定数を定義する cc -E 前処理(プリプロセッシング) のみ実行
4.3
通用範囲
(K&R 4.4),静的変数 (K&R 4.6)
プログラムmain1.c 1 void init(); 2 3 int main() 4 { 5 init(); 6 return 0; 7 } プログラムinit1.c 1 void init() {} プログラムinit2.c1 static void init() {}
プログラムmain2.c
1 #include <stdio.h> 2 extern int value; 3 4 int main() 5 { 6 printf("%d\n", value); 7 return 0; 8 } プログラムvalue1.c 1 int value = 1; プログラムvalue2.c
1 static int value = 1;
プログラムstatic1.c 1 #include <stdio.h> 2 #define ITERATIONS 100 3 int count(void); 4 5 int main() 6 { 7 int i;
8 for(i = 0; i < ITERATIONS - 1; i++)
9 count(); 10 printf("%d\n", count()); 11 return 0; 12 } 13 14 int count(void) 15 { 16 static int x = 0; 17 return ++x; 18 } % gcc -c main1.c -Wall % gcc -c init1.c -Wall
% gcc main1.o init1.o -Wall -o test1
% gcc -c init2.c -Wall
init2.c:1: warning: ‘init’ defined but not used
% gcc main1.o init2.o -Wall -o test2
Undefined first referenced
symbol in file
init main1.o
ld: fatal: Symbol referencing errors. No output written to test2
collect2: ld returned 1 exit status
% gcc -c main2.c -Wall % gcc -c value1.c -Wall
% gcc main2.o value1.o -Wall -o test3 % test3
1
% gcc -c value2.c -Wall
value2.c:1: warning: ‘value’ defined but not used
% gcc main2.o value2.o -Wall -o test4
Undefined first referenced
symbol in file
value main2.o
ld: fatal: Symbol referencing errors. No output written to test4
collect2: ld returned 1 exit status
% gcc static1.c -o static1 -Wall % static1
100
4.4
分割コンパイル(
separate compilation),ヘッダ・ファイル (K&R 4.5)
プログラム filecopy.h
1 void filecopy(FILE *input_file, FILE *ouput_file);
プログラム filecopy.c
1 #include <stdio.h> 2 #include "filecopy.h" 3
4 /* filecopy */
5 void filecopy(FILE *ifp, FILE *ofp) 6 {
7 int c;
8 while ((c = getc(ifp)) != EOF) 9 putc(c, ofp); 10 } ヘッダ・ファイルに書くもの. - 外部に公開する文字定数 (#define) - 外部変数の extern 宣言 - 関数のプロトタイプ - 外部に公開する構造体や型の定義 等 プログラム cat main.c 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include "filecopy.h" 4
5 int main(int argc, char *argv[]) 6 {
7 FILE *fp; /* file pointer */ 8 char *prog = argv[0]; 9 10 if (argc == 1) 11 filecopy(stdin, stdout); 12 else 13 while (--argc > 0) 14 if ((fp = fopen(*++argv, "r")) == NULL) { 15 fprintf(stderr, "%s: can’t open %s\n",
16 prog, *argv); 17 exit(1); 18 } else { 19 filecopy(fp, stdout); 20 fclose(fp); 21 } 22 if (ferror(stdout)) { 23 fprintf(stderr,
24 "%s: error writing stdout\n", prog);
25 exit(2); 26 } 27 exit(0); 28 } 実行形式 オブジェクト ファイル ソース コード オブジェクト ファイル ソース コード オブジェクト ファイル ソース コード オブジェクト ファイル ソース コード オブジェクト ファイル ソース コード コンパイル, アセンブル ライブラリ リンク % ls
cat_main.c filecopy.c filecopy.h % cc -c filecopy.c
% cc -c cat_main.c % ls
cat_main.c cat_main.o filecopy.c filecopy.h filecopy.o % cc filecopy.o cat_main.o -o cat1
% ls
4.5
ファイルのアクセス(書き出し)
プログラム cp1.c 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include "filecopy.h" 45 int main(int argc, char *argv[]) 6 {
7 FILE *fp_in, *fp_out; /* file pointers */ 8 void filecopy(FILE *, FILE *);
9 char *prog = argv[0]; 10
11 if (argc != 3) { 12 fprintf(stderr,
13 "usage: %s input_file output_file\n", prog);
14 exit(1);
15 }
16
17 if ((fp_in = fopen(*++argv, "r")) == NULL) { 18 fprintf(stderr, "%s: can’t open %s\n",
19 prog, *argv);
20 exit(2);
21 }
22
23 if ((fp_out = fopen(*++argv, "w")) == NULL) { 24 fprintf(stderr, "%s: can’t create %s\n",
25 prog, *argv); 26 exit(2); 27 } 28 29 filecopy(fp_in, fp_out); 30 fclose(fp_in); 31 32 if (ferror(fp_out)) { 33 fprintf(stderr,
34 "%s: error writing %s\n", prog, *argv);
35 exit(3); 36 } 37 fclose(fp_out); 38 exit(0); 39 } % ls cp1* textsample % cat textsample
Most wild animals remain fertile until they die, or until close to that time.
% cp1 textsample tmp % ls
cp1* textsample tmp % cat tmp
Most wild animals remain fertile until they die, or until close to that time.
4.6
字句の読み込み
プログラム get integers.c 1 #include <stdio.h> 2 3 int main(){ 4 int v, n; 5 char c; 67 while((n = scanf("%d", &v)) != EOF) 8 if (n == 1) 9 printf("%d\n", v); 10 else { 11 scanf("%c", &c); 12 printf("%c\n", c); 13 } 14 return 0; 15 } 注意.scanf() は,読み込みに失敗 した場合,ストリームに文字を残 す. プログラム process line.h 1 void process_line(char *);
プログラム get each line.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include "process_line.h" 5 #define MAX_LINE 256 6 7 int main(void){ 8 char line[MAX_LINE]; 9
10 while(fgets(line, sizeof(line), stdin) != NULL) { 11 if (line[strlen(line)-1] != ’\n’) {
12 fprintf(stderr, "too long line\n");
13 exit(1); 14 } 15 process_line(line); 16 } 17 exit(0); 18 } gets() はオーバフローの可能性が あるのでfgets() を使う. (scanf(”%s”, str) も同様.) ただしfgets() は,入力行が長いと 途中までしか読み込まないので, その場合の処理が必要. 改行文字(’\n’) まで読み込まれて いれば,問題ない. strlen(s) は,s の’\0’ を除いた文 字数を返す.(K&R p.48) プログラム process line1.c 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include "process_line.h" 5 6 void process_line(char *p) 7 { 8 if (!strcmp(p, "OWARI\n")) 9 exit(0); 10 11 printf("%s", p); 12 } strcmp() は文字列が一致した場 合,0 を返す.(K&R p.202)
プログラムprocess line2.c 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include "process_line.h" 5 #define DELIM " \t\n\":;.,!?" 6 7 void process_line(char *str) 8 { 9 char *tp, *p; 10
11 tp = strtok_r( str, DELIM, &p); 12 while (tp != NULL) {
13 puts(tp);
14 tp = strtok_r( NULL, DELIM, &p);
15 } 16 } プログラムprocess line3.c 1 #include <stdio.h> 2 #include <string.h> 3 #include "process_line.h" 4 #define DELIM " \t\n\":;.,!?" 5 #define DEBUG 6 7 void process_line(char *str) 8 { 9 char *tp, *p; 10 11 #ifdef DEBUG 12 fprintf(stderr, "str: %p : %s\n", str, str); 13 #endif 14
15 tp = strtok_r(str, DELIM, &p); 16 17 #ifdef DEBUG 18 fflush(stdout); 19 fprintf(stderr, "tp : %p : %s\n", tp, tp); 20 fprintf(stderr, "p : %p : %s\n", p, p); 21 #endif 22 23 while (tp != NULL) { 24 puts(tp);
25 tp = strtok_r(NULL, DELIM, &p); 26 27 #ifdef DEBUG 28 fflush(stdout); 29 fprintf(stderr, "tp : %p : %s\n", tp, tp); 30 fprintf(stderr, "p : %p : %s\n", p, p); 31 #endif 32 } 33 }
strtok r(str, delim, &p)
文字列s から delim に含まれる文字を 区切りとして,字句(トークン, token) に分割.返り値は,切り出した字句へ のポインタ.p にはそのトークンの直後 の文字へのポインタが設定される. s が NULL なら,p から字句を探す. r h a R u m o s i t ¥n ¥0 str tp p r h a R u m o s i t ¥n ¥0 str tp p str tp p r ¥0 h a R u m o s i t ¥n ¥0 str tp p r ¥0 h a R u m o s i t ¥n ¥0 str tp p r ¥0 h a R u m o s ¥0 i t ¥n ¥0 str tp p r ¥0 h a R u m o s ¥0 i t ¥n ¥0 str tp p r ¥0 h a R u m o s ¥0 i t ¥0 ¥0 % a.out > tmp Rumor has it
str: 0xbfbfe980 : Rumor has it tp : 0xbfbfe980 : Rumor p : 0xbfbfe986 : has it tp : 0xbfbfe986 : has p : 0xbfbfe98a : it tp : 0xbfbfe98b : it p : 0xbfbfe98e : tp : 0x0 : (null) p : 0x0 : (null) % cat tmp Rumor has