C言語の文字列

C言語の文字列について解説します。C言語には、文字列という型はなく、文字の配列を使って文字列を表現します。C言語の文字列は「\0」で終わります。printf関数の「%s」というフォーマットで文字列を出力できます。

#include <stdio.h>

int main(void) {
  // 文字列は、文字の配列で表現し、「\0」で終わる。
  char string[4];
  string[0] = 'a';
  string[1] = 'b';
  string[2] = 'c';
  string[3] = '\0';
  
  // 文字列を出力
  printf("%s\n", string);
}

出力結果です。

abc

char型は文字を表現する型

char型はC言語で文字を表現する型で「符号あり、または符号なしの8bit整数」として定義されています。ASCII文字コードを代入することを想定していると考えられます。

文字リテラル

「'a'」は文字リテラルです。対応するASCIIコードに変換されます。「'a'」の場合は、97に変換されます。

文字列リテラル

文字列リテラルを使って、文字列を簡単に表現することができます。文字列リテラルは、文字配列の先頭のアドレスを返すのでchar*型で受け取ります。文字列が変更されないようにconst修飾子をつけておくのがよいでしょう。以下が、文字列を表現するC言語のお勧めの表現です。

#include <stdio.h>

int main(void) {
  // 文字列リテラル
  const char* string = "abc";
  
  // 文字列を出力
  printf("%s\n", string);
}

動的に文字列を作成

動的に文字列を作成するには、calloc関数を使って、メモリを確保します。使い終わったら、free関数で解放します。

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  // 長さが3の文字列のためにメモリを確保。
  // C言語の文字列の末尾は「\0」である必要があるため、1バイト多く確保する
  char* string = calloc(4, sizeof(char));
  
  string[0] = 'a';
  string[1] = 'b';
  string[2] = 'c';
  string[3] = '\0';
  
  printf("%s\n", string);

  // 使い終わったら解放
  free(string);
}

出力結果です。

abc

C言語の文字列は「\0」で終わるので、余分にメモリを確保しておいても特に問題はありません。定数文字列ではなく、追加する必要があるのであれば、多くメモリ領域をとることもできます。100バイトのメモリ領域を確保する例です。

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  // 長さが3の文字列のためにメモリを確保。
  // C言語の文字列の末尾は「\0」である必要があるため、1バイト多く確保する
  char* string = calloc(100, sizeof(char));
  
  string[0] = 'a';
  string[1] = 'b';
  string[2] = 'c';
  string[3] = '\0';
  
  printf("%s\n", string);
  
  // 使い終わったら解放
  free(string);
}

出力結果です。

abc

文字列のメモリ領域の再割り当て

文字列のメモリ領域の再割り当てをするには、新しくメモリを確保し、memcpy関数で、前の文字列をコピーします。以前のメモリ領域を解放し、新しい文字列を再代入します。

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

int main(void) {
  // 長さが3の文字列のためにメモリを確保。
  // C言語の文字列の末尾は「\0」である必要があるため、1バイト多く確保する
  int32_t capacity = 4;
  char* string = calloc(capacity, sizeof(char));
  
  string[0] = 'a';
  string[1] = 'b';
  string[2] = 'c';
  string[3] = '\0';
  
  printf("Before %s\n", string);

  // 長さを5にしたい
  int32_t new_capacity = 6;
  char* new_string = calloc(new_capacity, sizeof(char));
  
  // 前の文字列をコピー
  memcpy(new_string, string, capacity - 1);
  
  // 新しい文字を追加
  new_string[3] = 'd';
  new_string[4] = 'e';
  new_string[5] = '\0';
  
  // 古い文字列を解放
  free(string);
  
  // 新しい文字列と入れ替え
  string = new_string;
  new_string = NULL;
  
  printf("After  %s\n", string);

  free(string);
}

出力結果です。

Before abc
After  abcde

メモリ領域の再割り当てはrealloc関数を使ってもでき、高速ですが、reallocは、処理系によって実装が異なり、バグが再現しにくいという経験があるので、パフォーマンスに問題がなければ、callocを使ってメモリの再割り当てを行う方が安心と感じます。

文字列の長さ

文字列の長さを取得するにはstrlen関数を使用します。string.hを読み込むと、strlen関数を使用できます。

#include <string.h>
size_t strlen(const char *s);

C言語では文字列は「\0」で終わるという約束事があります。strlen関数は、この約束事を前提として、文字列の長さを計算します。つまり、「\0」が見つかるまで、ループして、文字数をカウントしていくということです。逆にいえば、文字列が「\0」で終わっていない場合は、意図しないメモリ領域まで進み、バッファオーバーランが起こります。

strlenを使う場合は、文字列が「\0」で終わる文字列に対して使用していることを必ず確認してください。

strlen関数で、文字列の長さを求めるサンプルです。

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

int main(void) {
  const char* string = "Hello";
  
  int32_t string_length = strlen(string);
  
  printf("%d\n", string_length);
}

出力結果です。

5

文字の検索

文字を検索するにはstrchr関数を使います。文字を後ろから検索するstrrchr関数もあります。

文字列の検索

文字列を検索するにはstrstr関数を使用します。strstr関数で、文字列が含まれているかをチェックするサンプルです。戻り値がNULLでないことを確認すればOKです。

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

int main(void) {
  const char* message = "I like orange\n";
  const char* match = "orange";
  
  if (strstr(message, match) != NULL) {
    printf("Match\n");
  }
  else {
    printf("Not Match\n");
  }
}

出力結果です。

Match

文字列のコピー

