C++におけるnull文字と空文字の適切な扱い方と区別ポイント10選

C++のnull文字C++
この記事は約20分で読めます。

※本記事のコンテンツは、利用目的を問わずご活用いただけます。実務経験10000時間以上のエンジニアが監修しており、基礎知識があれば初心者にも理解していただけるように、常に解説内容のわかりやすさや記事の品質に注力しております。不具合・分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。(理解できない部分などの個別相談も無償で承っております)
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)


●null文字とは

C++をはじめとするプログラミング言語において、文字列を扱う上で欠かせない概念の一つが”null文字”です。

初心者のエンジニアの方にとっては、少し難しく感じるかもしれません。

しかし、null文字の役割を理解することは、バグの少ないコードを書くために非常に重要なのです。

ここでは、null文字とは一体どのようなものなのか、また、それがどのような役割を果たしているのかについて、わかりやすく説明していきたいと思います。

プログラミング初心者の方も、ぜひ最後までご一読ください。

きっと、null文字への理解が深まり、C++でのプログラミングにも自信が持てるようになるはずです。

○null文字の役割

そもそもnull文字とは、文字コード0(ASCIIコードでは’\0’)を表す特殊な文字のことを指します。

C++では、この文字を文字列の終端を表すために使用します。

つまり、null文字が現れたところが、その文字列の終わりだよ、というサインになるわけです。

なぜ、このようなサインが必要なのでしょうか。

それは、C++において文字列はchar型の配列として表現されるからです。コンピュータは、配列の長さを明示的に知ることができません。

そこで、文字列の終端にnull文字を置くことで、「ここまでが文字列だよ」と教えてあげているのです。

○サンプルコード1:null文字の付加

実際に、null文字を文字列の末尾に付加する方法を見てみましょう。

#include <iostream>
#include <cstring>

int main() {
    char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
    std::cout << "文字列の長さ: " << strlen(str) << std::endl;
    std::cout << "文字列の内容: " << str << std::endl;
    return 0;
}

実行結果

文字列の長さ: 5
文字列の内容: Hello

このコードでは、文字列”Hello”をchar型の配列strに格納しています。

最後の要素には、null文字(’\0’)を明示的に代入していることに注目してください。

これにより、strlen関数は正しく文字列の長さを計算することができます。

○サンプルコード2:null文字を含む文字列の出力

次に、null文字を含む文字列を出力するコードを見てみましょう。

#include <iostream>
#include <cstring>

int main() {
    char str[10] = "Hello\0World";
    std::cout << "文字列の長さ: " << strlen(str) << std::endl;
    std::cout << "文字列の内容: " << str << std::endl;
    return 0;
}

実行結果

文字列の長さ: 5
文字列の内容: Hello

ここでは、文字列リテラル”Hello\0World”を配列strに代入しています。

null文字が途中に現れているため、strlen関数はnull文字までの長さ、つまり5を返します。

また、std::coutによる出力も、null文字までの部分、つまり”Hello”だけが表示されます。

●空文字とは

前回はnull文字について詳しく見てきましたが、今度は空文字について考えてみましょう。

空文字というのは、一体どのようなものなのでしょうか。

プログラミングを学び始めたばかりの頃は、null文字と空文字の違いがよくわからなくて困ったという経験、ありませんか。

でも、ご安心ください。

この記事を読み進めていけば、その違いがしっかりと理解できるはずです。

○空文字の定義

空文字とは、文字を1つも含まない文字列のことを指します。

C++では、長さが0の文字列を空文字と呼びます。

ややこしいですが、空文字とは「何もない」ことを表す文字列なのです。

ただし、空文字を表すための方法はいくつかあります。

例えば、下記のような方法で空文字を表現できます。

・空文字リテラル “”
・長さ0のchar型配列
・nullptr

○サンプルコード3:空文字の作成と長さの確認

では、実際にコードを書いて、空文字を作成し、その長さを確認してみましょう。

#include <iostream>
#include <cstring>

int main() {
    // 空文字リテラルによる空文字の作成
    const char* str1 = "";

    // 長さ0のchar型配列による空文字の作成
    char str2[1] = {'\0'};

    // nullptrによる空文字の作成
    const char* str3 = nullptr;

    std::cout << "str1の長さ: " << strlen(str1) << std::endl;
    std::cout << "str2の長さ: " << strlen(str2) << std::endl;
    std::cout << "str3の長さ: " << (str3 ? strlen(str3) : 0) << std::endl;

    return 0;
}

実行結果

str1の長さ: 0
str2の長さ: 0
str3の長さ: 0

このサンプルコードでは、3つの方法で空文字を作成しています。

