C言語の文字列を初期化する方法: 文字配列、文字列ポインタの初期化

352, 2021-11-26

目次

C言語の文字列を初期化する方法

C言語では文字列を扱うことができます。
このC言語の文字列は初期化することが可能です
この記事では具体的にC言語の文字列の初期化方法について解説します。

文字列の初期化はあらゆるシーンで使われる一般的な処理です。
文字列の初期化方法を知っておくのはC言語でプログラムを作る場合に有用と言えます。

この記事では具体的に↓を見ていきます。

  • 文字列の種類について

  • 文字列のポインタの初期化

  • 文字配列の初期化

関連記事
C言語の文字列の配列の使い方
C言語で文字列の長さを取得する: strlen, wcslen
C言語の文字列の切り出し関数を作る: strncpy, trim
C言語で文字列を比較する方法: strcmp, strncmp, streq
C言語の文字列を初期化する方法: 文字配列、文字列ポインタの初期化

文字列の種類について

C言語の文字列には大きく分けて↓の3種類があります。

  • 文字配列

  • 文字列定数

  • 文字列のポインタ変数

「文字配列」とは書き換えと読み取りが可能な文字列のことです
この文字列はデータセグメントあるいはスタックセグメントというメモリ領域に確保されます。
コードにすると↓の配列のことを文字配列と言います。

    char str[10];  // 文字配列

「文字列定数」とは書き換えが不可能で読み取りが可能な文字列のことを言います
この文字列はテキストセグメントというメモリ領域に確保されます。
このメモリ領域のデータは変更することができません。
コードにすると↓のダブルクオートで囲まれたものが文字列定数です。

    "Hello, World!";  // 文字列定数

「文字列のポインタ変数」とは、文字配列または文字列定数のアドレスが保存されているポインタ変数のことです
これは厳密には文字列ではなくただのポインタですが、文字列のアドレスが代入されていることで文字列と同じ振る舞いをします。
コードにすると↓のポインタ変数pのことです。

    const char *p = "Hello, World!";  // 文字列のポインタ

C言語の文字列の初期化ではこれらの文字列を意識する必要があります。

文字列のポインタの初期化

文字列のポインタの初期化について解説します。
文字列のポインタの初期化は具体的に↓の2つの方法があります。

  • 文字列のポインタに文字列定数のアドレスを代入する

  • 文字列のポインタに文字配列のアドレスを代入する

まず最初に文字列のポインタの宣言方法から見ていきます。

文字列のポインタの宣言方法

文字列のポインタの宣言char型のポインタ変数の宣言と同じです。
↓のように宣言します。

    char *p;  // 文字列のポインタの宣言

C言語では文字列を表現するのに一般的にはchar型を使います
もちろんワイド文字列を表現するwchar_tなどの型もあります。
その場合はポインタ変数はwchar_t型のポインタになります。

文字列のポインタに文字列定数のアドレスを代入する

文字列のポインタに文字列定数のアドレスを代入して初期化します
↓のように行います。

    const char *p = "Hello, World!";

文字列のポインタ変数pconstを付けています。
これは文字列定数は書き換え不能な文字列だからです。

constを付けておかないと、開発者のコーディングミスなどでpの要素に対して書き換えが起こる可能性があります
そうすると文字列定数を書き換えた場合はセグフォになるなどプログラムがクラッシュする場合があります。
それを防ぐためにポインタ変数にconstを付けて、ポインタ変数pを保護しています。

文字列のポインタに文字配列のアドレスを代入する

文字列のポインタに文字配列のアドレスを代入して初期化します
↓のように行います。

    char ary[] = "Hello, World!";  // 文字配列aryの定義
    char *p = ary;  // 文字配列aryのアドレスをpに代入

↑の場合、aryと言う変数が文字配列です。
この文字配列aryのアドレスを文字列のポインタ変数pに代入しています。

文字列のポインタpは文字配列aryと同じように扱うことができます。
ただしsizeofだけは別です。
文字列のポインタのsizeofの結果は、ポインタ変数のサイズになります
文字配列のsizeofの結果は配列全体のサイズです。
この辺は挙動が変わるので注意してください。

