見出し画像

C言語教室 第37回 名前と予約語 と 第36回の解答

今でこそ変数や関数の名前に長い文字列が使えることに特に疑問を感じませんが、その昔は名前の長さにはかなり強い制限がありました。TinyBASICなんか変数名はA~Zの26個しか使えませんでしたし、8ビットな時代のBASICは長めの名前が使えるようになったものの、最初の2文字しか識別されずに、うっかりABCとAB1なんて名前を付けようものなら同じ変数になってしまいました。

さらに遡ればFORTRAN66の規格では名前の長さはシステムで保証されるのは「6文字以上」となっているので、それより長い名前を使うと危ないです。つまり大型機であっても自由にわかりやすい名前を使うことはママなりませんでした。それに基本的に大文字しか使えませんでしたしね。ですからプログラムを書く人は短い名前を選んだ上で変数名の一覧表というものを作り、そこに長い名前というか説明を書いたものです(ソースコードから使われている変数名一覧を作るソフトも愛用しました)。

UNIXにおいても、少なくともリンカに渡されるようなグローバルな名前の長さが強く制限される時代は長く続き、例えばファイルを作成する誰もが知るようなシステム関数である creat も、本当は最後に e を付けたかったんだよと、当時のエンジニアが書いていたのを見た覚えがあります。ANSIでは最低でも31文字までは使えるようになりましたし、大抵の処理系はもっと長い名前を扱えるようになったと思うのですが、極端に長い名前を使うとコンパイラは大丈夫でもデバッガでハマルことがあるので、何事も程々にです(結構、痛い目にあい続けています)。

古いC言語では慣習的に通常の名前は英語の小文字のみを使い、マクロ名はすべて大文字を使っていました。これは処理系によっては小文字しか使えなかったり、大文字と小文字を区別しないことがあったからで(すべて大文字に変換されるとか)、マクロはコンパイルに先立って展開されて大文字が無くなるので問題ありませんでした。最近では大文字と小文字を組み合わせても安心して使えるので、これを混在させた名前も普通に使われるようになりました。


さて、ここいらで、最後に前回の課題の回答があるので、まだ挑戦していない方が「うっかり解答を見てしまわないため」に、ここでお知らせしておきます。期限は設けていないので、ぜひ挑戦してみてください。もしよろしければお知らせいただければ嬉しいです。

C言語教室 第36回 論理と列挙


ちなみに名前に使える文字は基本的にAからZまでのアルファベットの大文字と小文字なのですが、先頭以外であれば数字を混ぜても大丈夫です。他の大部分の記号は演算子などに使われているので使えないのですが、例外的にアンダースコア(”_”)だけは使うことが出来ます。但し、先頭のアンダースコアはシステムが予約しており、もし使ってもエラーにならなくて使えることがあるかもしれませんが、予期しない結果を招くことになるかもしれませんので、避けることをオススメします。もちろん単語を区切るために先頭以外で使うことには問題ありません。

さて、こういった制限を除けば自由に変数や関数の名前をつけても大丈夫なのですが、もちろん例外があります。それは当たり前ですが、言語自身のキーワードで使われている if であるとか int という名前の変数や関数は作れません。これらの名前は「予約語」と呼ばれており、C言語には以下の予約語があります。

auto      break    case      char     const         continue  default   do
double   else      enum    extern   float         for            goto      if
int          long     register return  short        signed      sizeof    static
struct     switch  typedef union   unsigned void        volatile  while

これらが「単独で」「そのまま」使うことが出来ないだけで、例えば後ろに数字を続けて int1 という名前であれば大丈夫です。autoなんて普段見かけることがないので危ないですね。

これらだけを避ければ大丈夫かというと、残念ながらそんなことは無くて、処理系独自に何らかの用途で予約している単語がありますし(int32など)、またC++でCを呼び出すことがあるので、C++の予約語も避けておいたほうが無難です。ちなみにC++の予約語には以下のものが追加されています。

asm                bool                   catch                class
const_cast      delete                dynamic_cast  explicit
export            false                   friend              inline
mutable         namespace        new                 operator
private           protected           public              reinterpret_cast
static_cast      template            this                  throw
true                try                       typeid             typename
using              virtual                wchar_t

C++の予約語はじわじわ増えていて、以下のものも避けたほうが良いでしょう。

