12: コマンドライン引数
Linux にログインし、以下の講義ページを開いておく
こと
http://www-it.sci.waseda.ac.jp/teachers/w48369
2/CPR1/
C プログラミング入門
基幹
7 (水5)
1
2017-07-05まとめ:ポインタを使った処理
内容
説明
呼び出し元の変数を書き換える 第 9 回
文字列を渡す・配列を渡す
第 10 回
ファイルポインタ
第 10 回
複数の値を返す
第 11 回
大きな領域を確保する
第 11 回
2
今回の内容
コマンドライン引数の取り扱い
シェルから引数 (オプション) を受け取る
技術的には二重ポインタ (double pointer) である
• ポインタへのポインタ
• 秋期の「Cプログラミング」で使うが、この講義ではあ
まり踏み込まない
文字列から数値への変換
コマンドライン引数は単なる文字列なので、数値として扱
うには変換が必要
3
ポインタと文字列と配列の復習
復習:アドレスとポインタ
メモリ上の位置を表す値
型を持つ
変数 a のアドレスは
&a
ポインタ変数に格納できる
アドレス値のことをポインタとも呼ぶことがある
5
int *p 100 int a 矢印が指す位置のア ドレスを持っている { int a = 100; int *p = &a; printf("%d", *p);復習:アドレス演算と読み書き
デリファレンス演算子
*
で、アドレスが指す位置の内容を読
み書きできる
添え字演算子
[]
で、ポインタの指す位置をずらして読み書
きできる
ポインタ p にはアドレスが入っている
ポインタ p に対して p+1 は型の大きさ1つ分だけ動かした
アドレス
ポインタ p に対して *(p+n) と p[n] は同じ
6
p -23 85 メモリ上のデータをどんな値とみなすか は、ポインタの型で決まる p[0] == *(p+0) == *p == -23 p[1] == *(p+1) == 85 矢印が指す位置のア ドレスを持っている復習:配列とポインタ
配列変数名は、式中で配列の先頭へのポインタとなる
配列変数を a とすると
a
そのものがアドレス
&a[0] (0 番要素のアドレス) と同じ
&a と書いてもよい
7
p 365 a[0] == *a == p[0] == *p == 365 int a[4] それぞれ型が異なる場合があるが詳 細は省略する。ポインタに関する専 門書を参照 p = &a という代入をした場合復習:文字列(ポインタ)
文字列=「メモリ上の文字 (char 型の値) が並んでいる領
域」の先頭へのポインタ
なぜ char 型なのかは歴史的な事情による
日本語を含む場合でも、基本的には char でよい
文字列リテラルを書くと、その文字列がシステム領域に用意
され、その先頭ポインタを表す
文字列の終端は null 文字 (
'\0'
) である
なので、 null-terminated string と呼ばれる
8
'H' 'e' 'l' 'd' '!' '\n' '\0's
{ const char *s = "Hello world!\n"; 文字列データはシステム領域にある ローカル変数 ほかに、 ASCIIZ とか C string とも呼ばれる復習:文字列(配列)
配列を文字列リテラルで初期化すると、その文字数 + 1 の大
きさの配列が生成される
末尾に null 文字 (
'\0'
) が自動的に付加される
9
'H' 'e' 'l' 'd' '!' '\n' '\0' { char s[] = "Hello world!\n"; ローカル変数 char s[]コマンドライン引数処理
コマンドライン引数
シェルでコマンド名の後ろに書く文字列
ホワイトスペースで分割される (トークン化)
コマンドは受け取った引数を処理する
11
[user@host]$ gcc src.c -o src -Wall -Wextra
[user@host]$ ./src hello world
この場合、 5 個の引数を gcc というプログラムに渡している
C 言語で書いた自作のプログラムに引数を渡した
場合、どのように処理すればいいのか?
コマンドライン引数の受け取り方
main 関数として以下のプロトタイプを使う
int main(
int
argc,
char**
argv);
cf. 今までのは int main(void
);
引数名は何でもよいが慣用的に argc, argv または ac, av が
使われる
それぞれ、 argument count と argument vector (引数
の列) という意味
12
第2引数の書き方として、 • char **argv • char *argv[] のどちらでも、文法上は同じである。後者 の書き方をする人もいるので覚えておくシステムのメモリ領域
argv の内容
文字列へのポインタの配列
最後に null ポインタで終わる
13
./prog hello world 100 と実行した場合
'h' 'e' 'l' 'l' 'o' '\0' 'w' 'o' 'r' 'l' 'd' '\0' '1' '0' '0' '\0' '.' '/' 'p' 'r' 'o' 'g' '\0' argv[0] char **argv main 関数の自動変数の領域 argv[0][0] 規格では、 argv[0] に実 行したコマンドが必ず入る とは定められていないが、 多くの処理系でこうなる たとえば argv[1] が "hello" という文字列 だと思えばよい argv[1] argv[4] == NULL
システムのメモリ領域
argc の意味
null ポインタの入っている要素の番号を表す
n 個の引数を指定すると argc == n+1
14
./prog hello world 100 と実行した場合
'h' 'e' 'l' 'l' 'o' '\0' 'w' 'o' 'r' 'l' 'd' '\0' '1' '0' '0' '\0' '.' '/' 'p' 'r' 'o' 'g' '\0' argv[0] char **argv main 関数の自動変数の領域 argv[0][0] この例の場合 argc == 4 つまり、指定した引数の個数 + 1 argv[1]
例題:引数をすべて表示する
argv[i] を i = 1, ..., argc-1 まで表示
15
int main(int argc, char **argv)
{
int i;
printf("%d arguments:\n", argc-1); for(i = 0; i < argc; ++i)
{
printf("[%d] == \"%s\"\n", i, argv[i]); }
return 0; }
[user@host]$ ./arg hello 123
2 arguments [0] == "./arg" [1] == "hello" [2] == "123" argc までループさせない なぜなら、 argv[argc] == NULL なので表示でき ない プログラム名が arg の場合
例題:引数をすべて表示する (別の書き方)
argv はポインタ変数であり、直接移動させることもできる
16
int main(int argc, char **argv)
{
printf("%d arguments:\n", argc-1); for( ; *argv != NULL; ++argv)
{
printf("\"%s\"\n", *argv);
}
return 0; }
[user@host]$ ./arg hello 123
2 arguments "./arg" "hello" "123" *argv は argv[0] と同じで あり、 argv 自体を動かし ていくと、 *argv が表す文 字列が変わっていく プログラム名が arg の場合 NULL が現れるまで 動かすので、 argc は必要ない 初期化条件は空 ポインタを動かすだけでは何番目かがわからな い。必要なら変数を用意してカウントする
難しいと思う人は…
とりあえず、 argv[i] が、 i 番目の引数、と考えるだけで
OK
ただし、 i は 1 からカウント
最低限 p. 12 のプログラムが使えればよい
17
コマンドライン引数の注意
引数はあくまでも文字列である
たとえば、 100 と書いても、 "100" という文字列でしかな
い
数値として扱うには標準ライブラリ関数で変換する (次のス
ライドで説明)
引数が空文字列になる場合もある
たとえば、
./prog "" abc
と書いて実行すると、
argv[1] は空文字列、 argv[2] は "abc"
引数はシステム領域に作られるので、 (const はついていな
いが) 書き換えてはいけない
文字列から数値へ変換する
文字列→数値の変換
"100" という3文字 (+終端null) の文字列を int 型の 100
に変換したい
そうしないと、ループとか計算で使えない
代表的な2つの方法を紹介
1.
sscanf
2.
ato*, strto* 一族
20
// 例えば、 argv[1] == "3" だとして、 int i; int n = argv[1]; // 当然できない for(i = 0; i < n; ++i) { ...文字列を数値に変換する (1) sscanf
sscanf() は、文字列を解析して変数に値を書きこむ
21
int main(int argc, char **argv)
{
int i;
printf("%d arguments:\n", argc-1); for(i = 1; i < argc; ++i)
{ int v = -1; sscanf(argv[i], "%d", &v); printf("[%d] == \"%s\" (%d)\n", i, argv[i], v); } return 0; }
[user@host]$ ./arg hello 123 abc 3.14 4 arguments [1] == "hello" (-1) [2] == "123" (123) [3] == "abc" (-1) [4] == "3.14" (3) プログラム名が arg の場合 整数として 変換できな い文字列 だった場合 は何もしな い 整数として変換する 整数として変換できる ところまで使われる 変換対象の文字列
文字列を数値に変換する (1) sscanf
sscanf() は、文字列を解析して変数に値を書きこむ
22
int main(int argc, char **argv)
{
int i;
printf("%d arguments:\n", argc-1); for(i = 1; i < argc; ++i)
{ double v = -1; sscanf(argv[i], "%lf", &v); printf("[%d] == \"%s\" (%f)\n", i, argv[i], v); } return 0; }
[user@host]$ ./arg hello 123 abc 3.14 4 arguments [1] == "hello" (-1.000000) [2] == "123" (123.000000) [3] == "abc" (-1.000000) [4] == "3.14" (3.140000) プログラム名が arg の場合 double として変換する 小数の値として解釈さ れている l (エル) は不要
文字列を数値に変換する (2) 変換関数
文字列を数値に変換する関数として<stdlib.h> に以下の 2
種類がある
atox() は簡単に使えるが、変換に失敗したかどうかを判断
できない
23
関数名 変換する型 備考 atoi int 範囲外の値だった場合の戻り値は未定義。 変換に失敗した場合は 0 を返す。 (関数名は ASCII to x という意味 ASCII は文字コードのこと)atol long int atof double
strtol long int 変換に変換に失敗した場合は 0 を返す。
失敗した位置をポインタとして得られる。 strtol, strtoul は基数 (何進法表記か) を
指定する。
(関数名は string to x という意味) strtoul unsigned long int
strtod double
文字列を数値に変換する (2) 変換関数
なるべく右の strtox() を使うべき
24
{ // 整数に変換する例 int x; char *s = "2014.2"; x = atoi(s); printf("\"%s\" == %d\n", s, x); { int x; char *s = "2014.2"; char *p; x = strtol(s, &p, 10); printf("\"%s\" == %d", s, x); // 変換が完全に失敗した場合 if(str == p) { ... 変換の失敗位置が必要なければ、 NULL を渡してもよい p は変換に失敗した最初の位置である。 もし、文字列の先頭と同じなら、1文 字も解釈できなかったことになる 10進数を指定 #include <stdlib.h>int atoi(const char *str);
long strtol(const char *str, char **str_end, int base );