アセンブリ言語のプルグラムに関する質問です。
ある本でアセンブリを勉強をしているのですがわからない部分があります。
横幅が4の倍数協会にない場合のダミーデータを計算するようなのですが、
なぜダミーデータを計算するのかわかりません。
横幅が4倍でない場合にレジスタと演算回路で計算して4で割り切れない場合の余りを捨てるという意味でしょうか?というかアセンブリプログラムはそのように書いてあるのでしょうか?
個人的に4倍ではなく1バイトが8bitなので8で割ったほうが都合がよいように思えますが。
計算部分とアセンブリ言語のプログラムのある部分に疑問があります。
以上について質問します。疑問のある部分に丸で囲んだ数字を置きます。
wMargineをゼロクリアにして、widthをeaxレジスタに代入します。
4で割った余りを算出するためにeaxレジスタを3で論理積を行う。
(⓵ここで3倍した理由は4で割った際の余りが3以上になるようにするためですか?
だとしたらもともとダミーデータが出ないように計算したほうがいいように思えますが。)
最下位2bitのいずれかが立っているかを検査します。
もし、4で割ったときに余りがあったら、widthを3倍し、それを4で割って得る。そして4から余りを引いてwMargineに設定する。
(⓶「そして4から余りを引いてwMargineに設定する。」の部分の4から余りを引く理由がわかりません)
プログラム通りeaxレジスタにwidthを入れます。そしてebxレジスタに3を代入し乗算します。
(⓷eaxレジスタとebxレジスタを乗算するのはわかるのですがプログラムにはmul ebxとしか書いていないのですが、なぜmul ebxと書いただけでeaxレジスタとebxレジスタを乗算する結果がebxに入る理由がわかりません。演算結果を一旦別のレジスタに入れた後に、mov命令でebxに演算結果をいれるならば理解できます。このアセンブリプログラムを出力したCPUの構造故にmul ebxと書いただけでeaxレジスタとebxレジスタの乗算結果がebxに入るのでしょうか?だとしたら仕方ないです。)
その後4バイトの乗算であるため上位4バイトがedxと下位4バイトがeaxレジスタへ代入されるようです
(⓸この乗算結果を二つのレジスタに乗算の結果を分割して入れている命令部分はプログラムのどこに書いてあるのでしょうか?
どのようにアセンブリ命令を書くことで分割してレジスタに入れられたのでしょうか?)
最後にダミーデータを知るために下位4バイトを入れたeaxレジスタと0x00000003で乗算して余りを取得したと書いてあります。これでダミーデータが得られたそうですが、なぜ得られたのか解説をよんでもいまいちわかりません。(⓹下位4バイトを入れたeaxレジスタと0x00000003で乗算して余りを取得したと書いてあります。これでダミーデータが得られた理由がわかりません。また何で割ったのかわからないのですがアセンブリプログラムには書いてあるでしょうか?)
プログラムは以下の通りです。
mov wMargine, 0 mov eax, width /* 幅 */ and eax, 0x00000003 /* %4 */ jz no_margin xor edx, edx /* クリア */ mov eax, width /* 幅 */ mov ebx, 3 /* 3 */ mul ebx /* 幅x 3 */ and eax, 0x00000003 /* %4 */ mov ebx, 4 /* 4 */ sub ebx, eax mov wMargine, ebx
以上5つの質問でありますが、どうか解説して頂けると大変助かります。
C言語を勉強している上でより技術を磨くためにアセンブリ言語を身に着けたいと思います。
どうかよろしくお願いいたします。
修正
論理積と足し算を勘違いして使っていました。
ようやく回答者様の皆様の回答が理解できてきました。
以後できる限り気を付けて質問します。
どうかよろしくお願いいたします。
本当に申し訳ありませんでした。
3/19 復習している際に疑問が出来たので再度以下の事に関して質問したいのですが、ここでは書きにくいため新しく質問を作ります。回答していただけると大変ありがたいです。
一時的に質問をしないとのことでしたが今回に関してはどうかお許しください。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答5件
0
ベストアンサー
おそらくビットマップの仕様
水平方向のバイト数が4の倍数ではないときは、0x00で埋めて4の倍数にする。
です。(なんか他にもavi/jpg/png/gif/mpegの仕様で幅に制限あるフォーマットがあった気がするけど思い出せない)
個人的に4倍ではなく1バイトが8bitなので8で割ったほうが都合がよいように思えますが。
1bitには0か1の情報しか詰められませんけど、モノクロ画像でしょうか?
> ⓵ここで3倍した理由は4で割った際の余りが3以上になるようにするためですか?
24bit bmpなのでは?
> ⓶「そして4から余りを引いてwMargineに設定する。」の部分の4から余りを引く理由がわかりません
目的はwidth * 3 + wMargine== 4の倍数
にしたいのです。
wMargineが1~4とした場合
n = width * 3 / 4 width * 3 + wMargine= 4 * (n + 1)
この連立方程式を解いてください
編集:計算するとおかしかったので追記
数の性質です。
ある数n
の4で割った余りが
- 1の時、n+3は4で割り切れる
- 2の時、n+2は4で割り切れる
- 3の時、n+1は4で割り切れる
- 0の時、nは4で割り切れる
> ⓷eaxレジスタとebxレジスタを乗算するのはわかるのですがプログラムにはmul ebxとしか書いていない
> ⓸この乗算結果を二つのレジスタに乗算の結果を分割して入れている命令部分はプログラムのどこに書いてあるのでしょうか?
以前の回答でx86_64の仕様書を貼り付けましたので割愛
不明な命令くらい自分で調べましょう。
投稿2018/03/02 21:48
編集2018/03/02 22:12総合スコア15147
0
解決済ですが、diracpaulさんへの質問者のコメントには答えたいことがあります。
載せたアセンブラプログラミング以外のアセンブリ命令の組み合わせでも同じような処理を実現できるのでしょうか
アセンブリコードには決まりきった命令順(イディオムも)もよく出現しますが、そもそも命令の粒度が細かいので、人によって・コンパイラによって、同じ処理でもコードの違いは大きくなります。以下、コメントにC言語風の擬似命令を併記しながら解説します。
xor edx, edx // edx ^= edx; mov edx, 0 // edx = 0; sub edx, edx // edx -= edx;
既にご存知でしょう、これは3つ共にレジスタedxの0クリアが目的ですから、
mov edx, 0 が自然ですが、これだと32bitの0という値を機械語に持つため、機械語命令が長くなる・遅いかもしれない(アセンブルリストで機械語の違いを確認しましょう)、そこで2進数の性質を利用してxor命令がよく使われます。
edxには乗算の結果が格納されることから、おそらく安全のため0クリアした・・・宣言した変数はすべて0に初期化するのと同じ気持ちなのでしょう。でもmul命令の仕様がわかっていれば、edx を0にする必要が無いことは明らか。即ち、xor命令は削除できます。
次に乗算命令の箇所。
mov eax, width // eax = width; mov ebx, 3 // ebx = 3; mul ebx // edx:eax = eax * ebx;
32bit * 32bit の計算結果は 64bit になります。mul命令の結果が EDX:EAX に格納される(EDX, EAX レジスタを固定的に使う)ことから、乗数を EDX に入れて乗算させることが多いのではないでしょうか。レジスタは限りある資源なので、関数の中で使うレジスタを減らした方が何かと有利なのです。
どうせ mul 命令は edx を使うのだから、私ならここは edx を使い回します。ebx を使わずに済む事は大きいのです。こんな風に
mov eax, width // eax = width; mov edx, 3 // edx = 3; mul edx // edx:eax = eax * edx; ... // 途中省略 mov edx, 4 sub edx, eax // edx = 4 - (width * 3) % 4;
それはともかく、3倍は同じ値を3つ足してもよいので、例えばこうできる。
mov eax, width // eax = width; mov edx, eax // edx = eax; add eax, edx // eax += edx; // width * 2 add eax, edx // eax += edx; // width * 3
mulは時間がかかる命令ですから、一命令増えても実行速度は断然速いです。
じゃあ10倍するには10回加算を繰り返すかというと、そうではなくて、2のべき乗(2, 4, 8, 16 ...)倍にシフト命令を使えることをご存知でしょう。10倍は8倍+2倍だから、10倍するマクロは
#define MultBy10(x) ((x) << 3 + (x) << 1)
のように、シフトと加算の組み合わせで実現できます。もっとも今のコンパイラは何もしなくても大抵こんな風に最適化しますが、アセンブリ言語ならどう書けばよいか、質問者への課題にしましょうか笑。
さて、ここが重要。
提示されたアセンブリコードは何を計算しているのか。全体をC言語風に書けば、こうなります。
C
1 wMargine = 0; 2 if ((width % 4) == 0) goto no_margin; 3 wMargine = 4 - ((width * 3) % 4); // この計算は何か?
width から wMargine を求めていることがわかります(と共に処理を分岐している)が、width % 4 は 0, 1, 2, 3 しかなく、0の場合は明らかなので、0以外を場合分けして考えると、計算結果はこうなります。
width%4 | width | width*3 | (width*3)%4 | 4-(width*3)%4 |
---|---|---|---|---|
1 | 4n + 1 | 12n + 3 | 3 | 1 |
2 | 4n + 2 | 12n + 6 | 2 | 2 |
3 | 4n + 3 | 12n + 9 | 1 | 3 |
「width % 4」と「4 - ((width * 3) % 4)」が一致する!・・・実は乗算も減算も不要、即ちあの部分は無駄なコードなのです(あくまで提示されているコードを見る限りですが)。つまり、次の5行だけが残る。
mov wMargine, 0 // wMargine = 0; mov eax, width // eax = width; and eax, 0x00000003 // eax %= 4; jz no_margin // if (eax == 0) goto no_margin; mov wMargine, eax // wMargine = eax;
さらに、x86はmov命令でフラグレジスタが変化しないので、次の4行まで絞り込める・・・
mov eax, width and eax, 0x00000003 // eax = width % 4; mov wMargine, eax // wMargine = width % 4; jz no_margin // if (eax == 0) goto no_margin;
このような「無駄」はアセンブリ言語に限った事ではありません。言い換えれば、漫然とコードを書くなら、どんな言語を使っても無駄な計算をしたり効率が悪かったりするという戒めになるでしょう。
wMargine = 4 - ((width * 3) % 4); // この計算は何か?
念の為、この部分はどういうコードであったか。C言語風コメントをつけておきます。
xor edx, edx // edx = 0; (削除可能な命令) mov eax, width // eax = width; mov ebx, 3 // ebx = 3; mul ebx // edx:eax = eax * ebx; 即ち edx:eax = width * 3; and eax, 3 // eax &= 3; は eax %= 4; と同じ mov ebx, 4 // ebx = 4; sub ebx, eax // ebx -= eax; 即ち ebx = 4 - ((width * 3) % 4); mov wMargine, ebx // wMargine = ebx;
投稿2018/03/04 08:05
編集2018/03/04 16:37総合スコア1380
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/03/04 09:33 編集
2018/03/18 19:53 編集
2018/03/18 22:55 編集
2018/03/18 22:30
2018/03/20 22:24
2018/03/21 00:46 編集
2018/03/21 01:20
2018/03/21 02:47
2018/03/21 02:49
2018/03/21 02:57
2018/03/21 08:16
2018/03/21 12:37
2018/03/21 13:28
2018/03/21 13:50
2018/03/23 10:12
2018/03/23 10:39
2018/03/23 11:39 編集
2018/03/23 11:45
2018/03/23 11:47
2018/03/23 12:02
2018/03/23 12:04
2018/03/23 12:08
2018/03/23 12:15 編集
2018/03/23 12:29 編集
2018/03/23 12:30
2018/03/24 04:57
0
寝ておきたらほぼ終わってたが、とりあえず書いたので
基礎の基礎
1bitとは
2進数1桁の事であり、0か1のどちらかの状態を保存する記憶領域です
1byteとは
(2008年以降)標準的には8bitです。
論理積とは
まずは1ビットの論理積
左辺 & 右辺 = 解
左辺 | 右辺 | 解 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
という1ビットの演算です
1ビットが入力され1ビットが返される演算の事を論理演算といいます
今現在のコンピューターというのは突き詰めるとこういった論理演算で全てが実装されています。
次にビット毎の論理積
これを拡張して8bitの演算を考えます
例: 101 & 15
101 = 0x65 = 0b01100101 15 = 0x0F = 0b00001111 101 & 15 = 0b00000101
両辺ともにビットが立っているところのみ1になります。
AND EAX, 3について考える
これはEAX &= 0b11;ということです
つまり、EAXを2進数表記にした際の最下位2bitのうち立っているビットのみを取り出すものです。
割り算との関係
10進数である数を100で割ったあまりを考える際、下2桁を取り出しますよね?
2進数でも同じです。
0b10=2で割ったあまりが欲しい場合は最下位ビットが立っているなら1で0なら0なんです。
0b100=4で割ったあまりが欲しいならば最下位2bitが4で割ったあまりになるのです。
以上が、CPU関連で必要な前提知識
%演算子
C言語における%演算子は割り算のあまりを計算する演算子です。
10÷3=3あまり1
なので
10%3=1
になります。
割り算というのはCPUの算術演算(普通の算数の演算)の中でも非常に遅くなるべく回避したいものなので
X%4を計算するときはbit毎の論理演算で代用するのが定石になります。
今回のプログラムについて
ビットマップのフォーマットによるとbmpに保存されている幅情報の単位はピクセルです。
1ピクセル=1ドット=点1個と置き換えてもいいです。
bmpには何色使えるかでいくつかの種類があります。
今回対象にしているフォーマットは推測ですが24bpp
1ピクセルごとに24bit=3byte使うことで16777216色使えるフォーマットだと思います。
1行ごとに最低限必要なバイト数は3*width byteですが
これを4byteの倍数になるように0を付け加えなければいけません。
仮にwidth=31ピクセルの場合を考えます。
31*3=93
byte = 93=4*23 + 1
byteなので3byte足して1行ごとに96byteになります。
93から余りの1がパディング長なのではなく、96-93の3byteがパディング長なのです。
どうでもいいこと
ピクセル配列に追加されるバイト列なので
ダミーデータというとピクセル単位で追加される事を個人的に想起します。
投稿2018/03/04 17:10
総合スコア15147
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
あの本書のCPUの構造がわからないので同じような結果になるかはわからないのですが。自分なりにコードを書いてみました。結果としては同じような結果になると思いますでしょうか?
mov eax, width // eax = width;
mov edx, 3 // edx = 3;
mul edx // edx:eax = eax * edx;
がもともとのコードです。
以下が私の書いたコードです。
mov eax, width
mov ebx ,3
mul eax ,ebx //←こう書いた場合は掛け算の結果はeaxに入るのでしょうか?//
mov ebx ,eax //eaxの内容をebxに入れます。//
あーめんどくせー 四の五の言わんとやってみろなんだが、
やり方知らんと手が出せんだろからC/C++内にアセンブリ書いて呼んでみたサンプル。
C++
1/* 2 Visual C++ 32bit(x86)コンパイラで試すべし 3 */ 4 5#include <iostream> 6#include <cstdint> 7 8uint32_t your_asm(uint32_t width) { 9 uint32_t result; 10 __asm { 11 mov eax, width 12 mov edx, 3 13 mul edx 14 mov result, eax 15 } 16 return result; 17} 18 19uint32_t my_asm(uint32_t width) { 20 uint32_t result; 21 __asm { 22 mov eax, width 23 mov ebx, 3 24/* mul eax, ebx マチガイ */ 25 mul ebx 26 mov ebx, eax 27 mov result, ebx 28 } 29 return result; 30} 31 32int main() { 33 using namespace std; 34 for ( uint32_t i = 0; i < 10; ++i ) { 35 cout << i << '\t' << your_asm(i) << '\t' << my_asm(i) << endl; 36 } 37} 38 39/* 実行結果 400 0 0 411 3 3 422 6 6 433 9 9 444 12 12 455 15 15 466 18 18 477 21 21 488 24 24 499 27 27 50*/
投稿2018/03/05 02:41
編集2018/03/05 02:46総合スコア16614
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/03/05 11:57
2018/03/05 11:59
2018/03/05 12:07
2018/03/05 12:19
2018/03/05 12:20
2018/03/05 12:25
2018/03/05 12:29
2018/03/05 12:32
2018/03/05 12:33
2018/03/05 12:33
2018/03/05 12:46
2018/03/06 06:55
0
前の方が説明されているので、詳しくはお話しませんが、このコードは、単に4で割った余りを計算してwMarginにセットしているだけと思いますので、単にwidthに対して4で割った余りをセットするだけでも良いと思います。
ちなみに、アセンブラではコーディングでは、mulやdivや条件ジャンプは遅いので、高速処理をする場合には、極力避けるべきです。私はコンパイラーのコード生成部を実装していたことがありますが,2倍や4倍する場合などでは、mulを使う場合には、シフト命令を使っていました。
その意味からいうと、このコーディングでは高速化するには変えたほうが良いと思います。4で割った余りなら、単に0x03でAndすれば、1つの命令で済みます。
投稿2018/03/03 13:28
総合スコア157
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/03/03 13:44
2018/03/03 20:53 編集
2018/03/04 14:47 編集
2018/03/08 14:28 編集
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/03/02 22:08
2018/03/02 22:24
2018/03/02 22:51
2018/03/02 23:00
2018/03/02 23:22
2018/03/03 23:27
2018/03/04 06:14
2018/03/04 06:26
2018/03/04 06:48
2018/03/04 07:08
2018/03/04 07:41
2018/03/04 07:47
2018/03/04 07:53
2018/03/04 08:08
2018/03/04 08:11
2018/03/04 08:16
2018/03/04 08:28
2018/03/04 08:39
2018/03/04 09:12
2018/03/04 09:23
2018/03/04 09:27
2018/03/04 09:32
2018/03/04 09:41
2018/03/04 09:41
2018/03/04 09:48
2018/03/04 09:53
2018/03/04 09:54
2018/03/04 09:57
2018/03/04 10:00
2018/03/04 10:21
2018/03/04 10:22
2018/03/04 10:22
2018/03/04 10:37
2018/03/04 11:35
2018/03/04 12:38
2018/03/04 13:10
2018/03/04 13:17
2018/03/04 14:10 編集
2018/03/04 13:30
2018/03/04 13:46 編集
2018/03/04 13:43
2018/03/04 13:45
2018/03/04 13:52
2018/03/04 14:01
2018/03/04 14:04
2018/03/04 14:31
2018/03/04 16:52 編集
2018/03/04 17:01
2018/03/04 17:02
2018/03/04 17:08 編集
2018/03/04 17:09
2018/03/04 17:11
2018/03/04 17:13 編集
2018/03/04 17:18
2018/03/04 17:20 編集
2018/03/04 17:54
2018/03/04 17:56
2018/03/04 18:04 編集
2018/03/04 18:10
2018/03/04 18:21
2018/03/04 18:24
2018/03/04 22:51 編集
2018/03/04 20:37
2018/03/04 22:39
2018/03/05 00:05
2018/03/05 00:07
2018/03/05 00:14 編集
2018/03/05 01:02
2018/03/05 01:10 編集
2018/03/05 02:47
2018/03/05 12:06
2018/03/05 12:10
2018/03/05 12:16 編集
2018/03/05 12:28
2018/03/05 12:31
2018/03/05 12:31
2018/03/05 12:33
2018/03/05 12:36
2018/03/05 12:36
2018/03/05 12:38 編集
2018/03/05 12:54
2018/03/05 12:55
2018/03/05 22:59
2018/03/05 23:44
2018/03/05 23:48
2018/03/05 23:52
2018/03/06 06:58
2018/03/06 07:53
2018/03/06 07:54
2018/03/06 08:00
2018/03/06 08:13
2018/03/06 08:57