また関数の引数については配列のように書いても実体はポインタになります。
そのため関数の引数の文字配列にsizeofを使う場合は注意してください。

文字配列の初期化

文字配列の初期化方法について具体的に解説します。
文字配列の初期化方法は↓の通りです。

  • 文字配列を文字列定数で初期化する

  • 文字配列を初期化子リストで初期化する

  • 関数を使った初期化

文字配列の初期化で一般的なのは文字列定数を使った初期化です。
初期化子リストを使った初期化はあまり行われません。
それから関数を使った初期化も一般的と言えます。
関数を使った初期化は厳密には「初期化」とは言えませんが、ここでは広義の意味の初期化として紹介します。

文字配列を文字列定数で初期化する

文字配列を文字列定数で初期化する方法を解説します。

文字列定数「Hello」による初期化

文字配列を「Hello」という文字列定数で初期化するに↓のようにコードを書きます。

    char ary[] = "Hello";  // 文字配列を文字列定数で初期化

この初期化の場合、aryには"Hello"の文字列がコピーされます。
アドレスを代入しているわけではなくて文字列全体のコピーなので注意が必要です
配列の要素数はナル文字を考慮されて自動で確保されます

aryの要素数を確認するコードは↓になります。

    // aryの要素数を確認
    int n = sizeof(ary) / sizeof(ary[0]);
    printf("%d\n", n);  // 6

要素数は6になってます。
これは「Hello」の5文字に加えて配列の末尾にナル文字が1つ確保されているからです。

また文字配列の要素数を明示的に書くことも出来ます。

    char s[10] = "Hello";  // 要素数を明示する初期化

↑の場合は文字配列sの要素数は10確保されます。

    // sの要素数を確認
    n = sizeof(s) / sizeof(s[0]);
    printf("%d\n", n);  // 10

要素数が文字列定数の長さより少ない場合はコンパイラが警告を出す場合があります
GCCコンパイラでは↓のような警告が出力されます。

    char str[2] = "Hello";
    // GCC: warning: initializer-string for array of ‘char’ is too long

空文字列定数による初期化

空文字列定数で文字配列を初期化します。

    char ary[] = "";

この初期化を行うと、文字配列aryはナル文字だけ確保した配列になります。
printf()などで出力しても何も表示されません。

aryの使い道あるんだろうか

うーん、あるのかなぁ

文字配列を初期化子リストで初期化する

文字配列は配列なので初期化子を使った初期化が可能です。

    char ary[] = { 'H', 'e', 'l', 'l', 'o', '\0' };

↑の初期化だとaryには「Hello」という文字列がセットされます。
初期化子リストを使った初期化の注意点として、この初期化ではナル文字を明示的に指定する必要があります
↑の初期化子リストの末尾にある「\0」というのがナル文字です。
このナル文字は実際には0の値になります。

ナル文字を付加しない場合は文字列として機能しなくなるので注意が必要です
たとえばaryを文字列の長さを得るstrlen()などに渡してもバッファーオーバフローになる可能性があります
文字列定数を使った初期化の場合は自動でナル文字が付加されますが、初期化子リストを使った場合はこのように注意が必要です。
そのため文字列の初期化では初期化子リストよりも文字列定数がよく使われます。

初期化子リストを使った初期化では文字配列の要素数を明示的に書くことができます。

    char str[10] = { 'H', 'e', 'l', 'l', 'o', '\0' };

↑の場合は文字配列strは要素数10の文字配列になります。

関数を使った初期化

文字配列の関数を使った初期化について具体的に解説します。
先述の通り、関数を使った初期化は厳密には初期化ではありません。
しかし関数で文字配列を初期化するケースはC言語のプログラミングでよく行われます。
そのためここであなたに紹介します。

memset()による初期化

memsetはポインタで指定されたメモリを特定の値で埋め尽くす関数です

#include <string.h>

void *memset(void *s, int c, size_t n);

第1引数のsにはポインタを指定します。
第2引数のcには埋め尽くす値を指定します。
第3引数のnは埋め尽くすバイト数を指定します。

