C언어 한글 자모 분리하기 문제

ozon1000의 이미지

연휴 잘 보내셨나요?
얼마 전 제목과 같은 주제로 포럼에 글을 올렸던 적이 있습니다.
제가 부족한 탓에 결과적으로 만족스러운 출력을 낼 수 없었고 염치 없지만 다시한번 KLDP에 글을 쓰게 되었습니다.
개발 환경은 ubuntu 16.04 이며 utf-8 환경입니다.

입력으로 다음과 같은 변수에 이렇게 들어옵니다.

char *hangul = "안녕";

그리고 출력으로 다음과 같이 얻고 싶습니다.

char *result = "ㅇㅏㄴㄴㅕㅇ";

솔직히 많이 어려웠습니다. 어떤 식으로 접근을 해야 할지도 몰랐고요.
전체적인 느낌은 이해했지만 직접적으로 코딩을 하는것에 어려움이 많았습니다.
어떻게 유니코드로 변환해서 정해진 수식을 거쳐 분리해야 하는지 잘 모르고 있는게 현실입니다.

혹여나 지나 가시다가 이 글을 읽으시면 한번쯤 관심가져 주시면 감사드리겠습니다.
읽어주셔서 감사합니다.

항상 많이 배우고 있습니다.

ozon1000의 이미지

code가 이상하게 들어갔는데, 수정하려니 버튼이 안보이네요...

김정균의 이미지

댓글로 요청해 주시면 관리자 분들이 보는대로 수정해 드립니다.

 의 이미지

이전에 답변드렸던 사람입니다.

https://kldp.org/node/156803

제 코드가 동작하지 않는지요?
지금 테스트해보기로는 Ubuntu 16.04.1 LTS, 로케일 en_US.UTF-8에서 잘 동작하는데요.

setlocale(LC_ALL, "en_US.UTF-8");setlocale(LC_ALL, "");와 같이 고치는 것이 더 낫다고 하더군요. 이러면 현재 시스템 로케일을 따라가는 모양입니다만, 사실 여전히 그 부분은 잘 모릅니다.

덧. 이 경우엔 그냥 예전에 쓰신 글에 댓글을 다시면 통째로 끌어올려집니다.

ozon1000의 이미지

테스트 결과를 알려드려야 저도 그리고 답변자님도 이해가 빠르실 것 같아 글을 씁니다.
말씀하신 코드는 테스트 해봤습니다.
gcc로 컴파일 하면 다음과 같은 오류가 생깁니다.

undefined reference to '__gxx_personality_v0'

따라서 g++로 컴파일을 했고 이상없이 컴파일은 완료됩니다.

그리고 입력을 기다리는 콜솔창에 "김" 이라고 입력 후 엔터를 입력하면
예상되는 "ㄱㅣㅁ"이 아니라 이상한 특수 문자들이 출력됩니다.

혹 이유를 알 수 있을까요?

qiiiiiiiip의 이미지

아래 명령어의 결과를 그대로 보여주세요

$ printf "김" | xxd

$ echo -e "\xea\xb9\x80|\xb1\xe8"

$ grep 'ㄱ' your_conversion_filename.c
ozon1000의 이미지

1. 00000000: eab9 80
2. 김|xb1 그리고 그 뒤에 이상한 특수기호 하나 있어요
3. 이건 잘 모르겠네요 어떤 의도로 실행해보라고 하신지요.

qiiiiiiiip의 이미지

1번결과로 봐서 입력로케일은 utf-8 인것으로 보입니다.
2번결과로 봐서 님의 터미널읜 utf-8 캐릭터를 잘 보여주고 있습니다.

마지막으로 의심해볼점은 소스코드 (원래 쓰레드에서 다른분이 올려주신 코드)의
인코딩이 어떻게 저장되어있는지 입니다. ( 그걸 어떤 파일명으로 저장했는지는 모르겠고요.)
그게 utf-8로 인코딩이 잘되어있다면 잘 동작해야합니다.

잘 동작하지 않는다면
1. 소스코드는 다른 인코딩으로 저장되었다.
2. 그 코드가 아니다. 일부수정했다.

둘중하나입니다. 웬지 2번일것 같네요.

코드를 그대로 보여주시고
컴파일 명령어도 그대로 보여주세요

ozon1000의 이미지

맞습니다. 2번이 코드가 실행되지 않는 원인이었습니다.
이것참,,, 뭐라고 말씀드려야 될지 모르겠네요. 부끄럽기도 하고요.
저도 언젠간 사람들에게 많은 도움이 되는 프로그래머가 되고 싶네요!
감사드립니다.

 의 이미지

