Tihiroのストレスフリーな生活

少しだけ頭使って後は根性

C言語のポインタとか文法的なお話

概要

C言語を学ぶ上で弊害となりやすいと噂のポインタについてのお話です。実際のところはポインタというより、C言語の文法が分かりにくいだけなんじゃ? という感じです。

ポインタと文法

の前に通常の変数について

ポインタ型との比較用に通常の変数について確認しておくと

int i; // int型の変数を宣言
i = 10; // int型の変数に値を格納

char c; // char型
c = 'c'; // char型の変数に値を格納

ですね。

ポインタ型について

肝心のポインタ型は

int* ip; // intのポインタ型の変数を宣言
ip = &i; // intのポインタ型の変数に値を格納

char* cp; // charのポインタ型
cp = &c; // charのポインタ型の変数に値を格納

となります。通常の変数との違いは

です。

このことから、ポインタ型の変数には「アドレス値」が入っていることが分かります。逆に言うと、「アドレス値」を操作したいのでポインタ型の変数に入れて扱う。というわけです。

アスタリスクの役割について

ポインタを理解するには、アスタリスクを理解する必要です。ので、アスタリスクの役割について整理したり、考えたりします。

char c = 'a'; // 宣言にアスタリスクがつかない → char型の変数「c」の宣言(と値格納)
char* cp; // 宣言にアスタリスクがつく → charのポインタ型の変数「cp」の宣言

// ポインタ型の変数にアスタリスクがつかない → アドレス値を操作する
cp = &c; // charのポインタ型の変数「cp」にchar型の変数「c」のアドレス値を格納
printf("%s", cp); // charのポインタ型の変数「cp」に格納しているアドレス値を出力

// ポインタ型の変数にアスタリスクがつく → アドレス値の指し先に対して操作する
*cp = 'b';  // charのポインタ型の変数「cp」に格納しているアドレス値の指し先の値にbを格納
printf("%s", *cp); // charのポインタ型変数「cp」に格納しているアドレス値の指し先の値を出力

な感じです。分かりにくいですよね。もっと分かりにくくすると「C言語の世界ではchar型は整数型」なので

char c = 'a';
printf("%s", c); // → aが出力

char c = 'a';
printf("%d", c); // → 97が出力

となったりします。

この辺りがつまずきやすいポイントなのでは、と感じますが、これってポインタというよりもC言語の文法とかcharの扱いとかが分かりにくいだけな印象です。ただ、それをひっくるめてポインタというのであれば、それ(ポインタが複雑かどうか)はそう(複雑ということに)なるのですが。

実行して確認してみる

理解するには実際に実行して確認するのが一番の早道であり、唯一の道かと思います。

void testInt0(void) {
    int i = 10;
    printf("i:%d\n", i); // 10が出力
}

void testInt1(void) {
    int i = 11;
    int* ip = i;
    printf("ip:%d\n", ip); // 11が出力
}

void testInt2(void) {
    int* ip = 12;
    printf("ip:%d\n", ip); // 12が出力
}

void testInt3(void) {
    int i = 13;
    int* ip = &i;
    printf("ip:%d\n", ip); // 13ではなく、数値の羅列(アドレス値の10進数表記)が出力
}

void testInt4(void) {
    int i = 14;
    int* ip = &i;
    printf("ip:%d\n", *ip); // 14が出力
}

void testInt45(void) {
    int i14 = 14;
    int* ip = &i14;
    printf("i14:%d\n", i14); // 14が出力
    printf("ip:%d\n", *ip); // 14が出力
    *ip = 15;
    printf("ip:%d\n", *ip); // 15が出力
    printf("i14:%d\n", i14); // 15が出力(これがポインタ操作の影響だ!!)
}

void testCharA(void) {
    char c = 'a';
    printf("c:%s\n", c); // 実行時に例外エラー
}

void testCharB(void) {
    char c = 'b';
    char* cp = c;
    printf("cp:%s\n", cp); // 実行時に例外エラー
}

void testCharC(void) {
    char* cp = 'c';
    printf("cp:%s\n", cp); // 実行時に例外エラー
}

void testCharD(void) {
    char c = 'd';
    char* cp = &c;
    printf("cp:%s\n", cp); // dが出力
}

void testCharE(void) {
    char c = 'e';
    char* cp = &c;
    printf("cp:%s\n", *cp); // 実行時に例外エラー
}

void testStringA001(void) {
    char* cp = "A001";
    printf("c:%s\n", cp); // A001が出力
}

void testStringB002(void) {
    char* cp;
    cp = "B002";
    printf("cp:%s\n", cp); // B002が出力
}

void testStringC003(void) {
    char* cp = "C003";
    printf("cp:%s\n", *cp); // 実行時に例外エラー
}

void testStringD004(void) {
    char* cp;
    *cp = "D004"; // コンパイルエラー(cpが初期化されていないエラー)
    printf("cp:%s\n", cp);
}

void testStringE005(void) {
    char* cp;
    cp = (char*)malloc(sizeof(char*));
    cp = "E005";
    printf("cp:%s\n", cp); // E005が出力
}

void testStringF006(void) {
    char* cp;
    cp = (char*)malloc(sizeof(char*));
    *cp = "F006";
    printf("cp:%s\n", cp); // 文字化けした文字列が出力
}

void testStringG007(void) {
    char* cp;
    cp = (char*)malloc(sizeof(char*));
    *cp = "G007";
    printf("cp:%s\n", *cp); // 実行時に例外エラー
}

想定した結果となる記述と、そうじゃない記述を見比べていると何となく理解が進みます。

まとめ

宣言時のアスタリスクは単純に「変数をポインタ型として宣言します」、変数利用時のアスタリスクは「ポインタ型変数のアドレスの指し先を利用します」ということに気が付いたときは目から鱗な感じでした。

*1:アドレス値については他のサイト様をご参照頂ければ、と思います