4


0

これより4バイトを3にパックするためのより良い方法は何ですか?

値の配列はすべて0から63の範囲内にあり、値は6ビットしか必要とせず、次の値の最初の2ビットを格納するために追加の2ビットを使用できるので、4バイトごとに3にパックできますそうです。

パッキングをして開始ビットを追跡するために switch`文と nextbit`変数(デバイスのようなステートマシン)を使う前にこれをやったことは一度もありません。 私は確信していますが、もっと良い方法があるはずです。

提案/手がかりをお願いします、しかし私の楽しみを台無しにしないでください;-)

ビッグ/リトルエンディアンに関する移植性の問題はありますか?

_btw:このコードが正常に機能することを確認しました。再度解凍し、入力と比較します。 いいえ、それは宿題ではありません。自分で設定した運動だけです。

/* build with gcc -std=c99 -Wconversion */
#define ASZ 400
typedef unsigned char uc_;
uc_ data[ASZ];
int i;
for (i = 0; i < ASZ; ++i) {
    data[i] = (uc_)(i % 0x40);
}
size_t dl = sizeof(data);
printf("sizeof(data):%z\n",dl);
float fpl = ((float)dl / 4.0f) * 3.0f;
size_t pl = (size_t)(fpl > (float)((int)fpl) ? fpl + 1 : fpl);
printf("length of packed data:%z\n",pl);

for (i = 0; i < dl; ++i)
    printf("%02d  ", data[i]);
printf("\n");

uc_ * packeddata = calloc(pl, sizeof(uc_));
uc_ * byte = packeddata;
uc_ nextbit = 1;
for (int i = 0; i < dl; ++i) {
    uc_ m = (uc_)(data[i] & 0x3f);
    switch(nextbit) {
    case 1:
        /* all 6 bits of m into first 6 bits of byte: */
        *byte = m;
        nextbit = 7;
        break;
    case 3:
        /* all 6 bits of m into last 6 bits of byte: */
        *byte++ = (uc_)(*byte | (m << 2));
        nextbit = 1;
        break;
    case 5:
        /* 1st 4 bits of m into last 4 bits of byte: */
        *byte++ = (uc_)(*byte | ((m & 0x0f) << 4));
        /* 5th and 6th bits of m into 1st and 2nd bits of byte: */
        *byte = (uc_)(*byte | ((m & 0x30) >> 4));
        nextbit = 3;
        break;
    case 7:
        /* 1st 2 bits of m into last 2 bits of byte: */
        *byte++ = (uc_)(*byte | ((m & 0x03) << 6));
        /* next (last) 4 bits of m into 1st 4 bits of byte: */
        *byte = (uc_)((m & 0x3c) >> 2);
        nextbit = 5;
        break;
    }
}

5 Answer


4


IETF RFC 4648の「The Base16、Base32、およびBase64のデータエンコーディング」を調べてください。

部分コード批評:

size_t dl = sizeof(data); printf( "sizeof(data):%d \ n"、dl); float fpl =((float)dl / 4.0f)* 3.0f; size_t pl =(size_t)(fpl>(float)((int)fpl)? fpl 1:fpl); printf( "パックデータの長さ:%d \ n"、pl);

浮動小数点のものを使わないでください - ただ整数を使ってください。 C99ライブラリがあると仮定して、 'size_t’値を表示するには '%z’を使用してください。

size_t pl =((dl 3)/ 4)* 3。

部分的な単位が残されるまで3バイトの入力単位を扱い、次に特別な場合として残りの1または2バイトを扱うことで、ループを単純化できると思います。 参照されている標準では、最後にパディングするために1つまたは2つの '='記号を使用すると述べています。

私はBase64エンコーダとデコードを持っていますが、そのうちのいくつかを行います。 あなたは、Base64の「デコード」部分、つまりBase64コードが3バイトに格納されるべき4バイトのデータを持っていることをあなたのパッキングコードとして説明しています。 Base64エンコーダはあなたが必要とするアンパッカーに対応します。

