プログラミング言語Cの特徴ってなんでしょうか?この質問に対して「ポインタ(Pointer)」を挙げる人はそれなりに居るでしょう。…たぶん居るよね。居ることにします。
他のプログラミング言語にもポインタに相当する概念(「参照(Reference)」と呼ばれることが多い)はありますが、とりわけC言語のポインタは難しいと言われること多いようです。時にはこんなコーディング規約を持ち出して、ポインタ使用が禁止されることもあるそうです。
(俺がコードを理解できないから)ポインタの使用は禁止する。
なんだか心の声が漏れ聞こえる気もしますが、そこは大人な対応で迎えましょう。ええまあ、この記事は大人げないんですけど。ルールがポインタを使うなと言うなら、私だってちゃんと従いますよ。*
とか&
とか危なくて使えませんよね。
準備編
ここから本題。この記事では、プログラミング言語Cでポインタを一切利用しない「ポインタレス(Pointer-less)・プログラミング」にむけた説明を行います。
といっても、ポインタはC言語のさまざまな場面にあらわれる基礎概念です。ポインタを全面禁止するという制約により、一体どれだけの影響が出るのでしょう。
ポインタレス・プログラミングを実践する前に、C言語プログラム中でポインタがいつ利用されるのか、順番におさらいしておきましょう。
main関数
では、さっそくmain
関数から書き始めましょう:
int main(int argc, char *argv[])
「こんにちは、ポインタ警察です。ちょっとそこの
argv
を見せてください。その*
は何ですか?」
はい。ごめんなさい。という訳で、main
関数は引数なしバージョンしか使えません:
int main()
{
return 0;
}
コマンドライン引数(argv
)の利用は諦めた方が良さそうです。
文字列
気を取り直して、懐かしのHello Worldでも書いて心を落ち着かせましょう。文字列(String)を出力するだけですね:
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
return 0;
}
「こんにちは、ポインタ警察です。(以下略)」
えーと、どこがダメなんでしょうか?printf
関数シグネチャ(=関数宣言)を確認してみます:
int printf(const char * format, ...);
アッ、ハイ。C言語には「文字列型」というものは存在せず、文字列は文字型(char
)の連なりにすぎません。文字列を扱うプログラムではポインタ利用は不可避です。文字列処理は諦めてください。
配列
うーん。文字列処理がダメなら、計算でもしますか。まずは配列(Array)を用意して…
int main()
{
int a[] = { 1, 2, 3, 4, 5 };
int i, sum = 0;
for (i = 0; i < 5; i++) {
sum += a[i];
「こんにちは(以下略)」
ちょ、ちょっと待ってください。どこにもポインタなんて使ってませんよ。さすがに、このコードは許されるんじゃ?
問題の箇所は、配列要素にアクセスしているa[i]
です。プログラミング言語Cの"仕様"を記述する、日本工業規格JIS X 3010:2003にはこうあります:
6.5.2.1 配列の添字付け
添字演算子[]
は,E1[E2]
が(*((E1)+(E2)))
と等価であると定義する。2項+
演算子に適用する型変換の規則によって,E1
が配列オブジェクト(又は,配列オブジェクトの先頭要素へのポインタ)であり,E2
が整数である場合,E1[E2]
はE1
のE2
番目(0 から数える。)の要素を指し示す。
お堅い表現で読みにくいですが、「配列要素へのアクセスE1[E2]
はポインタ演算*(E1+E2)
と同等」と言っています。つまりコード上でa[i]
と書いた部分は、コンパイラによって*(a+i)
と読み替えられるのです。
さて、配列アクセスもアウトになりました。ポインタレス・プログラミングの道は遠く険しい。あとポインタ警察怖い。
実践編
とまあ、ここまでの内容で本旨は果たしてしまったのですが、一応実践編もやっときます。
お題
ポインタレス・プログラムの題材として、非常に高度なプログラムを用意しました。標準入力(stdin)から2つの数値をよみとり、それら2つの値の足し算を行い、結果を標準出力(stdout)へ出力するプログラムです!
#include <stdio.h>
int main()
{
int a[2], sum;
scanf("%d %d", &a[0], &a[1]);
sum = a[0] + a[1];
printf("%d\n", sum);
return 0;
}
ポインタレス・スタイルでは、当然scanf
もprintf
も文字列も配列も使えません。この辺りをどう対処するかが重要そうですね。
ポインタレス・スタイル
もう飽きました。 TVの料理番組よろしく、完成したものがこちらになります:
#include <stdio.h>
#include <ctype.h>
int read_int()
{
int value = 0;
int ch;
for (;;) {
ch = getchar();
if (ch == EOF || !isspace(ch))
break;
}
while (ch != EOF && isdigit(ch)) {
value = value * 10 + (ch - '0');
ch = getchar();
}
return value;
}
void print_int(int value)
{
int num;
int digit;
for (num = 1; num <= value; num *= 10)
;
num /= 10;
while (0 < num) {
digit = value / num;
putchar('0' + digit);
value = value % num;
num = num / 10;
}
}
int main()
{
int a1, a2, sum;
a1 = read_int();
a2 = read_int();
sum = a1 + a2;
print_int(sum);
putchar('\n');
return 0;
}
オレはようやくのぼりはじめたばかりだからな
このはてしなく遠いポインタレス坂をよ…
続かない。
謝辞
こちらの下らない記事は、下記ツイートよりアイデアを頂きました。この場を借りて御礼申し上げます。