2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C言語の文字と文字列のご紹介 for Swiftプログラマー

Last updated at Posted at 2017-12-25

検証環境

端末: MacBook Air
OS: macOS High Sierra
Swift: 4.0.3 (swiftlang-900.0.74.1 clang-900.0.39.2)
Clang: Apple LLVM version 9.0.0 (clang-900.0.39.2)

概要

皆様何気なくCの文字列(char*)をSwiftやObjective-Cの文字列(String, NSString)に変換する際に以下のメソッドを使用されているかと思います。

String.swift
init?(cString: UnsafePointer<CChar>) //typealias CChar = Int8

まずこのメソッドに渡すcString、つまりCの文字列についてご紹介したいと思います。
次に、実はNSString側は上記のメソッドが廃止され、以下のようにencodingを同時に指定するメソッドが用意されています。この時に渡すencoding値について考えたいと思います。

String.swift
init?(cString: UnsafePointer<CChar>, encoding enc: String.Encoding)

Cの文字について

C言語の文字を表す型としてcharが用意されています。このchar型の変数には1バイトの値を格納することが出来ます。
Cの文字はシングルクォーテーションで文字を囲みます。そしてシングルクォーテーションで囲まれた値は、文字リテラル(character literal)と呼ばれます。

Cの文字.c
int main(void) {
    char c = '*'; //「'」マークで文字を囲むことで1バイトのASCII値に変換される
    printf("%c\n", c); // *
    printf("%ld\n", sizeof(c)); // 1

}

コメントにも記載していますが、実は上記は以下の糖衣構文になっています。

cの文字.c
int main(void) {
    char c = 42;
    printf("%c\n", c); // *
    printf("%ld\n", sizeof(c)); // 1
}

つまり文字リテラルの実体は、「ただの数字」であるということです。

また以下のようにマルチバイト文字をシングルクォーテーションで囲んだ場合、生成される数字(*1)が、charのサイズ(1バイト)を超えてしまうためコンパイルエラーになります。

(*1: 生成される値については、下記のセクション「Cの文字列にマルチバイト文字を格納した場合」で説明いたします)

Cの文字.c
// UTF8でこのソースコードファイルは保存されています
int main(void) {
    char c = 'あ'; // error: character too large for enclosing character 
}

つまりchar型の変数にはマルチバイト文字をそのまま格納することが出来ません。

また文字リテラルの実体が1バイトの数字であるという検証は以下のようにint型(4バイト)に値を直接格納出来ることでも証明できます。

文字リテラルの実体は1バイトの数字.c
int main(void) {
    int num = 'abcd';
    printf("%0x\n", num); // 64656667
}

numの値を16進数で出力した結果が「64656667」となっており、それぞれバイト単位で値を読み取ると、「64,65,66,67」と分解出来ることが分かります。

ここでのまとめ

  • Cの文字はchar型で表される
  • char型は1バイトの値を格納するための箱である
  • シングルクォーテーションで囲んだ値は、文字リテラルと呼ばれる
  • 文字リテラルはchar型の箱を返し、中には数字(エンコーディング値)を格納する

Cの文字列について

C言語の文字列はchar型の配列です。つまり1バイトデータを格納するための配列で表現されています。
またCの文字列はダブルクォーテーションで文字を囲みます。またダブルクォーテーションで囲んだ値は、文字列リテラル(string literal)と呼ばれます。

Cの文字列.c
int main(void) {
    char str[] = "Hello"; // 同時に初期化を行うことで要素数を省略できます
    printf("sizeof(str)/sizeof(char) = %ld\n", sizeof(str)/sizeof(char)); // 6
}

上記にて初期化に利用したHelloという文字列は5文字ですが、要素数が6となっております。
実は、以下の糖衣構文となっております。

Cの文字列について.c
int main(void) {
    char str[] = {'H','e','l','l','o','\0'};
    printf("sizeof(str)/sizeof(char) = %ld\n", sizeof(str)/sizeof(char)); //6
}

つまり "Hello" という文字列リテラルは、最後にナル文字を格納した要素数6のchar配列を返すということです。

ここでのまとめ

  • Cの文字列はchar型の配列である
  • ダブルクォーテーションで囲んだ値は、文字列リテラル(String Literal)と呼ばれる
  • 文字列リテラルはchar型の配列を返し、最後の値はナル文字(数字の0)が格納されている

Cの文字列にマルチバイト文字を格納した場合

上記のセクションでは触れませんでしたが、以下のようにマルチバイト文字で初期化した場合のソースコードをそれぞれUTF8でファイルに保存した場合と、Shift-JISでファイルに保存した場合のそれぞれの結果をみてみたいと思います。

Cの文字列について.c
int main(void) {
    char str[] = "あ"; //初期化と同時に配列を宣言する場合は、要素数を省略できる
    int size = sizeof(str);
    for (int i = 0; i < size; i++) {
        printf("%hhx ", str[i]); // この出力をそれぞれのエンコーディングで検証する
    }
}

検証方法:

  1. エディタを開く
  2. エディタのエンコード設定をShift-JIS or UTF-8に変更
  3. ソースコードを貼り付けて保存する
  4. clangコンパイラでコンパイルする (\$cc ファイル.c)
  5. 実行する (\$./a.out)

