はじめに
今回はC言語における文字列の表現方法について学んでいく。
C言語における文字列の取り扱いは、他のプログラミング言語と異なって特殊な側面があるので、まずは基本から解説する。
1. 文字列の基本
C言語では文字列はchar型の配列として表され、ヌル文字(‘\0’)を終端とする。
いくつか注意が必要なので、まずは宣言の方法から見ていく。
char str[6] = "hello"
文字列はchar型の配列として定義し、ダブルクォーテーションで囲むことで文字列として認識させることができる。また、終端にヌル文字を明示的に含める必要はないが、実際にはヌル文字が終端に入るので、その分のメモリを確保する必要がある。よって配列には文字数+1バイトの領域を確保する必要がある。
char str[] = "world";
もちろん、配列を自動的に設定させることも可能だ。
2. 文字と整数
C言語においては、文字は整数値として内部で扱われる。
これは、C言語が文字をASCII(American Standard Code for Information Interchange, アスキー)コードとして表現するためである。
ASCIIコードとは、英数字や制御文字などの一般的な文字を128種類にマッピングしたものである。大文字のAからZは(65〜90)、ヌル文字はASCIIコードの0に対応している。
ASCIIコードの対応表についてはこちらを参照: https://ja.wikipedia.org/wiki/ASCII
例えば、CyberHubという文字列は、次の文字コードの配列によって表現される。
67, 121, 98, 101, 114, 72, 117, 98
クォーテーション
C言語におけるシングルクォーテーション(’)とダブルクォーテーション(”)の使い分けについて解説しておく。
シングルクォーテーション (‘)
単一の文字を表す場合に使用され、char型のデータとして扱われる。シングルクォーテーションで囲まれた文字は、その文字のASCIIコードとして格納される。
char letter = 'A';
ダブルクォーテーション (“)
文字列を表す場合に使用され、char型の配列として扱われる。文字列の最後にはヌル文字が自動的に追加される。
char greeting[] = "Hello, world!";
文字列の演算
Aであれば整数値65, Bであれば66といった具体的な数値が割り当てられていることから、C言語のプログラムでは文字に対して算術演算を行うことができる。
例として、次のプログラムの動作を確認してみよう。
#include <stdio.h>
int main() {
char c = 'A';
c = c + 3;
printf("%c", c);
return 0;
}
‘A’はAの文字コードである65と同じように扱うことができるため、65+3=68、すなわち3つ後ろの’D’が出力できる。
AからZまでを出力する次のプログラムも見てみよう。
#include <stdio.h>
int main()
{
char c;
for (c = 'A'; c <= 'Z'; c++) {
printf("%c", c);
}
printf("\n");
return 0;
}
for文の中で文字コードを整数演算しているのがポイントである。
3. 文字列の操作関数
C言語における文字列操作関数はstring.hで提供される。これらの関数を使用することで、文字列のコピーや比較・連結が可能になる。C言語では、文字列に対して算術演算子を用いないように注意しよう。
#include <string.h>
文字列操作を行う場合はstring.hをincludeしておく。
strlen
文字列の長さを求める関数。ヌル文字が現れるまでの文字列の長さを返す。
strlen(const char *string);
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, world!";
printf("Length: %lu\n", strlen(str)); // 出力: Length: 13
return 0;
}
strcpy
文字列のコピーを行う関数。コピー先の配列は、コピーされる文字列を格納するのに十分なサイズを持っている必要がある。
char *strcpy(char *string1, const char *string2);
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, world!";
char dest[50];
strcpy(dest, src);
printf("コピーされた文字列: %s\n", dest); // 出力: コピーされた文字列: Hello, world!
return 0;
}
strcat
文字列の連結を行う関数。1つの文字列を別の文字列の末尾に追加する。当然ながら、連結先の配列は追加される文字列を含むのに十分なサイズを持っている必要がある。
char *strcat(char *string1, const char *string2);
#include <stdio.h>
#include <string.h>
int main() {
char dest[50] = "Hello, ";
char src[] = "world!";
strcat(dest, src);
printf("連結された文字列: %s\n", dest); // 出力: 連結された文字列: Hello, world!
return 0;
}
strcmp
2つの文字列を比較する。文字列が等しい場合は0、異なる場合は0以外の値を返す。
int strcmp(const char *string1, const char *string2);
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "Hello";
char str2[] = "World";
int result = strcmp(str1, str2);
if (result == 0) {
printf("一致\n");
} else {
printf("不一致\n");
}
return 0;
}
strchr / strstr
strchrは指定された文字が初めて現れる位置をポインタで返す。
strstrは1つの文字列が別の文字列内で初めて現れる位置をポインタで返す。
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, world!";
char *p;
p = strchr(str, 'w');
if (p) {
printf("検索結果: %c\n", *p);
} else {
printf("Not found\n");
}
p = strstr(str, "world");
if (p) {
printf("検索結果: %s\n", p);
} else {
printf("Not found\n");
}
return 0;
}
4. バッファオーバーフロー
バッファオーバーフローとは、プログラムが格納先の領域を超えてデータを書き込むときに発生するセキュリティ上の問題である。
特に、文字列操作関数を使用する際によく発生する問題なので、注意が必要である。
次のプログラムは、strcpy関数をしようした際にバッファオーバーフローが発生する可能性のあるコード例である。
#include <stdio.h>
#include <string.h>
int main() {
char src[1024];
char dest[10];
printf("Enter a string: ");
gets(src); // 注意! gets() は非常に危険で、使用すべきではない
strcpy(dest, src); // ここでバッファオーバーフローが発生する可能性
printf("Copied string: %s\n", dest);
return 0;
}
src配列に大量のデータが入力されると、dest配列のサイズを超えてしまい、隣接するメモリ領域にデータを上書きして脆弱性を生じさせる可能性がある。
この場合は、strcpyのより安全な代替であるstrncpyを使用してコピーする最大文字数を指定することでバッファオーバーフローを防ぐことができる。
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // 確実にヌル終端されるようにする
strncpy
文字列のコピーを行う関数strcpyの文字数指定バージョン。第3引数でコピーする最大文字数を指定することができる。
char *strncpy(char *dest, const char *src, size_t n);