str1は、空文字リテラル “”を使って作成しています。

str2は、長さ1のchar型配列を作成し、最初の要素にnull文字を代入することで、長さ0の文字列としています。

str3は、nullptrを使って空文字を表現しています。

strlen関数で各文字列の長さを取得すると、いずれも0になることがわかります。

ただし、str3のようにnullptrを使う場合は、そのままstrlen関数に渡すとエラーになってしまうので、三項演算子を使ってnullチェックを行っています。

●null文字と空文字の違い

さて、ここまでnull文字と空文字について個別に見てきましたが、この2つの概念の違いについて、もう少し掘り下げてみましょう。

プログラミング初心者の頃は、null文字と空文字の違いがピンとこなくて、混同してしまうことがよくあります。

でも、この違いをしっかりと理解することが、バグの少ないコードを書くために非常に大切なのです。

○null文字と空文字の比較

null文字と空文字の最も大きな違いをみてみましょう。

null文字は、文字列の終端を表すために使用される特別な文字(’\0’)です。

一方、空文字は文字を1つも含まない文字列のことを指します。

つまり、null文字は文字列の一部として存在しますが、空文字はそもそも文字列の長さが0であるということです。

ここで、もう少し具体的に見てみましょう。

null文字を含む文字列は、次のように表現できます。

"Hello\0World"

この文字列の長さは、null文字までの5文字です。

null文字以降の”World”は、文字列の一部とはみなされません。

一方、空文字は次のように表現できます。

""

この文字列の長さは0です。

○サンプルコード4:null文字と空文字の比較

では、実際にコードを書いて、null文字と空文字の違いを確認してみましょう。

#include <iostream>
#include <cstring>

int main() {
    // null文字を含む文字列
    char str1[] = "Hello\0World";

    // 空文字
    char str2[] = "";

    std::cout << "str1の長さ: " << strlen(str1) << std::endl;
    std::cout << "str1の内容: " << str1 << std::endl;

    std::cout << "str2の長さ: " << strlen(str2) << std::endl;
    std::cout << "str2の内容: " << str2 << std::endl;

    return 0;
}

実行結果

str1の長さ: 5
str1の内容: Hello
str2の長さ: 0
str2の内容: 

str1はnull文字を含む文字列ですが、strlen関数ではnull文字までの長さ(5)が返されています。

また、cout << str1 でも、null文字までの部分だけが出力されています。

一方、str2は空文字です。

strlen関数の結果は0で、cout << str2 では何も出力されません。

●C言語とC++でのNULLの違い

ここまでnull文字について詳しく見てきましたが、C言語とC++でのNULLの扱いについても触れておく必要がありますね。

特にC++の基礎は学んだものの、C言語の知識もあるという方は、この違いについて気になるところではないでしょうか。

C言語とC++は共通点が多いプログラミング言語ですが、NULLの定義については微妙な差異があります。

この違いを理解しておくことで、より安全で移植性の高いコードを書くことができるようになります。

○C言語でのNULL

C言語におけるNULLは、次のように定義されています。

#define NULL ((void *)0)

つまり、NULLはvoid*型の0として定義されているのです。

これは、どんなポインタ型にも暗黙的に変換できるという特徴があります。

○C++でのNULL

一方、C++におけるNULLの定義は次のようになっています。

#define NULL 0

C++では、NULLは単に整数の0として定義されています。

これは、ポインタ型にのみ暗黙的に変換できます。

○サンプルコード5:C言語とC++でのNULLの使用例

では、実際にコードを見ながら、C言語とC++でのNULLの使い方の違いを確認してみましょう。

// C言語でのNULLの使用例
#include <stdio.h>

int main() {
    int *p = NULL;
    if (p == NULL) {
        printf("pはNULLです\n");
    }
    return 0;
}
// C++でのNULLの使用例
#include <iostream>

int main() {
    int *p = NULL;
    if (p == NULL) {
        std::cout << "pはNULLです" << std::endl;
    }
    return 0;
}

両方のコードとも、次のような出力結果になります。

pはNULLです

一見、C言語とC++でのNULLの使い方に違いはないように見えます。

しかし、C++ではNULLが整数の0として定義されているため、次のようなコードはコンパイルエラーになってしまいます。

int i = NULL;  // C++ではコンパイルエラー

一方、C言語ではNULLがvoid*型の0として定義されているため、上記のコードは問題なくコンパイルできます。

●std::stringとnull文字の関係

さて、ここまでC言語スタイルの文字列とnull文字の関係について見てきましたが、C++を学んでいる方なら、std::string型についても気になるところですよね。

std::stringは、C++の標準ライブラリで提供されている文字列クラスで、null文字の扱いが大きく異なります。