UTF8で保存した際の出力:

case_utf8_result.txt
e3 81 82 0 

Shift-JISで保存した際の出力:

case_shift_jis_result.txt
82 a0 0

上記それぞれの値ですが、このサイトで「あ」を入力して、結果を表示してみて下さい。
結果

つまりC言語のマルチバイト文字の文字列リテラルの結果は、テキストエディタのエンコードと一致することが分かります。

というのもコンパイラには「ソースコード」ではなく、そのソースコードが書かれた「ファイル」を渡しているため、至極当然の結果とも言えます。

つまりUTF8の場合、char str[] = "あ" は以下の糖衣構文であるとも言えます。

Cの文字列について.c
int main(void) {
    //e3 81 82 0 
    char str[] = {0xe3, 0x81, 0x82, 0x0};
    printf("%s \n", str); //ターミナルのエンコード設定がUTF8ならば「あ」と表示されます
}

上記実行するターミナルのエンコードをUTF-8にして実行した場合は、「あ」と表示されます。
Shift-JISにした場合は文字化けします。
(設定 → Profiles → Advanceタグ)
Screen Shot 2017-12-25 at 13.57.40.png

その結果 上がShift-JISにした際の結果で、下がUTF8にした際の結果
Screen Shot 2017-12-25 at 13.56.20.png

ここでのまとめ

  • char型の配列は、マルチバイト文字の文字列リテラルで初期化できる
  • 文字列リテラルで生成される値は、テキストエディタのエンコーディング値である
  • コンパイラにはソースコードではなく、それが記述されたファイルが渡される

Swiftの文字列に変換するときに指定するエンコードについて

以下のSwiftのAPIを用いて、CのAPIから渡された文字をSwiftのStringに変換したいと思います。この時指定するencoding値はどうするべきでしょうか?

String.swift
init?(cString: UnsafePointer<CChar>, encoding enc: String.Encoding) //CChar = Int8

検証するCのプログラムコードは以下です。

libc.c
char* file_name() {
    return "hello.txt";
}

char* new_file_header_str() {
    FILE *f = fopen(file_name(), "r");
    if (f == NULL) return NULL;

    char *str = calloc(256, sizeof(char));
    fgets(str, 256, f); //1行だけ
    fclose(f);
    return str;
}

上記をSwiftから呼び出した場合、Cのchar*型は、UnsafeMutablePointer<Int8>型として結果が渡されます。

まず最初にfile_name関数から取得したCの文字をSwiftの文字に変換することを検証したいと思います。
こちら文字列リテラルがそのまま返されております。
つまり、こちらをSwiftの文字列に変換する際のエンコード値は、libc.cファイルのエンコードと同一にする必要が有ることが分かります。

次に、new_file_header_str 関数から取得したCの文字をSwiftの文字列に変換する際に使用するエンコード値はどうでしょうか?
こちらはhello.txtファイルの文字列が返されております。
つまりこちらで指定しなくてはならないエンコード値は、hello.txtファイルが保存されているエンコード値と同一でなければならないことが分かります。

以下、lib.cファイルをUTF-8で、hello.txtファイルをShift-JISで保存し、Swiftからそれぞれ関数を呼び出したサンプルソースコードです。

get_str_from_c.swift
let name = file_name() //Optional<UnsafeMutablePointer<Int8>>
if let name = name,
    let converted = String(cString: name, encoding: .utf8) {
    print(converted)
} 

let header = new_file_header_str() //Optional<UnsafeMutablePointer<Int8>>
if let header = header,
    let converted = String(cString: header, encoding: .shiftJIS) {
    print(converted)
}

なおCライブラリーの呼び出しは以下を参照して下さい。
https://qiita.com/ysn551/items/83e06cf74ae628cb573c

ここでのまとめ

  • Cの文字列をSwiftの文字列に変換する際に指定するエンコード値は、Cから返される文字が文字列リテラルの場合は、Cソースコードファイルのエンコード値を指定する

Python3の文字列リテラル

このようにCの文字列リテラルは、エンコーディング値が直接格納されるため、開発環境に依存してしまいます。
ちなみにSwiftコンパイラの場合は、UTF8ファイル以外はコンパイル出来ないようになっていました。

一方Python3では、文字列リテラルによって生成される値は数字ですが、こちらはユニコード値が生成されます。
従ってファイル間の文字列リテラルのやり取りにエンコードを考慮する必要はありません。

python3での検証結果は以下です。ちなみにPython2はエンコーディング値が使われるのでソースコード間のエンコーディングが異なると駄目です。

以下のshift_jis.pyファイルをShift-JISエンコードで保存する

shift_jis.py
# ! coding=shift-jis

word = "よろしくです"

以下のutf8.pyファイルをUTF8で保存して実行する。

utf8.py
# ! coding=utf-8

import shift_jis as sh

if sh.word == "よろしくです": 
    print("true")
else:
    print("false")

上記をpython3で実行するとtrueが表示されますが、python2だとfalseが表示されます。

最後のまとめ

2018年もよろしくお願いします。m(__)m

2
2
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?