• ベストアンサー
※ ChatGPTを利用し、要約された質問です(原文:ユニコード文字列の分割)

ユニコード文字列の分割方法を改修する

このQ&Aのポイント
  • ユニコード文字列をカンマや'\'で分割する関数を改修する必要があります。
  • 現在の関数はマルチバイト文字に対応していますが、ユニコード文字に切り替えると分割が正しく行われません。
  • プロジェクトが既に進行中のため、対応に時間をかけずに解決する方法を探しています。

質問者が選んだベストアンサー

  • ベストアンサー
回答No.10

それとCreate関数内で if ( !(*pp)->temp ) return false; となってる部分がありますが if ( !(*pp)->temp ){ Release( pp ); return false; } にしとかないと(ほとんどないとは思いますが)メモリリークの危険が一応残っていますね。 ついでにstd::bad_allocに対しても全部例外安全にしようとすると Splitのnewのとこもこだわらないといけませんが std::bad_allocが飛ぶ状況ってほとんどまず考慮しないでいいかもしれないので アプリケーション次第ではtry-catch使わない方向で割り切ってしまっても全然いいかもしれません。 (後で細切れで気づくと何度も投稿しないといけないのが不便ですw 編集出来たらいいのに >教えて!goo )

baysidehotel
質問者

お礼

回答ありがとうございます。 本日、リーダさんに確認をとりました。 プロジェクトのどこかで工数を確保して改修することになりそうです。 いただいた回答全てを盛り込めるほどの工数は無理と思いますが・・・

その他の回答 (9)

回答No.9

おっと、ReleaseAllWords関数のとこの words = NULL; は不要でしたw 美しくないので削除しておいてくださいw

回答No.8

