C言語関係掲示板

過去ログ

No.1001 16進データ列を 順に10進数に変換する

[戻る] [ホームページ]
No.909

16進データ列を 順に10進数に変換する
投稿者---もみあげ(2003/12/25 22:10:12)


16進データ(00〜FF)のデータ列を読み込んで、
10進データ(0〜256)に変換し、配列に格納する
という処理について質問させてください。

例)
16進数データ:FFFEFA30F2
10進数の配列:{255,254,250,48,242}

下記のように考えましたが、どうでしょうか?

char input[256]="FFFEFA30F2";   // テストデータ
char tmp[10]="";                // 作業用
int output[126]={0};            // 結果格納

int length=strlen(input);      // テストデータのレングス

for(int i=0; i<length/2; i++){
    memcpy(tmp,input+2*i,2);            // 2文字ずつ読み込む
    output[i] = strtoul(tmp,NULL,16);   // 10進数に変換し、配列に格納 
}


Cの経験はまだ浅く、
識者の方のご意見が聞きたくて投稿させて頂きました。
よろしくお願いします。

No.910

Re:16進データ列を 順に10進数に変換する
投稿者---あかま(2003/12/26 06:16:24)


memcpyだと文字列の最後に'\0'がつかないので
strncpyにしないとバグになります。

あとは特に?綺麗なプログラムだと思います。
少しでも無駄を無くしたいなら
/2などの定数をループの外に出す。
上書きするなら初期化はいらない。
ポインタを使う。
とか個人的な趣味に近いものもあるかもしれませんが。

char input[256]="FFFEFA30F2";   // テストデータ
char tmp[10];                // 作業用
int output[126];            // 結果格納

char *length=input+strlen(input);      // テストデータのレングス

for(char *i=input; i<length; i += 2){
    strncpy(tmp,i,2);            // 2文字ずつ読み込む
    output[i] = strtoul(tmp,NULL,16);   // 10進数に変換し、配列に格納 
}


No.911

Re:16進データ列を 順に10進数に変換する
投稿者---YuO(2003/12/26 08:22:55)


>>下記のように考えましたが、どうでしょうか?

全く問題ないと思いますが。



>memcpyだと文字列の最後に'\0'がつかないので
>strncpyにしないとバグになります。

ん?
char tmp[10] = "";

という定義は,
char tmp[10] = { 0 };