Base-64デコーダ

注:base_64_invは、入力バイト値ごとに1つずつ、合計256個の値の配列です。それは各エンコードされたバイトのための正しいデコードされた値を定義します。 Base64エンコーディングでは、これはスパース配列 - 3/4ゼロです。 同様に、base_64_mapは、値0..63と対応する記憶値の間のマッピングです。

列挙型{DC_PAD = -1、DC_ERR = -2}。

static int decode_b64(int c){int b64 = base_64_inv [c];

if(c == base64_pad)b64 = DC_PAD。そうでなければ(b64 == 0

/ * 4バイトを3 *にデコードする。* / int int decode_quad(const char * b64_data、char * bin_data){int b0 = decode_b64(b64_data [0]);} int b1 = decode_b64(b64_data [1]); int b2 = decode_b64(b64_data [2]); int b3 = decode_b64(b64_data [3]); intバイト。

if(b0 <0 || b1 <0 || b2 == DC_ERR || b3 == DC_ERR ||(b2 == DC_PAD

/ *入力Base-64文字列を元のデータにデコードします。 返された出力長、または負のエラー* / int base64_decode(const char * data、size_t datalen、char * buffer、size_t buflen){size_t outlen = 0; if(datalen%4!= 0)が返される(B64_ERR_INVALID_ENCODED_LENGTH)。 (BASE64_DECLENGTH(datalen)> buflen)の場合、(B64_ERR_OUTPUT_BUFFER_TOO_SMALL)が返される。 while(datalen> = 4){int nbytes = decode_quad(data、buffer outlen); if(nbytes <0)が返す場合(nbytes)。 outlen = nバイト。データ= 4。 datalen  -  = 4。 assert(datalen == 0); / *以前の%4チェックのおかげで* / return(outlen); }

Base-64エンコーダ

/ * 3バイトのデータを4 *にエンコードする* / static void encode_triplet(const char * triplet、char * quad){quad [0] = base_64_map [(triplet [0] >> 2)

/ * 2バイトのデータを4 *にエンコードする* / static void encode_doublet(const char * doublet、char * quad、char pad){quad [0] = base_64_map [(doublet [0] >> 2)

/ * 1バイトのデータを4 *にエンコードします。

/ *入力データをBase-64文字列としてエンコードします。 返される出力長、または負のエラー。* / static int base64_encode_internal(const char * data、size_t datalen、char * buffer、size_t buflen、char pad) const char * bin_data =(const void *)データ。 char * b64_data =(void *)バッファ。

if(outlen> buflen)が(B64_ERR_OUTPUT_BUFFER_TOO_SMALL)を返す。 while(datalen> = 3){encode_triplet(bin_data、b64_data); bin_data = 3。 b64_data = 4。 datalen  -  = 3。 b64_data [0] = '\ 0';

if(datalen == 2)encode_doublet(bin_data、b64_data、pad);そうでなければ(datalen == 1)encode_singlet(bin_data、b64_data、pad); b64_data [4] = '\ 0'; return((b64_data  -  buffer)strlen(b64_data)); }

Base64エンコーディングに異体字アルファベットを使用し、データの埋め込みを行わないように管理している製品を扱う必要があるため、作業が煩雑になります。パディング 'base_64_map’配列には、0..63の範囲の6ビット値に使用するアルファベットが含まれています。


4


だから、これはちょっと[.kbd] #https://stackoverflow.com/questions/tagged/code-golf [code-golf]#のようなものですね。

'' '' '

#include
#include

static void pack2(unsigned char *r, unsigned char *n) {
  unsigned v = n[0] + (n[1] << 6) + (n[2] << 12) + (n[3] << 18);
  *r++ = v;
  *r++ = v >> 8;
  *r++ = v >> 16;
}

unsigned char *apack(const unsigned char *s, int len) {
  unsigned char *s_end = s + len,
                *r, *result = malloc(len/4*3+3),
                lastones[4] = { 0 };
  if (result == NULL)
    return NULL;
  for(r = result; s + 4 <= s_end; s += 4, r += 3)
    pack2(r, s);
  memcpy(lastones, s, s_end - s);
  pack2(r, lastones);
  return result;
}


1


もう1つ簡単な方法は、ビットフィールドを使用することです。 Cの `struct`構文のあまり知られていないコーナーの1つは大きな分野です。 次のような構造になっているとしましょう。

struct packed_bytes {
    byte chunk1 : 6;
    byte chunk2 : 6;
    byte chunk3 : 6;
    byte chunk4 : 6;
};

これは、 chunk1、` chunk2`、 chunk3、そして` chunk4`の型が `byte`であることを宣言していますが、構造体の中で6ビットしか占めていません。 結果は `sizeof(struct packed_bytes)== 3`です。 これで、必要なのは配列を取り出してそれを構造体にダンプするための小さな関数だけです。

void
dump_to_struct(byte *in, struct packed_bytes *out, int count)
{
    int i, j;
    for (i = 0; i < (count / 4); ++i) {
        out[i].chunk1 = in[i * 4];
        out[i].chunk2 = in[i * 4 + 1];
        out[i].chunk3 = in[i * 4 + 2];
        out[i].chunk4 = in[i * 4 + 3];
    }
    // Finish up
    switch(struct % 4) {
    case 3:
        out[count / 4].chunk3 = in[(count / 4) * 4 + 2];
    case 2:
        out[count / 4].chunk2 = in[(count / 4) * 4 + 1];
    case 1:
        out[count / 4].chunk1 = in[(count / 4) * 4];
    }
}

これで、上記のstructを使って簡単に読めるようになった `struct packed_bytes`の配列ができました。


0


ステートマシンを使用する代わりに、現在のバイトですでに使用されているビット数のカウンタを使用するだけで、そこから直接シフトオフセットを導出したり、次のバイトにオーバーフローしたかどうかを判断できます。 エンディアンについて:単一のデータ型のみを使用する限り(つまり、サイズの異なる型へのポインタを再解釈することはできません(例: int * a = …​; short * b =(short *)a;)ほとんどの場合、エンディアンに関する問題は発生しません。


0


DigitalRossのコンパクトコード、Grizzlyの提案、そして私自身のコードの要素を取り上げて、私はついに自分の答えを書きました。 DigitalRossは実用的な実用的な答えを提供しますが、私が理解せずにそれを使用しても、何かを学ぶのと同じ満足感は得られなかったでしょう。 このため、私は自分の答えを元のコードに基づいて選択することにしました。

Jonathon Lefflerがパックドデータ長の計算に浮動小数点演算を使用しないようにするというアドバイスを無視することも選択しました。 与えられた両方の推奨された方法 - 同じDigitalRossも使用し、パックされたデータの長さを最大3バイト増加させます。 これはそれほど問題ではありませんが、浮動小数点演算を使用することで回避することもできます。

ここにコードがあります、批評は歓迎します:

/ * gcc -std = c99でビルド* / #include #include #include

unsigned char * pack(const unsigned char *データ、size_t len、size_t * packedlen){float fpl =((float)len / 4.0f)* 3.0f; * packedlen =(size_t)(fpl>(float)((int)fpl)? fpl 1:fpl); unsigned char * packed = malloc(* packedlen); if(!packed)は0を返します。 const unsigned char * in = data; const unsigned char * in_end = in len; unsigned char * out; for(out =パック; in 4 <= in_end; in = 4){* out = in [0] | ((1で]

int main(){size_t i;符号なしcharデータ[] = {12、15、40、18、26、32、50、3、7、19、46、10、25、37、2、39、60、59、0、17、9、29 、13、54、5、6、47、32}。 size_t datalen = sizeof(データ); printf( "展開されたdatalen:%td \ n展開されたデータ\ n"、datalen); (i = 0; i <datalen; i)printf( "%02d"、data [i])の場合。 printf( "\ n"); size_t packlen; unsigned char * packed = pack(データ、sizeof(データ)、