Cの共用体を使ったローテートについて

このQ&Aのポイント
  • Cでローテート関数を作る下記の模範解答があり、動作も確認済みですが、どうしてそうなるのかイマイチ理解できません。
  • 私の考えでは、(1)のときにビットの状態は 0000 0000 0000 0000、(2)により ch[0]にchの値が渡されるので 0000 0001 0000 0000、(3)で左に1シフトされると 0000 0010 0000 0000 となり、i=1のときch[0]が2、以後インクリメントごとに4,8,・・・,128まではわかります。その後、ビットからはみ出した1が最下位に戻ってくるところがよくわかりません。
  • コメント行の「c[1]」はビット番号のことではなくてch[1]の間違いなのか?だとしてもif文のch[1]は0ではないか???と悩んでます。
回答を見る
  • ベストアンサー

Cの共用体を使ったローテートについて

独習Cで独学中です。 Cでローテート関数を作る下記の模範解答があり、動作も確認済みですが、どうしてそうなるのかイマイチ理解できません。 教えてください。 ========ここから=================================== #include <stdio.h> void rotate(unsigned char *c); int main(void) { unsigned char ch; int i; ch = 1; for(i=0; i<16; i++) { rotate(&ch); printf("%u\n", ch); } return 0; } void rotate(unsigned char *c) { union { unsigned char ch[2]; unsigned u; } rot; rot.u = 0; /* 16ビットをクリア */ ---(1) rot.ch[0] = *c;          ---(2)  /* 整数を左にシフト */ rot.u = rot.u << 1;        ---(3) /* ビットがc[1]にシフトしたかどうか確認する。 シフトしていれば、OR演算を実行して他方の端に戻る */ if(rot.ch[1]) rot.ch[0] = rot.ch[0] | 1; *c = rot.ch[0]; } ========ここまで============================== 私の考えでは (1)のときにビットの状態は 0000 0000 0000 0000 (2)により ch[0]にchの値が渡されるので 0000 0001 0000 0000 <-ch[0]->|<-ch[1]-> (3)で左に1シフトされると 0000 0010 0000 0000 となり、i=1のときch[0]が2、以後インクリメントごとに4,8,・・・,128まではわかります。 その後、ビットからはみ出した1が最下位に戻ってくるところがよくわかりません。 コメント行の「c[1]」はビット番号のことではなくてch[1]の間違いなのか? だとしてもif文のch[1]は0ではないか???と悩んでます。 解説をどうかよろしくお願いします!

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

  • ベストアンサー
  • 1839cc
  • ベストアンサー率54% (12/22)
回答No.2

> コメント行の「c[1]」はビット番号のことではなくてch[1]の間違いなのか? そのとおりですね。 ch[1]の間違いです。 > だとしてもif文のch[1]は0ではないか???と悩んでます。 このプログラムはリトルエンディアン専用のようですね。 以下のコードを考えて見ましょう。 このコードを実行した直後、c[0]~c[3]にはどのような値が入っていると思いますか? union {   unsigned long l;   unsigned char c[4]; } data; data.l = 0x12345678; 実は、これはマシンによって異なります。 たとえば、intel系CPUなどはリトルエンディアンと言われる設計ルールを使っています。 このようなCPUでは値は以下のようになります。 (c[0] == 0x78) (c[1] == 0x56) (c[2] == 0x34) (c[3] == 0x12) それに対して、ビッグエンディアンという設計ルールを採用している68系CPUなどではまったく逆の結果になります。 (c[0] == 0x12) (c[1] == 0x34) (c[2] == 0x56) (c[3] == 0x78) 要は、若いアドレスを上位バイトと捉えるか、下位バイトと捉えるか、の解釈の違いがあるわけです。 では、intが4バイトのリトルエンディアンであると想定してプログラムの動きを追ってみましょう。 まず、1回目の(2)の処理は ch[0] に対して0x01を代入しています。 リトルエンディアンなので最下位バイトに値が入り、uの値は0x00000001となります。 途中は省略しますが、8回目の(2)の処理で、uの値は0x00000080となります。 その直後の(3)の処理で、uの値は0x00000100となります。 このとき、ch[1]の値をリトルエンディアンで考えてみてください。 0x01になっているはずですよね? このサンプルコードは、あくまで共用体のサンプルだからこのような組み方をしてあるようですが、エンディアンの影響を受けてしまうので、あまり好ましいコードではないですね。 普通は共用体ではなく、ビットマスクを利用するべきです。