このmemset()を使うと文字配列を空文字列に初期化することができます
たとえば↓のように行います。

#include <string.h>
#include <stdio.h>

int main(void) {
    char s[] = "Hello";  // 文字配列の定義

    memset(s, 0, sizeof s);  // 文字配列を0埋め

    printf("s[%s]\n", s);  // s[]

    return 0;
}

↑の場合、memset()の第3引数にsizeof sを指定しています。
sizeofで文字配列を参照すると、配列全体のバイト数を得られるからです。
つまり↑のmemsetsのメモリ全体を値0で初期化していることになります。

sがポインタ変数だった場合はこのコードは機能しないので注意してください
なぜかというとsizeof sで求められるバイト数がポインタ変数のバイト数になるからです。

s0埋めされてるわけなので、その文字列の長さは0になります。
よってprintf()で出力しても何も表示されません。

strcpy()による初期化

strcpy()は第1引数のポインタに第2引数の文字列をコピーする関数です

#include <string.h>

char *strcpy(char *dest, const char *src);

この関数を使うと文字配列を特定の文字列で初期化することができます。

#include <stdio.h>
#include <string.h>

int main(void) {
    char s[10];  // 文字配列sの宣言

    strcpy(s, "Hello");  // 文字配列sにHelloをコピー

    printf("s[%s]\n", s);  // s[Hello]
    return 0;
}

↑の場合、文字配列sは初期化されていない文字配列です。
この文字配列sに「Hello」という文字列定数をコピーして初期化します。
結果はsの値は「Hello」になります。

便利なstrcpy()ですが、この関数は保存先のバッファのサイズを指定できません
そのためコーディングミスをするとバッファーオーバフローなどになる場合があります
保存先の文字配列のサイズが十分に確保されていない場合はそのような脆弱性を生む可能性があります。

よってセキュアなプログラムをC言語で作りたい場合、この関数を使うのは悩ましい問題と言えます。
基本的にプログラム的に脆弱性を防げない関数はセキュアにしたいプログラムを作る場合は使ってはいけません。
ただC言語の関数は全般的にヒューマンエラーがあった場合は脆弱性を生む傾向があります。
そのためそこまで神経質にならなくても良いという見方もあります。

安全第一

脆弱性反対

snprintf()による初期化

snprintf()は書式に従ってバッファを初期化する関数です

#include <stdio.h>

int snprintf(char *str, size_t size, const char *format, ...);

snprintf()の第1引数には保存先のバッファのポインタを指定します。
第2引数にはそのバッファのサイズを指定します。
第3引数にはフォーマット文字列を指定します。
第4引数以降にはフォーマット文字列に指定する引数を指定します。

文字配列を初期化する場合は↓のようにします。

#include <stdio.h>

int main(void) {
    char s[10];  // 文字配列sの宣言

    snprintf(s, sizeof s, "Hello %d", 10);  // 文字配列sに書式をコピー

    printf("s[%s]\n", s);  // s[Hello 10]
    return 0;
}

↑の場合、文字配列はsです。
これは要素数10の文字配列です。

この文字配列ssnprintf()で書式をコピーします。
書式は「Hello %d」で、%dには引数の10という値が展開されます。
結果は文字配列sは「Hello 10」という文字列で初期化されます。

snprintf()strcpy()に比べて、保存先バッファのサイズを指定できます。
そのためstrcpy()よりセキュアな関数と言えます
C言語の文字列処理で楽をしたい場合はこのsnprintf()の使い方を覚えるといいでしょう。

ただしsnprintf()も、実際の保存先バッファのサイズと指定したバッファのサイズが違う場合は、バッファオーバーフローになる場合があります
その点は注意が必要です。

最大の敵はヒューマンエラー

おわりに

今回はC言語の文字列の初期化について具体的に解説しました。
C言語の文字列の初期化は比較的に頻度が高い処理といえます。
文字配列、文字列定数の違いや初期化方法の違いなどを把握しておくことで高品質なC言語のプログラムを作れるでしょう。

めざせCマスター

スーパーハカーに俺はなる