创建一个x86汇编程序,将整数转换为16位二进制字符串0和1

时间:2016-11-25 19:20:48

标签: assembly x86 masm x86-16

正如问题所示,我必须编写一个MASM程序来将整数转换为二进制。我尝试了很多不同的方法,但它们都没有帮助我。我正在研究的最终代码如下。我在 Visual Studio 中调试代码时出现访问内存冲突错误。

如何解决错误的任何帮助,如果我在正确的轨道上,将不胜感激。第一个代码是我的C ++代码,它将char数组传递给.asm文件以转换为二进制文件。

#include <iostream>
using namespace std;
extern "C"
{
  int intToBin(char*);
}

int main()
{
  char str[17] = { NULL };
  for (int i = 0; i < 16; i++)
  {
    str[i] = '0';
  }

  cout << "Please enter an integer number :";
  cin >>str;
  intToBin(str);
  cout << " the equivilant binaryis: " << str << endl;
  return 0;
}

.asm文件如下:

.686
.model small
.code 

_intToBin PROC       ;name of fucntion
  start:    

    push ebp ; save base pointer
    mov ebp, esp ; establish stack frame

    mov eax, [ebp+8] ; stroing char value into eax
    mov ebx, [ebp+12]; adress offset of char array
    mov edx,32768 ;storin max 16bit binary in edx
    mov ecx,17  ; since its a 16 bit , we do the loop 17 times


  nextBite:
    test eax,edx        ;testing if eax is equal to edx
    jz storeZero        ;if it is 0 is to be moved into bl

    mov bl,'1'          ;if not 1 is moved into bl
    jmp storeAscBit     ;then jump to store ascii bit

  storeZero:
    mov bl,'0'          ;moving 0 into bl register

  storeAscBit:
    mov [di ],bl        ;moving bl (either 1 or 9) into [di]
    inc edx             ;increasing edx stack by 1 point to go to next bt
    shr edx,1           ;shfiting right 1 time so the 1 comes to second      
    loop nextBite       ; do the whole step again

  EndifReach:   
    pop ebp
_intToBin ENDP
 END

3 个答案:

答案 0 :(得分:2)

这是解释某些术语的高级答案。

第1部分 - 关于整数及其在计算机中的编码

整数值是整数值,在数学中它是纯粹抽象的东西。数字&#34; 5&#34;不是你在显示器上看到的(那个数字5(图形图像或&#34;字形&#34;)代表人类(和一些受过训练的动物)的基础10(十进制)格式的值5识别字形模式; 值5 本身纯粹是抽象)。

当你在C ++中使用int时,它并不是完全抽象的,它更难以连接到金属中。它是32位(在大多数平台上)的整数值。

但是,抽象的描述更接近真理,而不是将其想象为人类的十进制格式。

int a = 12345; // decimal number

此处a包含值12345,而不是格式。它不知道它是在源代码中输入的十进制字符串。

int a = 0x3039; // hexadecimal number

将编译为完全相同的机器代码,对于CPU来说,它仍然是(a == 12345)。最后:

int a = 0b0011000000111001; // binary number

也是一回事。它仍然是相同的12345值,只是用不同的格式编写。

最后一个二进制形式最接近用于存储值的CPU。它以32位(低/高电压电池/电线)存储,因此,如果您要测量特定电池/电线上的电压,您会看到&#34; 0&#34;前18位的电压电平,然后2位,&#34; 1&#34;电压电平,然后其余部分就像上面的二进制格式一样......两个最低有效位为&#34; 0&#34;和&#34; 1&#34;。

同样大多数的CPU电路都没有意识到特定位的特定值,这又是&#34;解释&#34;那个0/1,由代码完成。许多CPU算法如addsub从右到左工作&#34;&#34;在所有位上,不知道当前处理的位表示最终整数值,例如2 13 值(即第14个最低有效位)。

获取这些位,并计算这些位值的十进制/十六进制/二进制表示的字符串,当你给那些&#34; 1&#34; s它们的值。那么它就变成了文本 "12345"

如果您以不同的方式处理这些32位,例如LED显示面板的ON / OFF LED灯的表示,那么一旦您将其从CPU发送到显示器,LED显示面板将打开相应的LED灯,并不关心这些位在被视为12345时会形成 int值。

只有极少数CPU指令以某种方式工作,他们需要知道特定位的特定值。

第2部分 - 关于C / C ++函数的输入,输出和参数

您希望&#34;将十进制整数(输入)转换为二进制。&#34;

因此,让我们了解输入内容和输出内容。输入来自std::cin,因此用户将输入字符串。

但是如果你愿意的话:

int inputNum;
std::cin >> inputNum;

当用户无法输入正确的数字时,您将以已转换的整数值(32位,见上文)(或无效的std::cin状态结束,可能不是您处理此问题的任务。)

如果您的号码在int中,那么二进制转换已由clib完成,当时它将用户输入字符串编码为32位整数。

