二进制-了解计算机的源头

阿拉伯数字采取进位制法,高位在做,低位在右。十进制计数法,基于阿拉伯数字,十进制的数位全都是10^n的形式,这里的10称为十进制计数法的基数,这也就是十进制中“十”的由来。计算机中数据存放以二进制为主。二进制是使用2作为基数,它的位数也就是2^n的形式。

二进制与十进制之间转换

举例,二进制:110101

           十进制:1*2^5+1*2^4+0*2^3+1*2^2+0*2^1+1*2^0=53

由此可以推导出八进制、十六进制。

计算机为什么使用二进制?

这和计算机硬件有关,组成计算机系统的逻辑电路通常只有两个状态,即开关的接通和断开。断开用“0”表示,接通用“1”表示。因为只有这两种状态,即便系统在收到干扰是仍然可靠分辨出“0”和“1”,所有,再具体的系统实验中,二进制的数据表达具有抗干扰能力强,可靠性高的优点。

试想,用十进制或者更高的进制位涉及具有10种状态的电路,情况就显得很复杂了,判断状态时出错的几率大大提高了。

不仅在实际硬件电路上,而且在逻辑运算时,“真”“假”也只有2种状态,正好与二进制的“1”“0”对应。逻辑运算中的加法(“或”运算)、乘法(“与”运算)、否定(“非”运算)都可以通过“0”与“1”的加、乘、减来实现。

二进制的位操作

位操作:直接堆内存中的二进制进行操作。常见的二进制位操作包括向左移位和向右移位的移位操作,“或”、“与”、“异或”的逻辑操作。

向左移位

二进制110101向左移1位,就是在末尾添加一位0。

110101  ->  1101010

进行移位操作有可能会发生数字溢出的情况。所谓数字溢出,就是二进制数的位数超过了系统所指定的位数。现在一般主流系统都支持32位整型数字。如果移位操作发生了数字溢出,就需要将溢出的位数去除。

计算一下向左移位前后两个数的十进制数值,之前是53,向左移位后是106。由此可得,二进制左移一位,实际上就是十进制数字翻一倍。

向右移位

二进制110101向右移1位,就是去除末尾的那一位。

110101  ->  11010

计算一下向右移位前后两个数的十进制数值,之前是53,向左移位后是26,这正好是53除以2的整数商。由此可得,右移一位就是将数字除以2求得的整数商的操作。

在Java中代码实现移位操作

import java.math.BigInteger;
public class demo{
    public static int leftShift(int num,int m){
        return num << m;
    }
    public static int rightShift(int num,int m){
        return num >>> m;
    }
}

可见左移右移明显不一样,那为什么右移时不是 “>>”n呢?

这是因为java的二进制数值中最高一位是符号位,当符号位为0,表示该数值的整数,当符号位为1时,表示该数值为负数。

以32位Java为例,我们来看一下53、-53

这里-53用的补码形式

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

补码计算规则:

        正整数的原码、反码、补码完全一样,即符号位固定为0,数值位相同。

        负整数的符号位固定为1,由原码变为补码时,规则如下:

               1、原码符号位1不变,整数的每一位二进制数位求反,得到反码

               2、反码符号位1不变,反码数值位最低位加1,得到补码

负整数在进行右移时就会出现是否移动符号位的问题?

这就出现了两种右移。

  1. 逻辑右移:右移一位,左边补位
  2. 算术右移:符号位不动,其他右移,空出来的补1

位的“或”

二进制的“1”和“0”对应逻辑中的“真”和“假”,可以针对位进行逻辑操作。逻辑“或”:参与操作的位中只要有一个位是1,那最终结果就是1,即为真。

位的“与”

参与操作的位中必须全都是1,那最终结果为1,即为真。

位的“异或”

参与操作的位相同,那结果就是0,否则就是1.所谓“异或”中的“异”,也就是要参与操作的位不同,才能得到“真”的结果。

 