CONE1234
質問者

お礼

おっしゃる通り、「若いアドレスを上位バイトと捉えるか、下位バイトと捉えるか、の解釈」が間違っていることによるものでした。納得です! リトル(ビッグ)エンディアンという言葉も初めてでしたので新たに知識を得ました。(お恥ずかしながら・・・) この本の共用体の説明図では、ch[0]ch[1]という並びになっているのですが、C言語は汎用性がある一方、CPUによって使い分けなければならないわけですね。 丁寧なご説明どうもありがとうございました。

その他の回答 (2)

  • sakusaker7
  • ベストアンサー率62% (800/1280)
回答No.3

ISOの規格的にはちょっとまずかったんじゃないかなあという 気がするのですがそれはおいといて。 ch[1] ch[0] 0000-0000 1000-0000 の状態からさらに左(MSB方向)へシフトすると 0000-0001 0000-0000 になります。 つまり、c[1]が0から1になります。 そうすると if (rot.ch[1]) ← この条件が真になりますので rot.ch[0] = rot.ch[0] | 1; これで ch[0]の最下位ビットを立てる。つまり シフトの結果 0000-0000 になったch[0]を 0000-0001 にしている。 というわけです。

CONE1234
質問者

お礼

回答ありがとうございます。 ch[1],ch[0]の順に並んでるんですね。納得しました。 「ISOの規格的にまずかった」について気になるのでもう少し調べてみます。

  • php504
  • ベストアンサー率42% (926/2160)
回答No.1

関数内の処理中に各変数を出力すればわかると思いますが ch[0]に1を代入した段階で u も 1になっているはずです 0000 0000 0000 0001 <-ch[1]->|<-ch[0]-> ということですね

CONE1234
質問者

お礼

回答ありがとうございます。 ch[1],ch[0]の順に並んでるんですね。納得しました。