이건... 정말 이해하기 어렵군요.

아주 잘 짜인 코드라던가 이식성이 보장되는 코드라고 할 수는 없더라도,
최소한 C언어 코드에서 돌연 gxx가 언급되는 문제가 생길 거라고 예상하지는 못했는데요.

뭔가 제가 이해하는 수준을 넘어서는 문제가 있는 것 같습니다. 컴파일 및 테스트 환경에 대해 좀 더 알려주실 수 있으신지요.

김정균의 이미지

CentOS 7 환경에서 gcc/g++ 모두 빌드 잘 되고 동작 잘 하네요. 저도 별 문제 없을 것 같은데 혹시나 해서 테스트 해 봤습니다. 아마도 질문하신 분의 소스에 코드를 덮입히면서 발생한 문제가 아닐까 싶은데요.

CentOS 7.3

[root@an3 shm]$ env | grep "LANG\|LC_"
LANG=ko_KR.UTF-8
[root@an3 shm]$ gcc -v
gcc version 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC)
[root@an3 shm]$ uname -a
Linux an3.pkg.oops.org 3.10.0-514.6.1.el7.x86_64 #1 SMP Wed Jan 18 13:06:36 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
[root@an3 shm]$ gcc a.c -std=c99 -pedantic -pedantic-errors
감나라 배나라
ㄱㅏㅁㄴㅏㄹㅏㅂㅐㄴㅏㄹㅏ^C
[root@an3 shm]$ g++ a.c -std=c99 -pedantic -pedantic-errors
감나라
ㄱㅏㅁㄴㅏㄹㅏ^C

아 코드는 utf-8 format 입니다. 소스 코드가 cp949 일 경에는 빌드 에러는 안나지만 결과가 아래와 같습니다. 당연한 결과 이기는 하지만..

[root@an3 shm]$ ./a.out
감나라
▒▒▒▒▒▒▒▒▒▒▒▒▒▒^C
 의 이미지

Challenge accepted.

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <locale.h>
#include <wchar.h>
#include <string.h>
#include <stdbool.h>
 
#define NR_INITIAL  19
#define NR_MEDIAL   21
#define NR_FINAL    28
 
typedef struct korean_syllable_table {
    const char * const initial_table[NR_INITIAL];
    const char * const medial_table[NR_MEDIAL];
    const char * const final_table[NR_FINAL];
} KSTable;
 
static KSTable *init_korean_syllable_table(void) {
    const static wchar_t initial_wtable[NR_INITIAL] = {
        0x3131, 0x3132, 0x3134, 0x3137, 0x3138, 0x3139, 0x3141, 0x3142, 0x3143, 0x3145,
        0x3146, 0x3147, 0x3148, 0x3149, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e
    };
    const static wchar_t medial_wtable[NR_MEDIAL] = {
        0x314f, 0x3150, 0x3151, 0x3152, 0x3153, 0x3154, 0x3155, 0x3156, 0x3157, 0x3158,
        0x3159, 0x315a, 0x315b, 0x315c, 0x315d, 0x315e, 0x315f, 0x3160, 0x3161, 0x3162,
        0x3163
    };
    const static wchar_t final_wtable[NR_FINAL] = {
        0x0000, 0x3131, 0x3132, 0x3133, 0x3134, 0x3135, 0x3136, 0x3137, 0x3139, 0x313a,
        0x313b, 0x313c, 0x313d, 0x313e, 0x313f, 0x3140, 0x3141, 0x3142, 0x3144, 0x3145,
        0x3146, 0x3147, 0x3148, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e
    };
    const size_t required_memory_size =
        sizeof(KSTable) + (MB_CUR_MAX + 1) * (NR_INITIAL + NR_MEDIAL + NR_FINAL);
    void *raw_memory = NULL;
    char *table = NULL, *string_buffer = NULL;
    const char **table_entry = NULL;
    size_t i = 0;
    int mblen = 0;
 
    raw_memory = malloc(required_memory_size);
    if (!raw_memory)
        return NULL;
 
    table = (char *)raw_memory;
    string_buffer = table + sizeof(KSTable);
 
    table_entry = (const char **)(table + offsetof(KSTable, initial_table));
    for (i = 0; i<NR_INITIAL; i++, table_entry++) {
        mblen = wctomb(string_buffer, initial_wtable[i]);
        if (mblen < 0) {
            free(raw_memory);
            return NULL;
        }
        string_buffer[mblen] = '\0';
        *table_entry = string_buffer;
        string_buffer += (mblen + 1);
    }
    table_entry = (const char **)(table + offsetof(KSTable, medial_table));
    for (i = 0; i<NR_MEDIAL; i++, table_entry++) {
        mblen = wctomb(string_buffer, medial_wtable[i]);
        if (mblen < 0) {
            free(raw_memory);
            return NULL;
        }
        string_buffer[mblen] = '\0';
        *table_entry = string_buffer;
        string_buffer += (mblen + 1);
    }
    table_entry = (const char **)(table + offsetof(KSTable, final_table));
    for (i = 0; i<NR_FINAL; i++, table_entry++) {
        mblen = wctomb(string_buffer, final_wtable[i]);
        if (mblen < 0) {
            free(raw_memory);
            return NULL;
        }
        string_buffer[mblen] = '\0';
        *table_entry = string_buffer;
        string_buffer += (mblen + 1);
    }
 
    return (KSTable *)raw_memory;
}
 
