ポインタ
C言語

[C言語]ポインタとしての文字列

転職して業務系のSE(Java)から組み込み系PGの見習いになったので、C言語の研修で聞いた内容で、今まで自分が知らなかった部分のメモとして書きます。
勉強中ですので、経験者の方のツッコミをお待ちしております。

関数の引数にポインタを使う

文字列はchar型の配列として定義される。
配列は、連続したメモリ領域の先頭のアドレスをポインタとして持っているので、文字列はそもそもポインタとして使える。

関数の引数にポインタを使う
void print(char *str) {
    printf("%s\n", str);
}

int main(void) {
    char *pStr = "Hello";
    char arrStr[10] = "Hello";

    print(pStr);
    print(arrStr);

    return 0;
}

上記のプログラムでは、ポインタと配列をそれぞれ同じ関数に渡しているが、どちらも正常に「Hello」が出力される。
print関数の引数が配列void print(char str[])であっても、結果は同じになる。

ポインタと配列で文字列を扱うときの違い

ポインタで文字列を定義するときは、一般に文字列リテラルで初期化する。
文字列リテラルは変更不可なので、読み取り専用領域(ROM)に確保される。
※ROMに領域が確保されるかどうかはコンパイラ(処理系)に依存する。

配列で文字列を扱うときは、初期化の書き方char str[] = "String"というのは、
配列の初期化char str[] = {'S', 't', 'r', 'i', 'n', 'g', '\0'}の略なので、文字列リテラルではなく変数として書込み可能領域(RAM)に格納される。

このことから、文字列リテラルで初期化したchar型のポインタに対して、文字列の編集をしようとすると、実行時にエラーとなる。
ただし、ポインタに対して新しい文字列リテラルを割り当てることはできる。この操作は配列ではできない。

ポインタと配列の違い
int main(void) {
    char *pStr = "String";
    char arrStr[] = "String";   // 配列の初期化の簡略化

    pStr[0] = 's';  // ROMに対して書込みをしようとして実行時エラーとなる
    arrStr[0] = 's';    // RAMにあるので、実行時エラーにはならない

    pStr = "string";    // ポインタが付け換わるだけなので、エラーにはならない
                // ただし、最初の"String"はメモリに残り続ける
    arrStr = "string";  // 配列に新しい配列を入れることはできないのでコンパイルエラー

    return 0;
}

[2017/10/24 追記]
配列に対して文字列リテラルを代入する書き方は、関数の引数に対しては適用できる。これは関数の仮引数を配列として記述した場合は、その引数はポインタとして扱われるためである。

関数の引数としての配列はポインタとして扱われる
void print(char str[]) {
    str = "String"; // コンパイルエラーにはならない
    printf("%s\n", str);
}