C言語スタイルの文字列では、null文字が文字列の終端を表すために必須でしたが、std::stringではどうでしょうか。

std::stringを使うことで、null文字の扱いにまつわる多くの問題から解放されるのです。

○std::stringの文字列終端

std::string型では、文字列の終端にnull文字は必要ありません。

std::string内部では、文字列の長さが別途管理されているため、null文字を終端記号として使う必要がないのです。

これは、C言語スタイルの文字列処理で頻発するバグを防ぐのに非常に効果的です。

例えば、うっかりnull文字を付け忘れてしまっても、std::stringなら問題ないですからね。

○サンプルコード6:std::stringとnull文字

では、実際にコードを見ながら、std::stringとnull文字の関係を確認してみましょう。

#include <iostream>
#include <string>

int main() {
    std::string str1 = "Hello";
    std::string str2 = "World\0!";

    std::cout << "str1の長さ: " << str1.length() << std::endl;
    std::cout << "str1の内容: " << str1 << std::endl;

    std::cout << "str2の長さ: " << str2.length() << std::endl;
    std::cout << "str2の内容: " << str2 << std::endl;

    return 0;
}

実行結果

str1の長さ: 5
str1の内容: Hello
str2の長さ: 7
str2の内容: World!

str1は通常の文字列、str2はnull文字を含む文字列ですが、std::stringではnull文字も普通の文字として扱われます。

そのため、str2の長さは7になり、null文字以降の文字も出力されています。

このように、std::stringを使えば、null文字を特別扱いする必要がなくなるのです。

これは、C++初心者にとって、文字列処理のハードルを大きく下げてくれる機能だと言えますね。

○サンプルコード7:std::strncpyの危険性

ただし、C言語の文字列操作関数をstd::stringに対して使うことは避けたほうが良いでしょう。

例えば、次のようなコードは危険です。

#include <iostream>
#include <string>
#include <cstring>

int main() {
    std::string str = "Hello, World!";
    char buffer[10];

    std::strncpy(buffer, str.c_str(), sizeof(buffer));

    std::cout << "bufferの内容: " << buffer << std::endl;

    return 0;
}

実行結果

bufferの内容: Hello, Wo

このコードでは、std::stringの内容をC言語スタイルの文字列にコピーしていますが、コピー先のバッファが小さすぎるため、null文字が付加されません。

その結果、バッファオーバーフローが発生する可能性があります。

●null文字を意識したプログラミング

ここまでnull文字について詳しく見てきましたが、実際のプログラミングではどのような点に気をつければ良いのでしょうか。

C++を学び始めたばかりの頃は、null文字の扱いに戸惑うこともあるかもしれません。

経験則からお話しすると、null文字を意識したプログラミングのポイントは大きく2つあります。

1つはバッファオーバーフローの防止、もう1つは文字列操作関数の正しい使い方です。

この2つを押さえておけば、C++での文字列処理も安心して行えるようになるはずです。

では、それぞれ詳しく見ていきましょう。

○バッファオーバーフローの防止

バッファオーバーフローは、プログラミング初心者がよく陥る落とし穴の1つです。

これは、文字列をコピーする際に、コピー先のバッファが小さすぎて、null文字を含めてすべての文字をコピーできない状態を指します。

その結果、バッファの境界を越えてメモリを破壊してしまう可能性があります。

これは、セキュリティ上の大きな問題にもつながります。

○サンプルコード8:安全なstrcpy関数の実装

バッファオーバーフローを防ぐには、文字列をコピーする際に、コピー先のバッファが十分な大きさを持っていることを確認する必要があります。

#include <cstring>

void safe_strcpy(char* dest, const char* src, size_t dest_size) {
    if (dest_size > 0) {
        size_t i;
        for (i = 0; i < dest_size - 1 && src[i] != '\0'; ++i) {
            dest[i] = src[i];
        }
        dest[i] = '\0';
    }
}

この関数では、コピー先のバッファサイズ(dest_size)を明示的に受け取り、それを超えないようにコピーを行っています。

また、コピーの最後には必ずnull文字を付加しています。

こうすることで、バッファオーバーフローを防ぐことができます。

ただし、C++ならstd::stringを使うのがより安全で簡潔な方法ですね。

○サンプルコード9:MATLABで生成されるC/C++文字列

MATLABなどの他の言語と連携してC/C++を使う場合にも、null文字の扱いには注意が必要です。

例えば、MATLABから生成されるC/C++の文字列は、必ずnull文字で終端されています。

#include <mex.h>
#include <cstring>