static _Bool DecomposeKoreanSyllable(wchar_t code, wchar_t *initial, wchar_t *medial, wchar_t *final) {
    const wchar_t begin = 0xAC00;
    const wchar_t end = begin + NR_INITIAL * NR_MEDIAL * NR_FINAL;
 
    if (begin <= code && code < end) {
        code -= begin;
        if (final)*final = code % NR_FINAL;
        code /= NR_FINAL;
        if (medial)*medial = code % NR_MEDIAL;
        code /= NR_MEDIAL;
        if (initial)*initial = code;
        return true;
    }
    else return false;
}
 
int main(void) {
    char buf[80];
    KSTable *table = NULL;
    setlocale(LC_ALL, "");
    if (!(table = init_korean_syllable_table()))
        return EXIT_FAILURE;
 
    mbtowc(0, 0, 0);
    while (fgets(buf, sizeof(buf), stdin)) {
        size_t buf_offset = 0, buf_len = strlen(buf);
        wchar_t wc;
        int wc_len;
        while ((wc_len = mbtowc(&wc, buf + buf_offset, buf_len - buf_offset)) > 0) {
            wchar_t initial, medial, final;
            if (DecomposeKoreanSyllable(wc, &initial, &medial, &final)) {
                fputs(table->initial_table[initial], stdout);
                fputs(table->medial_table[medial], stdout);
                fputs(table->final_table[final], stdout);
            }
            buf_offset += wc_len;
        }
    }
    putchar('\n');
    free(table);
    return EXIT_SUCCESS;
}

이제 최소한 코드 자체는 인코딩에서 자유로워졌습니다. 보시면 아시겠지만 한글 자모를 소스코드에 직접 입력하는 대신 유니코드로 wchar_t 타입 배열에 저장했죠. init_korean_syllable_table 함수가 멀티바이트 한글 자모 문자열들을 생성합니다.

여전히 불완전한 부분이 남아있기는 합니다. 일단 원론적으로는 C99에서 wide character가 유니코드라는 보장이 없다는 점... -_-;; C11부터 유니코드 지원이 들어오던가요. 그건 넘어가더라도 wctomb 함수도 문제인데 setlocale 함수에 cp949나 euckr 같은 걸 넘겨주고 실행해보니까 init_korean_syllable_table 함수가 실패하는군요. 저 두 인코딩에 한글 자모가 없을 리가 없는데, 아오. 뭔가 제가 모르는 게 더 있는 모양입니다.

어쩌다보니 wide-multibyte 양방향 변환을 다 하는 예제를 만들게 됐네요.

세벌의 이미지

소스의 가독성은 난해하네요...컴퓨터는 이해하겠지만 사람이 이해하기는 어려울 듯.

shint의 이미지

완성형 문자코드를 조합형 문자코드로 변환하는 방법 ★★★★★ C / C++ / WIN32 API ★ IME(Input Method Edit) | 디버깅과 테스트
http://blog.daum.net/knightofelf/17717

완성형 한글
http://codepad.org/aeH3xFUC

----------------------------------------------------------------------------
젊음'은 모든것을 가능하게 만든다.

매일 1억명이 사용하는 프로그램을 함께 만들어보고 싶습니다.
정규 근로 시간을 지키는. 야근 없는 회사와 거래합니다.

각 분야별. 좋은 책'이나 사이트' 블로그' 링크 소개 받습니다. shintx@naver.com

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.