and  and_eq   bitand  bitor  compl   not   not_eq
or     or_eq     xor        xor_eq

なんだか結構ダメなものが多い感じを受けるかもしれませんが、もし使った場合にはエラーになることが多いと思う(そうとは限りませんが)ので、そこで気がつくとは思います。ローカルな名前であればエラーになってから直せば充分かもしれません。

名前をどう付けるか、どう付けるべきかという議論は果てしなく行われていますが、正解はありません。今までの教室で使ったサンプルコードを見ても、私のネーミングセンスが褒められたものではないことはすでにお気づきとは思いますが、昔の人なのと統合環境を使っていないこともあるので、あまり丁寧な名前を使わず、短い名前で済ます癖があります。基本的にプログラム全体で使うような名前は長くして、ループ内でしか使わないような名前は1文字で済ましてしまいます。長い名前が高い頻度で出るようであれば、そこはもしかしたら変数の使い方に問題があるのではないかと考えるくらいです(有効範囲が広い変数が高い頻度で使われるのはどこかがオカシイ)。


ということで、前回の課題の解答をします。

課題
列挙型を定義してから、列挙型の名前を文字列として表示する関数を書きなさい(複数の名前が同じ値を持つときは、どの名前を表示しても良いものとする)。

https://note.com/kazushinakamura/n/ncc1c7aebf58b

課題のところにもヒントを書きましたが、素直にswitch/caseを使えば以下のように書けます。

#include <stdio.h>

enum signal_color { RED, AMBER, GREEN };

const char* signal_color_name(enum signal_color color_id) {
  const char *r = "";
  switch(color_id) {
  case RED:   r = "RED";   break;
  case AMBER: r = "AMBER"; break;
  case GREEN: r = "GREEN";
  }
  return r;
}

void main() {
  enum signal_color status = GREEN;
  printf("%s\n", signal_color_name(status));
}

これだと値の名前と表示名の定義が別れてしまうので、名前と表示名の構造体で表を作って管理したくなります。

#include <stdio.h>

enum signal_color { RED, AMBER, GREEN };

struct signal_color_name {
  enum signal_color id;
  const char* name;
} name_table[] = {
  { RED,   "RED"   },
  { AMBER, "AMBER" },
  { GREEN, "GREEN" }
};

const char* signal_color_name(enum signal_color color_id) {
  const char *r = "";
  for (int i = 0; i < sizeof(name_table) / sizeof(struct signal_color_name); i++) {
    if (name_table[i].id == color_id) {
      r = name_table[i].name;
      break;
    }
  }
  return r;
}

void main() {
  enum signal_color status = GREEN;
  printf("%s\n", signal_color_name(status));
}

これでも値に対して、いちいち文字列を書くのが同じことをニ度している感じがするので、ここでプリプロセッサの「マクロ置き換え演算子(#)」に活躍してもらって少しだけ手を抜いてみます。

#include <stdio.h>

#define STR(x) #x

enum signal_color { RED, AMBER, GREEN };

struct signal_color_name {
  enum signal_color id;
  const char* name;
} name_table[] = {
  { RED,   STR(RED)   },
  { AMBER, STR(AMBER) },
  { GREEN, STR(GREEN) }
};

const char* signal_color_name(enum signal_color color_id) {
  const char *r = "";
  for (int i = 0; i < sizeof(name_table) / sizeof(struct signal_color_name); i++) {
    if (name_table[i].id == color_id) {
      r = name_table[i].name;
      break;
    }
  }
  return r;
}

void main() {
  enum signal_color status = GREEN;
  printf("%s\n", signal_color_name(status));
}

マクロ置き換え演算子というのはマクロ実引数を文字列化するもので、引き数で与えられた文字列を文字通りの文字列に展開してくれるので、REDという値を"RED"に展開してくれるものです。これでも同じ値をニ度書くには違いありませんが、ちょっとだけ見通しが良くなるかもしれません。もちろん、表示名がマクロの値そのものではなく、例えば日本語にしたいという場合は、この技は使えません。


今回の課題はありません。次回は、ちょっと余談として、関数呼び出しの仕組みを触れたいと思います。

ヘッダ画像は、いらすとや より
https://www.irasutoya.com/2013/05/blog-post_1701.html

#C言語 #プログラミング講座 #名前 #予約語 #答え合わせ #名前の長さ #C++

この記事が気に入ったらサポートをしてみませんか?