と等しく,結果として
char tmp[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

と等しいです。

これにより,元々のプログラムはtmp[2]は常に0となり,
ライブラリの文字列として使えます。

それに対して,あかまさんのプログラムではtmpが初期化されていないため,
tmp[2]は不定です。
そして,strncpyによってナル文字は付加されないため,
strncpyを終えた時点でもtmp[2]は常に不定です。
よって,strtoulにナル文字で終端するとは限らない文字列でない物を渡すことになり,
結果は不定となります。


No.914

Re:16進データ列を 順に10進数に変換する
投稿者---あかま(2003/12/26 15:41:02)


ありゃ、失礼しました。
char tmp[10] = "";

tmp[0]='\0';
だと思っていました。
あーんどstrncpyは'\0'つかないのねー。あはは(笑ってごまかす)

アホレスで2つも賢くなっちゃったよ。


この投稿にコメントする

削除パスワード

No.912

Re:16進データ列を 順に10進数に変換する
投稿者---nop(2003/12/26 09:30:26)


>16進データ(00〜FF)のデータ列を読み込んで、
>10進データ(0〜256)に変換し、配列に格納する
>という処理について質問させてください。

必ず16進数文字以外はありえない場合には、
sscanf()を使うという手もあります。

sscanf()の利点はワークエリアが不要な点でしょうかね。

例:
 int val;
 char buf[] = "12345678";
 sscanf( buf, "%2X", &val );

で val に 0x12 が格納されます。

No.915

Re:16進データ列を 順に10進数に変換する
投稿者---Ancient Hacker(2003/12/28 21:22:56)


int i = 0;
for (char *p = input; *p != '\0'; p += 2)
{
    if (p[1] == '\0')
    {
        /* 偶数個でなければ切り捨てる */
        break;
    }
    tmp[0] = p[0];
    tmp[1] = p[1];
    output[i++] = strtoul(&tmp, NULL, 16);
}

〆能蕕 strlen で文字数を計測しなくてもよさそうです。
⊇蘓桓圓砲呂△蠅ちなのですが,無理して memcpy や strcpy などのランタイム関数を使う必要はありません。単純に2文字をコピーしたほうがより自然で明快だと思われます。
B召諒が指摘するとおり tmp[] および output[] に対する初期化もあまり意味がありません。(あっても問題はないし,デバッグ時には便利なこともあるのですが)
ざいて言うなら,日本語環境の文字列では unsigned char を使ったほうが安全です。
イいんせん私は発想が Ancient なので,本当にアプリケーション内で使用する処理であれば strtoul などのランタイムは使わず,自分で実装します。そのほうが確実です。
6) ところで for (int i = 0... のような記述は C ではなく C++ では?

No.916

Re:16進データ列を 順に10進数に変換する
投稿者---YuO(2003/12/29 17:32:33)


>(5) いかんせん私は発想が Ancient なので,本当にアプリケーション内で使用する処理であれば strtoul などのランタイムは使わず,自分で実装します。そのほうが確実です。

確実とは?

標準関数を使うことによって,何をしているかを他のプログラマ(時間経過後の自分含む)に対して,
コメント無しで明示することが出来ます。

また,標準関数は最低限のテストをしていることが予測されますし,
一般的に使われるような関数(strtoulなど)では,大量のユーザーによるチェックがされています。
品質面において,標準関数を抜くことは難しいです。
#標準関数よりも制限を強めれば,小さく速いルーチンを書くことは可能ですが……。

そもそも,標準関数を使わないことによる利点がみつかりません。


>(6) ところで for (int i = 0... のような記述は C ではなく C++ では?

思いっきりCですよ。
ISO/IEC 9899:1999でちゃんと認められています。


No.917

Re:16進データ列を 順に10進数に変換する
投稿者---Ancient Hacker(2003/12/29 23:14:32)


>標準関数を使うことによって...コメント無しで明示することが出来ます。
言わんとするところはわかるのですが,初心者のかたも見ています。ソースコードにはぜひともコメントを書いてください。たとえわかりきった処理でも,たとえ標準ライブラリを使っていても,煩瑣にならない限りはコメントを書いてください。これは太古からの先人の知恵です!(そのわりには私の例示したコードにはコメントがありません←凶悪)

>標準関数よりも制限を強めれば,小さく速いルーチンを書くことは可能です
小さく速いルーチンというのも,昨今ではさほど重要ではありません(世には x86 プロセッサの命令サイズやペアリング・ストールをいたく気にする人々もいますが,そうした最適家の諸氏にとっては魅力的でしょう)私はむしろ「制限を強めれば」という点に重きを置きたいのです。ライブラリ関数のソースコードが提供されている場合もありますが,たとえば文字の検査が貧弱なため関数の中ほどだけを修正(強化)したい場面があります(今回の件はそうでもありませんが)。ライブラリ関数を使おうとして,その前後で動作を補正するような処理を書くと,ひどく不自然な処理になることがあります。むしろライブラリ関数(の修正バージョン)をまるごとごっそり自分で実装したほうが「動作の詳細」が確実になることがあるのです。

>一般的に...大量のユーザーによるチェックがされています。品質面において,標準関数を抜くことは難しいです。
ユーザー(プログラマー)がチェックしなければならなかったライブラリというのは,品質に問題のある,信頼するに足りないライブラリだということになります。が,この論理はいささか屁理屈じみています。それはともかく,現実的に見てライブラリ関数は困ったことに絶対確実とまではいきません。英語を母国語としている提供元(のプログラマー)が提供するマルチバイト文字用ライブラリが,しばしば問題を生じていたのはさほど過去のことではありません。また 16 ビット → 32 ビット, 32 → 64 ビットのようにシステム環境が大きく変化した場合に,その初版近くのコンパイラ(および一緒に提供されたライブラリ)が不具合を含む可能性が高いことは,多くの開発者の認めるところです。最終的には改善されるのでしょうが,それを待っていられない状況もあります。

>標準関数を使わないことによる利点がみつかりません。
利点の有無に関してはまったく触れていません。しかし,利点がないと思えばぜひとも標準関数を使って下さい。先人が築いた技術をうまく利用することも,プログラマーの重要なスキルのひとつです。

>ISO/IEC 9899:1999
 初耳です(どういったことが規格化・明文化されているかに関しては,私はいまだかつて細部を確認したことがありません)C言語もぜひとも進化しなければなりません。昨今ではオブジェクト指向に押され気味ですが,Cはまだまだ使える強力な言語です。

 なお,私が以前に掲示したサンプル中で変数宣言およびループ前処理としての tmp[2] = '\0' が欠落しています(←ここもツッコんでほしかった!)。また,文サイズ縮小のため本レスでの引用文を一部省略していますが,悪しからずご了承ください。さらにまた,最初の 16 進データ云々とは内容が大幅に変わってしまったことお詫びいたします。

No.940

Re:16進データ列を 順に10進数に変換する
投稿者---YuO(2004/01/05 23:50:43)


>>標準関数を使うことによって...コメント無しで明示することが出来ます。
>言わんとするところはわかるのですが,初心者のかたも見ています。
>ソースコードにはぜひともコメントを書いてください。
>たとえわかりきった処理でも,たとえ標準ライブラリを使っていても,煩瑣にならない限りはコメントを書いてください。

非標準の関数に関しては,コメントを書く意義はあると思いますが,
標準の関数に関して,コメントを書く意義は無いです。
puts("Hello, world!"); /* Hello, world!と改行を標準出力に出力する */

なんていうのは冗長なだけです。
#コメント自体,処理の塊単位で十分な気がしますけどね……。


>>標準関数よりも制限を強めれば,小さく速いルーチンを書くことは可能です
>小さく速いルーチンというのも,昨今ではさほど重要ではありません。

もちろん,それはわかっています。

私が考える標準関数を使わない場所とは次のような物です。
time-criticalな場所において標準関数の汎用性が不要な場合。
これを前提として先のようなことを書きました。入力に前提を設けることによって,チェックを省いたり,高速なアルゴリズムを使ったりすることができます。C++の例になりますが,unsigned char buf[5];を逆順にするのに
std::reverse(buf, buf + 5);
とするよりも,
inline void reverse_uchar_array_5 (unsigned char * p) {
    unsigned char t = p[0];
    p[0] = p[4];
    p[4] = t;
    t = p[1];
    p[1] = p[3];
    p[3] = t;
}
を利用した方が圧倒的に速くなります(Efficient C++ 11.7より若干変更)。 これは,reverseは汎用なので,これらのコードをループで実現することになる為です。
標準で処理方法が定められていない場合に特定の方法を利用したい場合。
bsearch,qsort,srandやrandなどが相当します。
実装定義動作に関連する場合。
例えば,ワイド文字としてUCS-2をUTF-16で利用している処理系においても,多バイト文字列からUTF-16に変換するためにmbstowcsを使わずに自分で書くときなど。



>ライブラリ関数のソースコードが提供されている場合もありますが,たとえば文字の検査が貧弱なため関数の中ほどだけを修正(強化)したい場面があります。
>ライブラリ関数を使おうとして,その前後で動作を補正するような処理を書くと,ひどく不自然な処理になることがあります。
>むしろライブラリ関数(の修正バージョン)をまるごとごっそり自分で実装したほうが「動作の詳細」が確実になることがあるのです。

動作が確実でなくなるのは,関数自体ではなく補正ルーチンのせいですよね。
もともと本来の関数の仕様に追加で条件を加えているのですから,
その補正ルーチン側をちゃんと作れば,動作自体はちゃんと確実になります。


>それはともかく,現実的に見てライブラリ関数は困ったことに絶対確実とまではいきません。英語を母国語としている提供元(のプログラマー)が提供するマルチバイト文字用ライブラリが,しばしば問題を生じていたのはさほど過去のことではありません。
>また 16 ビット → 32 ビット, 32 → 64 ビットのようにシステム環境が大きく変化した場合に,その初版近くのコンパイラ(および一緒に提供されたライブラリ)が不具合を含む可能性が高いことは,多くの開発者の認めるところです。

標準関数に多バイト文字列用関数はほとんど存在しないと思いますが……。
また,後者は特定の環境における問題であって,バージョンアップによって解決されうる問題です。

どちらにしても,まずは標準関数を使い,それがbuggyなら代替関数を作ってプリプロセッサで切り分ける,
という方が「確実」だと思いますけど。
#つまり,標準関数を使わないという選択肢は最初は存在しない。


ちなみに,非標準ライブラリ関数に関しては,最初から使わないという選択肢が存在すると思っています。
移植性が全くなくなるためです。
#コンパイラのバージョンアップを含む。


>>標準関数を使わないことによる利点がみつかりません。
>利点の有無に関してはまったく触れていません。

利点があるからこそ,
>>>(5)いかんせん私は発想が Ancient なので,本当にアプリケーション内で使用する処理であれば strtoul などのランタイムは使わず,自分で実装します。そのほうが確実です。
と書いたのではないのですか……?


そうそう,ここのエンコーディングはEUC-JPなのでADA1〜ADA5にあたる文字は存在しません。
Windowsで書いたと予測して(5)に直しましたが,(木)の方がよかったですか?


No.941

Re:16進データ列を 順に10進数に変換する
投稿者---nop(2004/01/06 00:32:44)


長々と書いてますが、

>これは太古からの先人の知恵です。

と言っておきながら、
「先人の知恵」の結集である標準関数を使用したがらない時点で理論が破綻してるかと。



P.S.これ以上はスレ違いかと。

ヨコヤリ失礼。