现在您可以使用C prototype创建asm函数:

void formatToBinary(uint16_t value, char result[17]);

这意味着你将给它uint16_t(无符号16位)整数值,并指向内存中17个保留字节的指针,在那里你将写入'0''1'个ASCII字符,以及通过另一个0值终止它(对于这个值的粗略描述,请按照我的问题中的评论中的第一个链接)。

如果你必须把输入作为字符串,即

char str[17];
std::cin > str;

然后,您将在str(在&#34; 12345&#34;输入之后)字节中输入值:'1'(十进制49),'2',{{1} },'3''4''5'。 (注意最后一个是零,非ASCII数字0 =值'0')。

首先需要将这些ASCII字节转换为整数值(在C ++ 48中可能有所帮助,或者转换/格式化的其他少数函数之一)。在ASM中,检查SO以查询问题&#34;如何输入整数&#34;。

一旦你将它转换为整数值,你可以按照上面描述的相同方式进行(此时它已经编码为16或32位,因此输出它的字符串表示应该很容易)

你可能仍会遇到一些棘手的问题,比如你不想输出前导零等......但如果你理解这是如何运作的话,所有这些都应该很容易。

在这种情况下,您的ASM函数原型可能只有atoi才能将字符串指针重用为输入和输出。

您的void convertToBinary(char*);看起来很奇怪,因为这意味着ASM会返回int intToBin(char*); ..但为什么?这个整数值,没有绑定到任何特定的格式,所以它同时是二进制/八进制/十进制/六进制。取决于你如何显示它。因此,您不需要它,您只需要表示二进制形式值的字符串,即int。并且你没有给你输入的号码(除非它从字符串中取出)。

从任务描述和你的技能水平我认为你可以在C ++中将输入转换为char *(即int)。

顺便说一句,如果你完全理解计算机中的值发生了什么,以及CPU指令如何对它们起作用,你通常可以通过许多不同的方式来实现某些结果。例如,Jose转换为二进制文件的方式很简单,就是大会新人如何编写它(他写的就像这样让你更容易理解):

std::cin >> int_variable;

它仍然有点脆弱,例如他初始化&#34; bin&#34;以这种方式,它包含32个空格,第33个值为零(C字符串的空终止符)。然后在代码中他确实修改了32个字节,所以第33个零仍然在那里工作。如果你要调整他的代码以跳过前导零,那么它会打破&#34;打破&#34;通过显示缓冲区的剩余部分,因为他没有明确设置空终止符。

这是如何在Assembly中编写性能代码,准确了解所发生的一切,而不是设置已设置/等的值的常用方法。在你学习的过程中,我会建议你在&#34;防守&#34;方式,而不是做一些浪费的事情,如果出现一些错误,它将作为安全网工作,所以我会在 mov eax, num // ◄■■ THE NUMBER. lea edi, bin // ◄■■ POINT TO VARIABLE "BIN". mov ecx, 32 // ◄■■ NUMBER IS 32 BITS. conversion: shl eax, 1 // ◄■■ GET LEFTMOST BIT. jc bit1 // ◄■■ IF EXTRACTED BIT == 1 mov [edi], '0' jmp skip bit1: mov [edi], '1' skip : inc edi // ◄■■ NEXT POSITION IN "BIN". loop conversion 后添加mov byte ptr [edi],0再次明确设置终结符。

但它实际上不是很快,因为它使用的是分支。 CPU不是那样的,解码新指令是一项代价高昂的任务,如果不确定,将执行哪些指令,它只是在一条路径前解码,如果猜错了,它会抛出它,并解码正确的路径,但这意味着执行中会暂停几个周期,直到新路径的第一条指令被完全解码并准备好执行。

因此,在编写性能编码时,您希望避免难以预测的分支(最终loop很容易为CPU预测,因为它始终循环,直到ecx为{{1}之后的最终退出})。在这种情况下,许多可能的方法之一可以是:

loop

正如您所看到的,现在除了循环之外没有分支,CPU分支预测将更加平滑地处理这一点,并且性能会更好。

与Cody讨论的双字变体(NASM语法,32b目标):

0

没有对其进行分析,只是验证它会返回正确的结果。

答案 1 :(得分:1)

接下来是使用“ atoi ”将字符串转换为数字,然后使用程序集将数字转换为二进制的示例:

#include "stdafx.h"
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{   char str[6]; // ◄■■ NUMBER IN STRING FORMAT.
    int num;    // ◄■■ NUMBER IN NUMERIC FORMAT.
    char bin[33] = "                                "; // ◄■■ BUFFER FOR ONES AND ZEROES.
    cout << "Enter a number: ";
    cin >> str;  // ◄■■ CAPTURE NUMBER AS STRING.
    num = atoi(str); // ◄■■ CONVERT STRING TO NUMBER.
    __asm { 
           mov eax, num   // ◄■■ THE NUMBER.
           lea edi, bin   // ◄■■ POINT TO VARIABLE "BIN".
           mov ecx, 32    // ◄■■ NUMBER IS 32 BITS.
        conversion:
            shl eax, 1     // ◄■■ GET LEFTMOST BIT.
            jc  bit1       // ◄■■ IF EXTRACTED BIT == 1
            mov [edi], '0'
            jmp skip
        bit1:
            mov [edi], '1'
        skip :
            inc edi   // ◄■■ NEXT POSITION IN "BIN".
            loop conversion
    }
    cout << bin;
    return 0;
}

答案 2 :(得分:1)

你可以在没有任何循环的情况下完成所有事情,使用SIMD并行执行所有操作。

十进制字符串 - &gt;整数部分很复杂,但有a complete atoi() implementation in asm使用SSE4.2 PCMPISTRI,然后是一些shuffle,然后PMADDWD将数字乘以它们的地方值。对于Haswell,IACA表示它有大约64个周期的延迟,因此短整数可能不会更快。

整数 - &gt; base 2字符串部分要简单得多,只能使用SSE2有效地完成。

这使用与Evgeny Kluev's answer on a question about doing the inverse of PMOVMSKB相同的技术,将位模式转换为0 / -1元素的向量:广播 - 重排输入字节,使每个向量元素包含您想要的位(加上邻居) 。并且只留下零或1,然后与全零向量进行比较。

此版本仅需要SSE2,因此它适用于可以运行64位操作系统的每个CPU,以及一些仅32位的CPU(如早期的Pentium4)。使用SSSE3可以更快(一个PSHUFB而不是三个shuffle来获得我们想要的低和高字节)。你可以做8位 - &gt; MMX一次8个字节。

我不会尝试将其从NASM转换为MASM语法。我实际测试了这个,它的确有效。我可能只是在尝试转换时引入语法错误。 x86 32位System V调用约定与影响此代码AFAIK的任何方式的32位Windows cdecl调用约定没有区别。

;;; Tested and works

;; nasm syntax, 32-bit System V (or Windows cdecl) calling convention:
;;;; char *numberToBin(uint16_t num, char buf[17]);
;; returns buf.

ALIGN 16
global numberToBin
numberToBin:
        movd    xmm0, [esp+4]       ; 32-bit load even though we only care about the low 16 bits.
        mov     eax, [esp+8]        ; buffer pointer

        ; to print left-to-right, we need the high bit to go in the first (low) byte
        punpcklbw xmm0, xmm0              ; llhh      (from low to high byte elements)
        pshuflw   xmm0, xmm0, 00000101b   ; hhhhllll
        punpckldq xmm0, xmm0              ; hhhhhhhhllllllll

        ; or with SSSE3:
        ; pshufb  xmm0, [shuf_broadcast_hi_lo]  ; SSSE3

        pand    xmm0, [bitmask]     ; each input bit is now isolated within the corresponding output byte
        ; compare it against zero
        pxor    xmm1,xmm1
        pcmpeqb xmm0, xmm1          ; -1 in elements that are 0,   0 in elements with any non-zero bit.

        paddb   xmm0, [ascii_ones]  ; '1' +  (-1 or 0) = '0' or 1'

        mov     byte [eax+16], 0    ; terminating zero
        movups  [eax], xmm0
        ret


section .rodata
ALIGN 16

;; only used for SSSE3
shuf_broadcast_hi_lo:
        db 1,1,1,1, 1,1,1,1     ; broadcast the second 8 bits to the first 8 bytes
        db 0,0,0,0, 0,0,0,0     ; broadcast the first 8 bits to the second 8 bytes

bitmask:  ; select the relevant bit within each byte, from high to low for printing
        db 1<<7,  1<<6, 1<<5, 1<<4
        db 1<<3,  1<<2, 1<<1, 1<<0
        db 1<<7,  1<<6, 1<<5, 1<<4
        db 1<<3,  1<<2, 1<<1, 1<<0

ascii_ones:
        times 16 db '1'

使用PSHUFLW在第二个shuffle步骤中进行反转在旧CPU(第一代Core2及更早版本)上进行反转速度更快,因为128b shuffle缓慢,因为只有低64位的快速移动速度很快。 (与使用PUNPCKLWD / PSHUFD相比)。请参阅Agner Fog的Optimizing Assembly guide以了解有关编写高效asm的更多信息,以及标记wiki中的其他链接。

Thanks to clang for spotting the possibility)。

如果你在循环中使用它,你可以将向量常量加载到向量寄存器中,而不是每次都重新加载它们。

从asm,你可以称之为

    sub     esp, 32

    push    esp           ; buf[] on the stack
    push    0xfba9        ; use a constant num for exmaple
    call    numberToBin
    add     esp, 8
    ;; esp is now pointing at the string

或者使用asm中的注释原型从C或C ++中调用它。