<think>嗯,用户现在问的是能不能用MSYS2终端来开发编程语言,还有他们想从更底层开始,甚至提到是否要敲二进制机器码。看来用户对之前的解释可能觉得不够深入,想要更基础的方法。我需要先确认MSYS2是否适合这个任务,然后考虑如何从源头开始,比如汇编或者机器码。 首先,MSYS2是一个在Windows上提供类似Unix环境的工具,包含GCC、GDB等工具链,完全可以用它来开发编译器或解释器。用户可能担心环境限制,所以需要明确MSYS2是可行的,并且可以安装必要的工具,比如NASM、GCC、Make等。 接下来,用户希望从“源头”开始,可能指的是不用级语言,而是从汇编或机器码直接构建。这时候需要解释不同的实现层次,比如自举(Bootstrapping)的概念,或者从汇编开始编写编译器。但完全从二进制机器码开始是不现实的,所以可能需要引导用户使用汇编作为起点,然后逐步构建更级的工具。 还要考虑用户的实际能力和目标。如果用户是教育目的,想了解计算机底层,那么从汇编开始是有意义的。但如果是想快速实现一个语言,可能更效的方式是用级语言写解释器。需要平衡理论可行性和实际操作性,给出不同层次的建议。 另外,用户提到“不够彻底的新”,可能希望完全从零开始,不使用现有工具。这时候需要解释工具链的发展过程,比如如何先用汇编写一个简单的编译器,再用这个编译器编译更复杂的版本,逐步自举。同时,也要指出完全从二进制开始需要极大的工作量,可能不切实际,但可以分阶段进行。 最后,需要给出具体的步骤示例,比如用MSYS2安装NASM,写一个简单的汇编程序,然后过渡到用汇编编写词法分析器,再逐步构建更级的部分。同时提供资源推荐,帮助用户深入学习相关概念,比如《计算机系统要素》和《编译器设计》等书籍。</think>完全可以使用MSYS2终端进行开发!这个环境足够构建完整的工具链。让我们从最底层的实现路径开始探索,彻底揭示编程语言从01到可执行的全过程: --- ### 一、基础工具链准备(MSYS2环境) 1. **安装必要组件** ```bash pacman -S nasm gcc make binutils ``` - NASM:x86汇编编译器 - GCC:交叉编译工具链 - OBJDUMP:反汇编查看工具 2. **验证汇编开发能力** ```nasm ; hello.asm section .data msg db 'Hello from binary!',0xA len equ $-msg section .text global _start _start: mov eax, 4 ; sys_write mov ebx, 1 ; stdout mov ecx, msg mov edx, len int 0x80 mov eax, 1 ; sys_exit xor ebx, ebx int 0x80 ``` 编译命令: ```bash nasm -f elf32 hello.asm && ld -m elf_i386 -o hello hello.o ``` --- ### 二、机器码级语言实现(手动编码) #### 1. 直接编写二进制指令 使用hexdump创建可执行文件: ```bash # 生成退出状态码42的程序 echo -ne '\xB8\x2A\x00\x00\x00\xC3' > exit42.bin chmod +x exit42.bin ./exit42.bin; echo $? # 输出42 ``` 对应机器码: ``` MOV EAX, 42 -> B8 2A 00 00 00 RET -> C3 ``` #### 2. 手动构造ELF文件头 ```python # 用Python生成最小可执行文件 header = bytes.fromhex(''' 7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00 02 00 03 00 01 00 00 00 54 80 04 08 34 00 00 00 00 00 00 00 00 00 00 00 34 00 20 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 80 04 08 00 80 04 08 8D 00 00 00 8D 00 00 00 05 00 00 00 00 10 00 00''') code = bytes.fromhex('B82A000000C3') # 同前例的机器码 with open('minimal_elf', 'wb') as f: f.write(header + code.ljust(0x8D, b'\x00')) ``` --- ### 三、自举实现路径(Bootstrapping) #### 阶段1:汇编实现词法分析器 ```nasm ; lexer.asm section .data input db 'var x=42',0 token_types db 'IDENTIFIER',0, 'OPERATOR',0, 'NUMBER',0 section .text global _start _start: ; 实现简单的分词逻辑 mov esi, input mov edi, buffer next_char: lodsb cmp al, 'a' jge maybe_identifier ; 其他字符处理... ``` #### 阶段2:用阶段1的编译器编译升级版 ```c // 用汇编实现的C子集编译器(第二阶段) void compile_expression(char* expr) { // 此时编译器本身已可用汇编实现的基础功能 } ``` --- ### 四、现代语言自举示例(以Pascal为例) 1. **Step 0:用汇编实现Pascal-P编译器** ```nasm ; 编译Pascal子集的汇编编译器 parse_procedure: ; 处理过程声明 cmp dword [token], TOKEN_PROCEDURE jne .not_proc call parse_identifier call emit_prologue ``` 2. **Step 1:用Pascal-P编写Pascal编译器** ```pascal { 用Pascal自身编写的编译器 } procedure Compile(stmt: Statement); begin case stmt.kind of Assignment: GenCode(MOV, stmt.target, stmt.value); end; end; ``` 3. **Step N:最终用级Pascal编译自身** ```pascal { 完全自举的编译器 } procedure TParser.ParseVarDecl; var ident: string; begin Expect(TK_VAR); ident := ParseIdent; Expect(TK_EQUAL); CompileExpr; end; ``` --- ### 五、实现路线图建议 1. **硬件层理解** - 掌握x86指令编码格式(ModR/M、SIB字节) - 理解ELF文件结构(使用`readelf -a`分析) 2. **构建工具链里程碑** ``` 手工汇编 → 宏汇编器 → 单趟编译器 → 自举编译器 → 优化编译器 ``` 3. **现代实践技巧** - 使用QEMU进行跨架构测试:`qemu-i386 ./hello` - 借助LLVM后端生成机器码(避免手动优化) - 使用Flex/Bison生成基础解析代码 --- ### 六、关键调试技术 1. **GDB机器级调试** ```bash gdb -q ./hello (gdb) layout asm (gdb) starti (gdb) si # 单步执行汇编指令 ``` 2. **观察寄存器变化** ```bash watch $eax # 监控寄存器值变化 ``` 3. **反编译验证** ```bash objdump -d -M intel hello.o ``` --- 这种从二进制级语言的实现路径,能让我们深度理解计算机系统本质。建议从制作一个能编译四则运算计算器的编译器开始,逐步添加功能。是否需要我提供具体的汇编实现示例?或是想了解某个阶段的实现细节?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值