文字列をコピーしてみましょう。strlen関数で文字列の長さを計算、calloc関数でメモリ領域を確保、memcpy関数で文字列をコピーします。

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

int main(void) {
  const char* string1 = "Hello";
  
  int32_t string1_length = strlen(string1);
  
  char* string2_tmp = calloc(string1_length + 1, sizeof(char));
  memcpy(string2_tmp, string1, string1_length);
  
  const char* string2 = string2_tmp;
  string2_tmp = NULL;
  
  printf("%s\n", string2);
  
  free((void*)string2);
}

出力結果です。

Hello

文字列のコピーを行う他の方法

文字列のコピーを行うのに、strcpy関数strncpy関数を使う方法もあります。

文字列の連結

文字列の連結ってC言語でどうやるんでしょう? 二つの文字列を連結して、結果を返す。うーん、悩みますね。「えっ、自前でやれってこと?」。はい。そうです。二つの文字列の長さを計算して、連結後の文字列の長さを計算して、元の文字列を順番にコピーします。

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

int main(void) {
  const char* string1 = "ab";
  const char* string2 = "cde";
  
  int32_t string1_length = strlen(string1);
  int32_t string2_length = strlen(string2);
  
  int32_t string3_length = string1_length + string2_length;
  char* string3_tmp = calloc(string3_length + 1, sizeof(char));
  
  memcpy(string3_tmp, string1, string1_length);
  memcpy(string3_tmp + string1_length, string2, string2_length);
  
  const char* string3 = string3_tmp;
  string3_tmp = NULL;
  
  printf("%s\n", string3);
  
  free((void*)string3);
}

出力結果です。

abcde

「毎回、これやるんですか?」「はい。必要になれば。」「こういう処理って、何回もでてきませんか?」「何回もでてきます(笑)。」

文字列の連結を行う他の方法

自分自身の末尾に、文字列を追加する場合は、strcat関数strncat関数も使えます。

文字列の置換

文字列の置換ってC言語でどうやるんでしょう? うーん、悩みますね。「えっ、自前でやれってこと?」。はい。そうです。

「Foo::Bar::Baz」を「Foo/Bar/Baz」に置換する処理を書いてみましょう。

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

int main(void) {
  const char* module_name = "Foo::Bar::Baz";
  int32_t module_name_length = strlen(module_name);
  
  // 多めに確保するのはOK
  char* module_path_tmp = calloc(module_name_length, module_name_length);
  
  int32_t module_path_tmp_index = 0;
  for (int32_t i = 0; i < module_name_length; i++) {
    
    if (module_name[i] == ':' && module_name[i + 1] == ':') {
      module_path_tmp[module_path_tmp_index] = '/';
      i++;
      module_path_tmp_index++;
    }
    else {
      module_path_tmp[module_path_tmp_index] = module_name[i];
      module_path_tmp_index++;
    }
  }
  
  const char* module_path = module_path_tmp;
  module_path_tmp = NULL;

  printf("%s\n", module_path);
  
  free((void*)module_path);
}

出力結果です。

Foo/Bar/Baz

文字列関数

C言語の文字列に関する関数はstring.hにあります。

C言語でUnicodeって扱えるの?

C言語でUnicodeを扱えるライブラリとしてutf8procを紹介しておきます。UTF-8, UTF-16, UTF-32の変換が可能です。

Unicodeの扱いについては、詳しくは書かないですが、ざっと、僕が考えていることを書いておきます。

C言語標準関数やC言語仕様だけでは、Unicodeを扱うことは難しいです。

UTF-8について

文字列の出力は、ソースコードの文字列ががUTF-8であって、UNIX/Linux/Macの環境であれば、そのまま出力できます。

UTF-8はASCIIコードの上位互換ですので、過去に作られたC言語のライブラリは、そのまま使うことができます。

Webの用途として、C言語を使う場合は、UTF-8で文字列を扱うのがよさそうです。UTF-8は、単なるバイト列であり、エンディアンを意識する必要がありません。

単なるバイト列ということは、C言語では、UTF-8の文字列を以下のように表現できるということです。ソースコードはUTF-8で保存したとします。

// C言語におけるUTF-8の表現
const char* string = "あいうえお";

マルチバイト文字、UTF-16、UTF-32について

C言語には、マルチバイト文字を扱う仕組みが用意されています。マルチバイト文字には、2種類あって、2バイト文字と4バイト文字です。

2バイト文字というのは、たとえば、Shift_JISのような1文字を2バイトで表現するコードと、UTF-16LEやUTF-16BEのような原則2バイトで1文字を表現するUnicodeの符号化方式での利用が想定されています。

4バイト文字というのは、Unicodeの一つの符号化体系のUTF-32あるいは、Unicodeのコードポイントを保存することが想定されています。UTF-32は、Unicodeのコードポイントと、1対1で対応します。つまり、UTF-32とは、Unicodeのコードポイントのことです。

2バイト文字は、Windowsで使わざるをえません。Windowsの内部文字コードは、UTF-16LEで、Unicode環境でコンパイルされたWindows APIは、UTF-16LEの文字列を受け取るからです。

2バイト文字と、4バイト文字は、UTF-8と異なり、簡単に出力できません。「中身を、み、みたい。でも、みれない。」みたいな。

UTF-8がいいんだ。でも、現実は、それだけじゃないんだ、変換が、必要なんだ。これが現実なんだ~。みたいな。

マルチバイト文字は、UTF-8を表現するために、使うものではないので、頭の中で、こんがらないようにしておきましょう。

関連情報