ホーム » スタッフ » 斉藤徹 » 講義録 » 情報構造論 » C言語での入出力処理のおさらい

2021年6月
 12345
6789101112
13141516171819
20212223242526
27282930  

最新の投稿(電子情報)

アーカイブ

カテゴリー

C言語での入出力処理のおさらい

テストのプログラム作成の問題で、入力処理の書き方が適切でないものが多いので、基本とテクニックの解説。

scanf()の使い方

// scanf( "フォーマット" , 引数... ) ;
// データの型とフォーマット
// int       %d (10進数として入力) - Digit
//           %o (8進数として入力)  - Octal digit
//           %x (16進数として入力) - heXa-decimal digit
//        - short int %hd - half-size digit
//        - long  int %ld - long-size digit
//
// float     %f
// double    %lf - long-size float
//
// char[]    %s
// char      %c

// 基本 scanf() はポインタ渡し。文字列(char配列)は配列の先頭アドレスを渡す
int x ;
scanf( "%d" , &x ) ;  // ポインタを渡し、xの場所に書き込んでもらう。
char str[ 10 ] ;
scanf( "%s" , str ) ; // strは配列の先頭の場所。 & は不要。

通常のscanfの%d,%sなどは、データ区切りは空白や改行となる。
%s で “tohru saitoh”といった空白の入った文字列を入力するには、一工夫が必要。

printf()の使い方

// printf( "フォーマット" , 引数... ) ;
// データの型とフォーマット
// 基本は、scanf() と同じ。
// float 型の出力
//       %f 固定小数点表示 1.23 のような出力
//       %e 指数表示 1.23e+10 のような出力
//       %g 固定小数点%fと指数表示%eのどちらか
// 桁数指定
//       %5d   - 必ず5桁で出力         "  123"
//       %05d  - 5桁の空白部は0で埋める "00123"
//       %5.2f - 全体5桁、小数点以下2桁 " 1.23"
//       %5s   - 文字列を5桁で出力     "  abc"
// 左寄せ
//       %-5s  - 文字列を左寄せ5桁で出力"abc  "

scanf(),printf()は成功した項目数を返す

scanf(),printf() は、返り値を使わないように思うけど、実際は入力に成功した項目件数、出力に成功した項目件数を返す。

int x ;
char str[ 10 ] ;
int ans ;

ans = scanf( "%d%s" , &x , str ) ;
printf( "%d¥n" , ans ) ; // 入力に成功すれば2。
ans = printf( "%d%s¥n" , x , str ) ;
printf( "%d¥n" , ans ) ; // 出力に成功すれば2。

特に、ファイルからの入力であれば、途中でデータがなくなって、これ以上データが入力できない時には、scanfの返り値を用いる。

char name[ 100 ] ;
int  age ;
while( scanf( "%s%d" , name , &age ) == 2 ) {
   printf( "%s %d¥n" , name , age ) ;
}

Microsoft の scanf_s() は安全な入力関数

C言語の標準関数 scanf() の %s では、バッファオーバーフローを検出できない。Microsoft の Visual Studio の C言語では、こういった危険な scanf(“%s”) は危険なので、scanf_s() を使うようになっている。ただし、他のC言語では使えない場合が多いので要注意。

char name[ 10 ] ;
scanf( "%s" , name ) ; // バッファオーバーフロー

scanf_s( "%s" , name , sizeof( name ) ) ;
// バッファオーバーフローの心配がない

fget()とsscanf()を使った安全な入力

C言語では、1行のデータを読む場合には、gets() 関数がある。しかし、配列サイズを指定できないので、バッファオーバーフローの危険がある。一方、ファイルからの入力関数 fgets() は、入力するデータサイズを指定できるため、fgets を使うべき。

char buff[ 1000 ] ;
gets( buff ) ; // バッファオーバーフローの危険性

fgets( buff , sizeof( buff ) , stdin ) ;
// 入力に失敗すると NULLを返す

一方、入力した文字列のデータから、scanf() のようにデータを抽出する、sscanf() という関数がある。

char  string[] = "123 tohru 1.23" ;
int   x ;
char  str[ 10 ] ;
double y ;

sscanf( string , "%d%s%lf" , &x , str , &y ) ;
// x = 123 , str = "tohru" , y = 1.23 
// scanfと同様に成功した項目数3が返る。

これらを踏まえ、1行に名前と年齢のデータが記録されていて、データが入力できるだけ繰り返す処理を fgets + sscanf で書くと以下のようになる。

char buff[ 1000 ] ;
char name[ 1000 ] ;
int  age ;

while( fgets( buff , sizeof( buff ) , stdin ) != NULL ) {
   if ( sscanf( buff , "%s%d" , name , &age ) == 2 ) {
      // buffには必ず1000文字以下なので、nameが1000文字を超えることはない
      printf( "%s %d¥n" , name , age ) ;
   }
}

なお、このプログラムを動かす場合、これ以上データがない場合には、Windowsであれば “Ctrl-Z” を入力。unixやmacOSであれば”Ctrl-D”を入力すること。

その他の気づいた点

文末の「;」を忘れないで

JavaScript では、単純式の末尾の「;」は、忘れてもそれなりに正しく動く。しかし、C言語では「;」を忘れると、文法エラーになるので要注意。

1: struct A {
2:    :
3: } ;          ←この行のセミコロンを忘れると、5行目で文法エラーの表示となる。
4:
5: for( ... ) { ←この行を見ても文法エラーの原因はわからない。3行目が原因。
6:    :
7: }

局所変数やヒープメモリにはゴミデータが入っている

void foo() {
   int sum ;  ←初期化忘れ
   int array[ 3 ] = { 11 , 22 , 33 } ;
   for( int i = 0 ; i < 3 ; i++ ) {
      sum += array[ i ] ;
   }
}

局所変数やヒープメモリは、関数に入って確保やmallocで確保されるメモリ領域だけど、このメモリ領域には以前の処理で使われていたデータが残っている場合がある。こういった初期化されていないメモリ領域は、悪意を持ったプログラムがデータを盗むために使われる場合もある。必要に応じてちゃんと初期化が必要。

また、大域変数(グローバル変数)は、C言語では 0 で初期化される。(ポインタなら NULL が入っている。)