関連するQ&A

  • ビットをローテートするプログラムの解説をお願いします。(C言語)

    下記のプログラムは、rotate() を呼び出されるたびに1つずつ左にビットをローテートするものです。 #include <stdio.h> void rotate(unsigned char *c); int main(void) { unsigned char ch; int i; ch = 1; for(i=0; i<16; i++) { rotate(&ch); printf("%u\n", ch); } return 0; } void rotate(unsigned char *c) { union { unsigned char ch[2]; unsigned u; } rot; rot.u = 0; /* 16ビットをクリア */ rot.ch[0] = *c; /* 整数を左にシフト */ rot.u = rot.u << 1; /* ビットがc[1]にシフトしたかどうか確認する。 シフトしていれば、OR演算を実行して他方の端に戻る */ if(rot.ch[1]) rot.ch[0] = rot.ch[0] | 1; *c = rot.ch[0]; } 【質問】 自分で考えてみたので、間違いがあれば、ご指摘お願いします。 --------------------------------------------------------- ch = 1; だから rotate(&ch); で初回実行時に *c に1を渡す。 i=0 のとき、 rot.ch[0]  rot.ch[1] 0000 0000  0000 0000    rot.u = 0;     rot.u 0000 0001  0000 0000    rot.ch[0] = *c; *c は 1 0000 0010  0000 0000    rot.u = rot.u << 1; if(rot.ch[1]) → 偽 *c = rot.ch[0]; で *c が 0000 0010 すなわち 2 になる。 printf文で「2」を表示。 --------------------------------------------------------- i=1 のとき、 rot.ch[0]  rot.ch[1] 0000 0000  0000 0000    rot.u = 0;     rot.u 0000 0010  0000 0000    rot.ch[0] = *c; *c は 2 0000 0100  0000 0000    rot.u = rot.u << 1; if(rot.ch[1]) → 偽 *c = rot.ch[0]; で *c が 0000 0100 すなわち 4 になる。 printf文で「4」を表示。 --------------------------------------------------------- i=2 のとき、rot.ch[0] が 0000 1000 で *c が 8 になる。 printf文で「8」を表示。 i=3 のとき、rot.ch[0] が 0001 0000 で *c が 16 になる。 printf文で「16」を表示。 i=4 のとき、rot.ch[0] が 0010 0000 で *c が 32 になる。 printf文で「32」を表示。 i=5 のとき、rot.ch[0] が 0100 0000 で *c が 64 になる。 printf文で「64」を表示。 i=6 のとき、rot.ch[0] が 1000 0000 で *c が 128 になる。 printf文で「128」を表示。 --------------------------------------------------------- i=7 のとき、 【ここからがどうしても分かりません。どうか力をかして頂けないでしょうか?お願いします。】

  • C言語のコードについて

    C言語の問題なのですか、作成したのですが内容がわからないです。 コードをわかりやすく解説していただけると嬉しいです。 #include <stdio.h> void printBinary(unsigned char num) { int i ; /*①上位ビットから順に表示する*/ for(i = 7 ; i >= 0; i--) { /*②シフトとマスクを使用しています。*/ printf("%d", (num>>i) &0x01 ); } printf("\n"); } int main(void) { unsigned char num1 = 0xD2;/*11010010*/ unsigned char num2 = 0x5E;/*01011110*/ printf("0xD2 : "); printBinary(num1); printf("0x5E : "); printBinary(num2); return 0; }

  • c言語による2のべき乗

    右、左シフトと2のべき乗による乗除算が同じことを証明するプログラムを作っているのですがうまくいきません。 プログラムを載せるのでどこが間違っているのかご教授お願いします。 #include <stdio.h> int count_bits(unsigned x){ int count = 0; while(x){ if(x&1U) count++; x>>=1; } return(count); } int int_bits(void){ return(count_bits(~0U)); } void print_bits(unsigned x){ int i; for(i=int_bits()-1; i>=0; i--) putchar(((x>>i)&1U) ? '1' : '0'); } int main(void){ unsigned nx, no, n1, n2; printf("非負の整数を入力してください:"); scanf("%u", &nx); printf("何ビットシフトしますか?:"); scanf("%u", &no); n1=nx * (2^no); n2=nx / (2^no); printf("\n整数 = "); print_bits(nx); printf("\n左にシフトした値 = "); print_bits(nx << no); printf("\n右にシフトした値 = "); print_bits(nx >> no); printf("\n2のべき乗で乗算した値 = "); print_bits(n1); printf("\n2のべき乗で除算した値 = "); print_bits(n2); putchar('\n'); return(0); }

  • 共用体のサンプルコード : 内容がわかりません

    #include <iostream> using namespace std; union bits{ bits(double n); void show_bits(); double d; unsigned char c[sizeof(double)]; }; bits::bits(double n) { d = n; } void bits::show_bits() { int i, j; for(j = sizeof(double)-1; j>=0; j--){ cout << "バイト単位のビットパターン" << j << ":"; for(i=128; i ; i >>=1) if(i &c[j]) cout <<"1"; else cout << "0"; cout <<endl; } } int main () { bits ob(1991.829); ob.show_bits(); return 0; } このコードが何をしているのか解説していただけないでしょうか?

  • C言語

    文字列を逆順にするプログラムを考えているのですが分かりません。(例)qwerならrewqです。入力終了は、EOFです。考えたのですが、分かりません。(コンパイルエラーです。)教えてください。宜しくお願いします。#include <stdio.h> unsigned str_length(const char str[]) { unsigned len=0; while (str[len]) len++; return (len); } void put_rstring(const char str[]) { unsigned i = str_length(str): while (i-- >0) putchar(str[i]); } int main(void) { char str[30]; int ch; printf("文字列を入力\n"); /* ----この文字列を入力したあとに、Ctrl+Zを押すと、逆から表示               で反対から、文字列が表示----*/ while (1) { ch=getchar(); if (ch==EOF) break; } printf("逆から表示"); put_rstring(str); puts("です。"); return(0); }

  • 困っています

    どうしても、--unsigned型のビット内容表示--の所が意味が分かりません。分かりやすく教えてください。宜しくお願いします。 /* 0~UINT_MAXを2進・8進・16進で表示 */ #include <stdio.h> #include <limits.h> /*--- 整数xのセットされたビット数を返す ---*/ int count_bits(unsigned x) { int count =0; while (x) { if (x & 1u) count++; x>>=1; } return (count); } /*---- unsigned型のビット数を返す ----*/ int int_bits(void) { return (count_bits(~0U)); } /*---- unsigned型のビットを内容を表示 ---*/ void print_bits(unsigned x) { int i; for (i=int_bits() -1; i>=0; i--) putchar(((x>>i) & 1U) ? '1' : '0'); } int main(void) { unsigned i; for (i=0; i<UINT_MAX; i++) { print_bits(i); printf(" %6o %5u %4X\n", i, i, i); } return(0); }

  • unsigned int型について

    C言語初心者です。 unsigned int型に関する質問です。 --------------------------------- #include <stdio.h> int main(void) { unsigned int in1 = 10; unsigned int in2 = -10; unsigned char ch1 = 10; unsigned char ch2 = -10; printf("in1 = %d\n", i); printf("in2 = %d\n", i); printf("ch1 = %d\n", ch1); printf("ch2 = %d\n", ch2); return 0; } --------------------------------- 上記のプログラムを作成して実行すると、結果は以下の通りです。 [実行結果] in1 = 10; in2 = -10; ch1 = 10; ch2 = 246; 変数in2の値を表示した結果に関してですが、 unsigned int型にも関わらず負の値「-10」が表示されるのは 何故でしょうか?

  • C++ basic_ostreamの拡張

    UTF-16文字列を扱うためのostreamを用意したいのですが、wcoutの代わりに、unsigned shortを用いたostreamを使いたいと思いました。 そこで、basic_ostreamのクラスのunsigned short型のインスタンスを作ったのですが、以下のエラーが出てしまってコンパイルが出来ませんでした。 「error C2296: '<<' : 無効です。左オペランドには型 'u16ostream (__cde cl *)(void)' が指定されています。」 「error C2297: '<<' : 無効です。右オペランドには型 'u16char *' が指定 されています。」 コンパイラはVC++2008です。 それとついでですが、通常通りwcoutを使う時みたいに、localeを設定する必要はあるのでしょうか? 回答、よろしくお願いします。 /* 以下ソースコード */ #include <iostream> typedef unsigned short u16char; typedef basic_ostream<u16char> u16ostream; int main() { u16ostream ucout(); u16char* str = (u16char*)(L"ああ"); ucout << str << '\n'; return 0; }

  • 「動的確保した2次元配列のメモリ解放」を関数化したい

    質問タイトルの通りですが、 「動的確保した2次元配列のメモリ解放」をC言語で関数化したいと思っています。しかし、関数の引数には動的確保した配列の先頭アドレスのみ渡す形にしたいです。そのような場合の関数化は可能ですか? どうもうまくいかず、困っています。 以下、具体的に、サンプルソースを記述します。 わかる方、よろしくお願いします。 //====================================================// #include<stdio.h> unsigned char** AllocByteArray2d(int column, int row); void FreeByteArray2d(unsigned char** box); int main(voidls){ unsigned char array**; array = AllocByteArray2d(2, 3); FreeByteArray2d(array); return 0; } unsigned char** AllocByteArray2d(int column, int row){ unsigned char* box; box = (unsigned char**)malloc( sizeof(unsigned char*)*column ) int i; for(i=0; i<column; i++){ box[i] = (unsigned char*)calloc( row, sizeof(unsigned char)); if(box[i] == NULL) exit(EXIT_FAILURE); } return box; } //引数では配列の先頭アドレスだけ渡す形にしたい void FreeByteArray2d(unsigned char** box){ //ここをどう書いたらいいかわからない }

  • C言語

    入力した文字列と文字列'x'を受け取り、'x'の位置のポインタを返すのですが、例えば、saxcvと入力すると、xcvと表示されるのですが、このソースだと、saxcvと全部表示されてしまいます。分からないので、教えてください。宜しくお願いします。 #include <stdio.h> char *a(char *sew) { char *p=sew; while (*sew != 'x') { sew++; } return(p); } int main(void) { char str[21] ={'\0'}; int i=0,no=0; char ch; printf("文字を入力してください:"); while (i<21) { ch=getchar(); if (ch=='\n') {break;} else if (ch != '\0' && ch != '\0') { str[i] =ch; i++; } } for(i=0; i<20; i++) { if(str[i]=='x') { printf("'x'以降は%sです。\n", a(str)); no=1; break; } } if (no==0) printf("'x'は見つかりませんでした。"); return (0); }

専門家に質問してみよう