C言語 補足 ③

ここでは、入出力について、GOTO文について、マクロについて補足の説明を行います

入出力について

C言語 応用編5日目では、ファイルへの読み書き方法を取り上げました
この章では、入出力についてもう少し踏み込んで説明をした後、標準入出力についての説明をしていきます

ストリーム

C言語では、データソースからのデータの流れを抽象化した概念としてストリームがあります
ストリームは、ファイル、キーボード、画面など、データの入出力に関わる様々な対象を統一的に扱うための仕組みです
ストリームを使うことで、効率的かつ柔軟なデータ処理が可能になります

ストリームの具体的な動作は以下のようになっています

  1. 入力ストリーム:
    • データソースからデータを読み込む場合、データの先頭から順番に、一つずつデータが読み込まれます
    • 川の上流から順番に水が流れてくるように、データも先頭から順番に処理されます
  2. 出力ストリーム:
    • データソースにデータを書き込む場合、データの末尾に一つずつデータが書き込まれます
    • 川の下流に向かって水が流れ込むように、データも末尾に向かって追加されていきます

C言語 応用編5日目で取り上げたファイル入出力では、fopen()関数でファイルを開き、fread()関数でデータを読み込み、fwrite()関数でデータを書き込んでいました
これらの関数は、ストリームを通してデータの読み書きを行っていたのです

標準入力、標準出力、標準エラー

C言語では、ストリームで入出力を行う対象をすべてファイルとして扱います
OSは、これらのファイルを管理するために、ファイルディスクリプタと呼ばれる番号を使用します
特に、標準入力、標準出力、標準エラー出力は、それぞれ以下のファイルディスクリプタ番号が割り当てられています

  • 標準入力: 0
  • 標準出力: 1
  • 標準エラー出力: 2

これらの標準入出力は、プログラムが実行される際に自動的に開かれ、特別な操作なしに利用することができます

例えば、標準出力を行うprintf関数は、ストリームの接続先が端末画面に設定されているため、プログラムで標準出力を行うと、出力する文字列が標準出力の接続先である端末画面に出力されます

標準出力

標準出力を行う標準ライブラリ関数として、printf関数以外に以下の関数が存在します

putchar: 1文字を標準出力に出力します

putchar('c'); // 文字cを出力
putchar('\n'); // 改行を出力

puts: 文字列を標準出力に出力し、最後に改行を出力します

puts("Hello");

ファイルへ出力を行うfputsfprintf関数で標準出力を行うには、stdoutという定数マクロをストリームとして指定します

fprintf(stdout, "Hello, world!\n");

標準入力

標準入力(ストリームの接続先がキーボードに設定されている)を行う標準ライブラリ関数として、以下の関数が存在します

scanf: 標準入力から書式付きのデータを読み込みます

char c;
scanf("%c",&c);

getchar: 標準入力から1文字読み込みます

int c = getchar();

gets: 標準入力から1行読み込みます

char str[256];
gets(str);

gets関数は読み込むバイト数の指定がないためバッファオーバーランの危険性があります
そのため、fgets関数の使用が推奨されています
fgets関数で標準入力から読み込むには、stdinという定数マクロをストリームとして指定します

char buffer[256];
fgets(buffer, sizeof(buffer), stdin);

標準エラー

標準エラー出力という、出力ストリームもあります
実行時エラーなどのメッセージを標準出力とは区別して書き出すために用意されています。標準エラー出力には、stderrという定数マクロを使用します

fprintf(stderr, "エラーが発生しました。\n");

ラベルとGOTO

ラベルとGOTOは、プログラムの任意の場所にジャンプするための機能です
ラベルはコロン(:)をつけた文字列でジャンプ先の位置を示します
GOTOはラベル名を指定してその場所に処理を移します

しかし、プログラムの流れを自由に変えることができる反面、使いすぎるとコードの可読性や保守性が定価する危険性があります
そのため、GOTO分は必要最小限に使用することが推奨されます

たとえば、エラーが発生した場合にそのあとの処理をスキップしてリソースの解放の処理に飛ばす場合や、多重ループ中にループから抜け出すときなどに使われています
ラベルとGOTOの使い方の例を以下に示します

1.多重ループされた文からの脱出

#include <stdio.h>

int main(void) {
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (j == 2) {
        goto end_for; // jが2のときにループを抜ける
      }
      printf(“i = %d, j = %d\n”, i, j);
    }
  }
  end_for: // ラベル
  printf(“ループを脱出しました\n”);
  return 0;
}

2.処理のリトライ

#include <stdio.h>

int main(void) {
  int a = 1;
  again: // ラベル
  if (a < 2) {
    a++;
    goto again; // aが2未満のときにラベルに戻る
  } else {
    printf(“a = %d\n”, a);
  }
  return 0;
}

3.エラーハンドリング

#include <stdio.h>

void error_handling(void) {
  FILE *fin = fopen(nothing.c, “r”); // ファイルを開く
  if (fin == NULL) {
    goto err; // ファイルが開けなかったらエラー処理にジャンプ
  }
  int c;
  while ((c = fgetc(fin)) != EOF) {
    putchar(c); // ファイルの内容を出力
  }
  fclose(fin); // ファイルを閉じる
  return; // 正常終了
  err: // ラベル
  perror(“処理に失敗しました。”); // エラーメッセージを表示
  return; // 異常終了
}

マクロについて

C言語 応用編 6日目の章でプリプロセッサについて説明をしました
そこで少し触れたマクロについて、ここで取り上げていきます

マクロとは、プリプロセッサによって、プログラム中の文字列を別の文字列に置き換える機能のことです
マクロは、コンパイルの前に行われるプリプロセッサというプログラムによって処理されます
マクロには、オブジェクト形式マクロと関数形式マクロの2種類があります

オブジェクト形式マクロ

オブジェクト形式マクロは、単純にプログラム中の識別子を指定した文字列に置き換えるマクロです

例えば、以下のように定義できます

#define PI 3.14 // 円周率を定義

このように定義すると、プログラム中のPIという識別子が、コンパイル前に全て3.14という文字列に置き換わります
このマクロの利点は、保守性や可読性が向上することです
例えば、円周率の値を変更したい場合は、マクロの定義部分だけを変えればよく、プログラム中に3.14という数字が直接書かれている場合に比べて、その数字の意味がわかりやすくなります

関数形式マクロ

関数形式マクロは、マクロ置換を行う時に引数を取ることができ、あたかも関数のように使用できるマクロです
例えば、以下のように定義できます

#define MAX(a, b) ((a) > (b) ? (a) : (b)) // 2つの値のうち大きい方を返す

このように定義すると、プログラム中のMAX(10, 20)という記述が、コンパイル前に((10) > (20) ? (10) : (20))という記述に置き換わります
このマクロの利点は、関数呼び出しのオーバーヘッドがないため、実行速度が向上する可能性があることです

マクロには、有効範囲、無効化、複数行マクロ、マクロによる分岐など注意すべき点がいくつかあります
JPCERT/CC コーディングスタンダード
こちらのサイトの「01.プリプロセッサ(PRE)」には、マクロを使用する上での注意点が列挙されていますので、参考にしてください

マクロは便利な機能ですが、使い方によってはバグやエラーの原因になることもあります
マクロの定義や使用は慎重に行いましょう

コメント

この記事へのコメントはありません。

関連記事

C言語 補足①

C言語 ~2日目~

C言語 導入編①

PAGE TOP