おお!全員に対してしっかりご返答なさっていらっしゃいますし ご担当なさっているプロジェクトのこれからについて大事なことでしょうから こちらとしてももうちょいしっかり情報を補足しておきます。 ・ユニコードを使う「可能性がある箇所」では必須です。 と書きましたが、これの意図するところは 実際には 現状ではファイルI/Oやコンソール出力時などのみで、確かに十分、というのが実状かもしれません。 ただし「将来的なことまで見据えて考えると」 Microsoftの気まぐれ一つで状況が変わったりするかもしれませんし 「特定の、この関数」内部では未来永劫絶対setlocaleの必要はない!と断言するのは さすがにMicrosoft幹部でも難しいと思うので 場合分けしてやって、将来変更がないか常におびえる状況になるよりも 先に全体にやっておいた方が遥かにリスクが少ない ので「必須と思った方が遥かに楽であろう」という意味です。 ・usertype.datについて 既にご存知でしたら良いですが もしまだ未着手でしたらだいぶ可読性が上がるかもしれませんので usertype.datを使うと、intやifなどの、元々色がつくようになっている 予約語など以外にも VC++上で任意のキーワードに色がついた状態で表示出来ます。 こちらの環境では Program Files\Microsoft Visual Studio 9.0\Common7\IDEの中に usertype.datを置くと効果がありました。 既にある場合は、ここに色を変えたいキーワードを一行にひとつずつ書き加えます。 ない場合は、例としてはメモ帳などで以下のように書いたテキストファイルを作り LPCTSTR TcsLiteral TCHAR ファイル名と拡張子をusertype.datにして上記などの、適切なディレクトリに置き VC++を再起動すれば効果が確認できるはずです。 ・実際の実装法について 実際では決め打ちとかできないので 標準ではない便利なライブラリとかになるべく頼らず書くなら(C++03の仕様では) これくらい書かないといけないかもしれません。 (なお、載せるのに分割状況書くのは面倒なので全部インライン指定になってますが、コンストラクタやデストラクタなどは一見短いように見えて、生成される機械語は長くなったりする可能性があるのでインライン化はこの場合お勧めできません。また、細かい仕様とかその辺は調整してください。) typedef LPCTSTR const TcsLiteral; class TcsPtrArray { typedef struct LINEARDATA { LINEARDATA* next; TcsLiteral p; LINEARDATA( TcsLiteral p_ ) : next(NULL), p(p_){} } LD; TCHAR* temp; LD* words; TcsPtrArray() : temp(NULL), words(NULL) {} ~TcsPtrArray(){ free( temp ); ReleaseAllWords(); } void ReleaseAllWords(){ for ( LD* a; a = words; ){ words = words->next; delete a; } words = NULL; } public: static void CheckFunc_S( TcsLiteral s ){ _tprintf( _T("[%s]\n"), s ); } void Split( const TCHAR delimiter ){ if (words) ReleaseAllWords(); TCHAR* c = temp; words = new LD( c ); LD** pp = &words->next; for ( ; *c; ++c ) { if ( *c != delimiter ) continue; *c = _T('\0'); *pp = new LD( c+1 ); pp = &(*pp)->next; } } template < class F > void ForEach( F& f ) const { for ( const LD* a = words; a ; a = a->next ) f(a->p); } static bool Create( TcsPtrArray** pp, TcsLiteral s ){ //生成 try { *pp = new TcsPtrArray; } catch ( std::bad_alloc ){ return false; } (*pp)->temp = _tcsdup( s ); if ( !(*pp)->temp ) return false; return true; } static void Release( TcsPtrArray** pp ){ //解放 delete *pp; *pp = NULL; } }; 少々長くなりましたが、必要ヘッダとかをインクルードすればコピペでサクッと出来ると思います。 機能拡張等は用途に応じてご自由に行ってください。 ここまで準備しておけば使い方は簡単です。 int main() { setlocale( LC_ALL, "japanese" ); TcsLiteral input = _T("ソーシャル,ネットワーク,,ソリューション,"); TcsPtrArray* tpa; if ( !TcsPtrArray::Create( &tpa, input ) ) return 0; tpa->Split( _T(',') ); tpa->ForEach( TcsPtrArray::CheckFunc_S ); TcsPtrArray::Release( &tpa ); //Createに対して対で解放 } 分からない箇所がありましたら聞いてください。

回答No.7

あ、さらにもうひとつw C++用に変更、なのでやってもなんら問題のですが(むしろやった方が良いはず) 下のコードで修正忘れてました。 int main(){ の直後に setlocale( LC_ALL, "japanese" ); を持ってくるように直してください。 そうすれば、内部で何が行われてても確実に大丈夫なはずです。

baysidehotel
質問者

お礼

何度も回答いただきましてありがとうございます。 いただいたアドバイスともとにプロジェクトのリーダを相談したいと思います。

  • wormhole
  • ベストアンサー率28% (1626/5665)
回答No.6

>char* str: 分割対象の文字列 「分割対象の文字列」の文字コードがUTF-8であるなら問題ないですが (問題なといってもASCIIコードの文字で分割する場合においてですが) Shift_JISだと処理内部でシングルバイト文字と マルチバイト文字の1バイト目と2バイト目を区別する必要があります。 なぜ区別しないといけないかはShift_JISの文字コード表とにらめっこするなり googleなどで検索するなりしてみてください。 また他の人もいってますが >while(*str != NULL){ >words[i][j] = NULL コンパイラはエラーや警告を出さないかもしれませんが 内容的にはバグと変わりありません。 NULLはポインタなので例え定義が0であろうと charとして扱ってはいけません。

baysidehotel
質問者

お礼

回答ありがとうございます。 いただいた指摘と他の方のアドバイスも含めてプロジェクトのリーダさんを相談します。

回答No.5

もひとつ >プログラム内部では文字列は原則std::string型で保持していて 依然大変な作業になる可能性はあるかもしれませんが、状況次第では こちらのページの内容が役に立つかもしれません。 ・UNICODEで分けないtstring型を宣言する http://marupeke296.com/TIPS_No14_tstring.html

回答No.4

どうも epistemeさんへの補足について 1. そこなんですよね。 「ふつ~」に使いたいという前提では少なくとも char*の内部表現とwchar_t*の内部表現は違いますので こればっかりは全部マルチバイト専用のコードになってる場合 必要箇所全てにわたって書きなおす必要があります。 (現実的には「編集→検索と置換→クイック置換」などの機能を駆使して必要箇所をまとめて書き変えていく、といったことになるかと) コストがかかっても変換する必要がある場合はWideCharToMultiByteやMultiByteToWideCharなどで変換する必要があります。 (MFCではCStringA、CStringW、CStringなどの便利な使い方があったと思いますが http://www.usefullcode.net/2006/11/widechartomultibytemultibyteto.html) ただ いずれにせよ、よっぽどでかいプロジェクトの場合、最初からこれが配慮されてないコードで書かれたものを正確に直し続けていく事は 結構大変な作業になることは覚悟しなければいけません。 ただし、特定の個所でのみ両対応にする、という場合はそれほど大変ではない場合もあります。 例えば const char* text = "aaa"; MessageBox(NULL, text, "", 0 ); はデフォがマルチバイトの設定でしか通用しない書き方ですが LPCTSTR text = _T("aaa"); MessageBox(NULL, text, _T(""), 0 ); とかに直していくのが面倒で、この付近に関してはマルチバイト文字しか使わなくてOKと分かってる場合は const char* text = "aaa"; MessageBoxA(NULL, text, "", 0 ); こういう風に、呼び出し側の関数をマルチバイト専用に明示的に指定してやることで対処することも可能です。 逆に、ユニコード版しか用意されてない関数とかでは LPCWSTR text = L"WINDOW"; HTHEME theme = OpenThemeData( hw, text ); などと、明示的にユニコード指定にしてやる必要があります。 2. ユニコードを使う「可能性がある箇所」では必須です。 なのでTCHARを使う場合はやっておいてください。 とはいえ、アプリケーション中で一つのロケールしか必要ないのであれば アプリケーションの最初に1回だけ呼んでおいて、そのまま変更しなければOKです。 3. これは epistemeさんが、baysidehotelさんの「関数のロジック」をもとに splitを書いたときに 内部でmallocを使用したことに起因するものです。 もし私が一から作る場合は 確保と解放は近くに書かれてた方がぱっと見分かりやすいので、呼び出し側で確保するともいます。 コードを一部お借りすると MFCが使えるという事でC++の規則込みで書いてしまいますが #include <windows.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <locale.h> #include <tchar.h> typedef LPCTSTR const TcsLiteral; int split( TCHAR* p, LPCTSTR words[], const TCHAR delimiter ){ int row = 0; words[row] = p; for ( ; *p; ++p ) { if ( *p != delimiter ) continue; *p = _T('\0'); words[++row] = p+1; } return ++row; } /* おためし */ int main() { TcsLiteral input = _T("ソーシャル,ネットワーク,,ソリューション,"); LPCTSTR words[5]; //おためしなので決め打ち TCHAR* const temp = _tcsdup( input ); //確保に当たる setlocale( LC_ALL, "japanese" ); const int n = split( temp, words, _T(',') ); for ( int i = 0; i < n; ++i ) _tprintf( _T("[%s]\n"), words[i] ); free(temp); //対で解放 } 私ならこんな感じにするかもしれません。 実行結果 [ソーシャル] [ネットワーク] [] [ソリューション] []

baysidehotel
質問者

お礼

回答ありがとうございます。 1.2.について →やはり根本的には相当数のコストをかけて該当箇所を修正するのがベストですね  ただ実施できるかは。。。 3.について →epistemeさんのコードをよく見ていませんでした。  メモリの確保と開放がmain()内にあるので保守性を考慮するとこちらのほうが  いいのかもしれません。

回答No.3

>  内部的にTCHAR型にキャストして処理すればよいでしょうか? あなたのいうUNICODE対応とは何ですか? 呈示したコードは文字セットがchar, wchar_tのどちらでも動くことを 目的としたものです。 > setlocale()がコールされていますが、必須でしょうか? それは僕がきめられることではありません。 > main()関数の最後にfree()がコールされていますが、TCHAR型は > メモリ領域を確保いているのでしょうか? コードを読んでください。spィt内で確保した領域を解放しています。

baysidehotel
質問者

お礼

何度も回答いただきありがとうございます。 1.2.については不具合を指摘した人の環境を確認してみます。 3.についてはコードをよく読んでいませんでした。 #1で示していただいた内容と、他の方のアドバイスも含めて プロジェクトのリーダに相談いたします。 お手数をおかけしました。

  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.2

そのファイルがShift_JISで書かれていているのでは? それをchar*として読み込めば、「全角」文字は「2つの文字」になります。 「ソ」を2つに分けたとき、2番目が「\」になっています。 この2番目の\を「ソの一部」と判断できずに「\」だと判定してしまっているのが今の現象です。 ※ 「シフトJISのだめ文字」として有名な現象です。検索すれば他にもあることがわかります。 プロジェクト全体を変えるなら、TCHARを使って全角文字を「1文字」として処理するようにするのがいいのでしょう (#1さんにある方法です) ここだけを変えるなら、「2バイト目でないdelimiter」だけを処理するようにすることでしょう。 例えば。 bool is2byte = false ; // 2バイト目かどうかのフラグ while(*str != NULL){  // 文字列を分割する処理  if( is2byte || (str != delimiter) ){ //2バイト目ならdelimiterとの比較はしない   words[i][j++] = *str;   // 次の処理が2バイト目かどうか   if(is2byte){    is2byte=false;   }else{    is2byte=(*strが2バイト文字の1バイト目):   }  }  else {   words[i][j] = NULL   i++; j=0;   is2byte=false;  } とか。 あと、細かいことかもしれませんが > while(*str != NULL){ > words[i][j] = NULL 「何も指していないポインタ」であるNULLと、ヌル文字'\0'は別です。 VC++の実装でNULL='\0'=0にはなっていますが。

baysidehotel
質問者

お礼

回答ありがとうございます。 >「ソ」を2つに分けたとき、2番目が「\」になっています。 >この2番目の\を「ソの一部」と判断できずに「\」だと判定してしまっているのが今の現象です。 知りませんでした。 いままで文字コードを意識してコーディングをしたことがなかったもので・・・ >あと、細かいことかもしれませんが >> while(*str != NULL){ >> words[i][j] = NULL >「何も指していないポインタ」であるNULLと、ヌル文字'\0'は別です。 >VC++の実装でNULL='\0'=0にはなっていますが。 こちらのほうは改修に合わせて修正します。

回答No.1

...別に問題なさそうですが。 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <locale.h> #include <tchar.h> int split(const TCHAR* str, TCHAR* words[], TCHAR delimiter) { TCHAR* p; int row = 0; TCHAR* buf = (TCHAR*)malloc(sizeof(TCHAR)*(_tcslen(str)+1)); _tcscpy(buf, str); words[row] = buf; for ( p = buf; *p != _T('\0'); ++p ) { if ( *p == delimiter) { *p = _T('\0'); words[++row] = p+1; } } return ++row; } /* おためし */ int main() { const TCHAR* input = _T("ソーシャル,ネットワーク,,ソリューション,"); TCHAR* words[5]; int n; int i; setlocale(LC_ALL, "japanese"); n = split(input, words, L','); for ( i = 0; i < n; ++i ) { _tprintf(_T("[%s]\n"), words[i]); } free(words[0]); }

baysidehotel
質問者

補足

回答ありがとうございます。 いくつか確認させてください。 1.split()の引数が全てTCHAR型です。   問題の関数は全て現在はchar型で処理しています。   内部的にTCHAR型にキャストして処理すればよいでしょうか?   あるいはsplit()の呼び出し部分も合わせて改修が必要ですか?   プログラム内部では文字列は原則std::string型で保持していて  split()コール時にchar型にキャストしています。 2.setlocale()がコールされていますが、必須でしょうか?   現状はファイルI/Oかコンソール出力時のみ、該当の箇所で直前に  設定して処理後に設定を解除しています。 3.main()関数の最後にfree()がコールされていますが、TCHAR型は  メモリ領域を確保いているのでしょうか? よろしくお願いいたします。

関連するQ&A

専門家に質問してみよう