void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) {
    if (nrhs != 1 || !mxIsChar(prhs[0])) {
        mexErrMsgTxt("Invalid input. Expecting a string.");
    }

    char* str = mxArrayToString(prhs[0]);
    size_t len = strlen(str);

    mexPrintf("文字列の長さ: %d\n", len);
    mexPrintf("文字列の内容: %s\n", str);

    mxFree(str);
}

このコードは、MATLABから呼び出されるC++の関数(mexFunction)の例です。

mxArrayToString関数を使って、MATLABの文字列をC++のchar*型に変換しています。

この際、変換された文字列は必ずnull文字で終端されているため、strlen関数で長さを取得できます。

○サンプルコード10:ワイド文字列リテラルとnull文字

C++では、ワイド文字列リテラルを使うこともできます。

ワイド文字列リテラルは、L”…”のように、ダブルクォーテーションの前にLを付けて表現します。

#include <iostream>
#include <cwchar>

int main() {
    const wchar_t* str = L"Hello, World!";
    std::wcout << L"文字列の長さ: " << wcslen(str) << std::endl;
    std::wcout << L"文字列の内容: " << str << std::endl;
    return 0;
}

実行結果

文字列の長さ: 13
文字列の内容: Hello, World!

ワイド文字列でも、最後にはnull文字(より正確にはワイドnull文字)が付加されます。

ワイド文字列の操作には、wcstrlen関数など、専用の関数を使う必要がありますので注意が必要です。

●よくあるエラーと対処法

null文字や空文字を扱う際に、C++プログラマがよく遭遇するエラーにはどのようなものがあるでしょうか。

ここでは、私が経験したよくあるエラーを3つ紹介し、その対処法について考えてみたいと思います。

プログラミング初心者の頃は、文字列処理でエラーが出ると、何が原因なのかわからずに頭を抱えたものです。

ただ、エラーの原因を理解し、適切な対処法を知ることで、バグの少ないコードが書けるようになります。

○文字列操作関数の誤用

C++で文字列を扱う際によく使われる関数に、strcpy、strcat、strcmpなどがあります。

これらの関数は、C言語から引き継がれたものですが、null文字の扱いに十分注意しないと、バッファオーバーフローなどの問題を引き起こします。

例えば、次のようなコードはバッファオーバーフローを引き起こす可能性があります。

char str1[10];
char str2[] = "Hello, World!";
strcpy(str1, str2);  // str1のサイズが小さすぎる

このエラーを避けるには、コピー先のバッファが十分な大きさを持っていることを確認するか、より安全なstrcpy_s関数を使うのが良いでしょう。

または、C++ならstd::stringを使うのが一番安全です。

○null文字の付加忘れ

C言語スタイルの文字列を扱う際は、文字列の最後にnull文字を付加することを忘れがちです。

null文字がないと、文字列の終端が正しく認識されず、意図しない動作につながります。

ここでは、null文字の付加を忘れているコードの例をみていきましょう。

char str[5];
str[0] = 'H';
str[1] = 'e';
str[2] = 'l';
str[3] = 'l';
str[4] = 'o';
// str[5] = '\0'; // null文字の付加を忘れている

このエラーを避けるには、文字列の最後にはかならずnull文字を付加するように習慣づけましょう。

または、strcpyやstrcatなどの関数を使って文字列をコピーすれば、自動的にnull文字が付加されます。

○バッファオーバーフローの発生

バッファオーバーフローは、プログラミング言語を問わず、文字列処理で最も注意すべきエラーの1つです。

特に、ユーザー入力を受け取る際は、バッファサイズを超えるような長い文字列が入力される可能性を考慮する必要があります。

ここでは、バッファオーバーフローが発生するコードの例を見ながら考えていきましょう。

char str[10];
std::cin >> str;  // 10文字を超える入力でバッファオーバーフロー

このエラーを避けるには、入力される文字列の長さを制限するか、バッファサイズを十分に大きくとる必要があります。

または、std::stringを使えば、バッファサイズを気にする必要がなくなります。

これらのエラーは、C++プログラマがよく遭遇するものですが、原因を理解し、適切な対処法を身につけることで、安全で効率的なコードを書けるようになります。

日頃からエラーが起きにくいコーディングを心がけることが大切ですね。

まとめ

この記事では、C++でのnull文字と空文字について、その違いや適切な扱い方を詳しく説明してきました。

C++でのnull文字と空文字の扱いは、一見複雑に見えるかもしれません。

しかし、この記事で紹介した知識を身につければ、自信を持って文字列処理に取り組めるはずです。

ぜひ、日頃からnull文字を意識したプログラミングを心がけ、C++プログラマとしてのスキルを磨いていってください。

皆さんが、バグの少ない高品質なコードを書けるようになることを願っています。