从汇编代码开始全面解析synchronized还原最真实的偏向锁

目录
  • 前言
  • 一.TemplateTable::monitorenter()
  • 二.lock_object():
  • 三.biased_locking_enter()
    • 1).参数
    • 2).概念
    • 3).源码

前言

我们都知道java之所以跨平台能力强,是因为java在编译期没有被编译成机器码,而是被编译成字节码。早期的jvm会将编译好的字节码翻译成机器码解释执行,我们在jvm的源码中还可以看到早期的解释器——bytecodeInterpreter.cpp(虽然已经不再使用)。对于字节码这种总数固定,解释逻辑固定的命令,现代jvm将其执行进行了优化,在jvm初始化的时候,直接将每个字节码指令将要执行的汇编代码加载到内存中,在运行时执行某段字节码时直接调用内存中对应的汇编代码即可,这样的解释器就时模板解释器——templateTable.cpp。而synchronized修饰代码块时,其编译成字节码后就是monitorenter和monitorexit(关于如何查看编译后的字节码可以查看笔者往期的博客)。

所以要想看现代jvm的synchronized实现还要从模板解释器(templateTable)的monitorenter方法看起(网上许多文章都是从bytecodeInterpreter开始分析,虽然大致逻辑一样,更有甚者将偏向锁撤销逻辑硬是理解成偏向锁加锁逻辑,非常混乱),本文笔者就从模板解释器汇编源码开始分析还原最真实的偏向锁实现,解释monitorenter字节码命令的方法开始,从汇编代码开始全面解析synchronized。

一.TemplateTable::monitorenter()

关于这个monitorenter()方法,主要包括在方法栈帧中获取lockRecord以及若lockRecord不够则扩容的逻辑,由于这部分代码是将字节码直接解释成机器码,所以以方法名的形式将机器码封装成了对应的汇编命令,我们碰到的汇编方法将其当成对应的汇编命令即可(值得注意的是里面又很多jmp,jcc,jccb等跳转指令,由于篇幅有限本文就不过多介绍,有兴趣的读者可以自行了解,本文就将其当成跳转指令),其他汇编命令也比较简单,这里就不过多介绍,读者如果碰到相关不熟悉的命令可以自行搜索下相关概念,好了话不多说我们直接看源码:

void TemplateTable::monitorenter() {
  transition(atos, vtos);
  // 检查对象是否为null,此时对象存在rax寄存器中
  __ null_check(rax);
  // rbp是堆栈寄存器,通常指向栈底
  // 栈帧中存在一个monitor数组用于保存锁相关信息,又叫lockRecord(后面都统称为lockRecord)
  // frame::interpreter_frame_monitor_block_top_offset和frame::interpreter_frame_initial_sp_offset
  // 表示monitor top 和monitor bot偏移量
  // Address(x, j)表示距离x地址j偏移量的地址
  // 所以这里声明的两个变量我们可以简单理解为栈帧中的monitor top 和monitor bot地址
  const Address monitor_block_top(
        rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize);
  const Address monitor_block_bot(
        rbp, frame::interpreter_frame_initial_sp_offset * wordSize);
  const int entry_size = frame::interpreter_frame_monitor_size() * wordSize;
  Label allocated;
  // 初始化c_rarg1寄存器中的值(这里本质是一个异或运算)
  __ xorl(c_rarg1, c_rarg1); // points to free slot or NULL
  // 这部分代码逻辑是循环从lockRecord数组中找到一个空的槽位,并将其放入c_rarg1寄存器中
  {
    Label entry, loop, exit;
    __ movptr(c_rarg3, monitor_block_top);
    __ lea(c_rarg2, monitor_block_bot);
    // 直接跳到entry标签位
    __ jmpb(entry);
    // 绑定loop标签开始循环
    __ bind(loop);
    // 检查当前LockRecord是否被使用
    __ cmpptr(Address(c_rarg3, BasicObjectLock::obj_offset_in_bytes()), (int32_t) NULL_WORD);
    // 没有被使用则将其放到c_rarg1
    __ cmov(Assembler::equal, c_rarg1, c_rarg3);
    // 检查和当前对象是否一样
    __ cmpptr(rax, Address(c_rarg3, BasicObjectLock::obj_offset_in_bytes()));
    // 如果一样则表示重入,跳出循环
    __ jccb(Assembler::equal, exit);
    // 否则则跳到下一个entry
    __ addptr(c_rarg3, entry_size);
    // 绑定entry标签
    __ bind(entry);
    // 比较c_rarg3与c_rarg2寄存器中的值,即是否相等
    __ cmpptr(c_rarg3, c_rarg2);
    // 若不等则跳到loop继续循环
    __ jcc(Assembler::notEqual, loop);
    __ bind(exit);
  }
  //检测一个空槽位是否被找到(如果是重入则不会跳转会去新申请)
  __ testptr(c_rarg1, c_rarg1);
  //找到则跳到 allocated标签
  __ jcc(Assembler::notZero, allocated);
  // 如果没有空的slot则申请一个,这里还包括了申请后调整位置的逻辑
  {
    Label entry, loop;
    // 将lockrecord底部的指针放到c_rarg1寄存器中
    __ movptr(c_rarg1, monitor_block_bot);
    // 计算并移动栈顶和栈底到新位置,均移动entry_size(rsp寄存器指向栈顶)
    __ subptr(rsp, entry_size);
    __ subptr(c_rarg1, entry_size);
    // 设置新的栈顶位置和栈底位置分别到c_rarg3寄存器和monitor_block_bot地址上
    __ mov(c_rarg3, rsp);
    __ movptr(monitor_block_bot, c_rarg1);
    // 跳到entry标签——为了先比较下然后开始循环
    // c_rarg1则是新的空slot
    __ jmp(entry);
    __ bind(loop);
    // 这两行是将老栈顶位置的值存到新栈顶位置
    __ movptr(c_rarg2, Address(c_rarg3, entry_size));
    __ movptr(Address(c_rarg3, 0), c_rarg2);
    // 推进到下一个位置
    __ addptr(c_rarg3, wordSize);
    __ bind(entry);
    __ cmpptr(c_rarg3, c_rarg1);
    __ jcc(Assembler::notEqual, loop);      

  }
  // 绑定allocated标签
  __ bind(allocated);
  __ increment(r13);
  // 保存对象到lockRecord中,locrRecord对象有两个属性分别是对象指针和锁
  // BasicObjectLock::obj_offset_in_bytes()也表示偏移量
  __ movptr(Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()), rax);
  // 加锁方法
  __ lock_object(c_rarg1);
  // 检查以确保该监视器在锁定后不会导致堆栈溢出
  __ save_bcp();
  __ generate_stack_overflow_check(0);
  // 调用下一个指令
  __ dispatch_next(vtos);
}

我们看到下一个方法是_lock_object()方法,这个方法我们等下在分析,在这之前笔者先介绍下我们源码中看到的lockRecord,其实时basicLock.cpp中的BasicObjectLock类:

//只有两个属性
class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
 private:
  //锁对象
  BasicLock _lock;
  //表示持有锁的对象
  oop       _obj;
}
//再来看看锁对象——只有一个属性
class BasicLock VALUE_OBJ_CLASS_SPEC {
 private:
  //markword一般保存的是持有锁对象的markword
  volatile markOop _displaced_header;
}

可以看到lockRecord是用于关联对象和锁的关系的,如果在当前方法中有加锁的对象,就会在解释栈帧中添加一个lockRecord用于记录相应的对象和锁的关系,不仅如此lockRecord还会隐式的锁重入的计数器,当发生重入时,就会为同一个对象创建多个lockRecord。从源码中我们也可以看到在解释的方法执行期间,lockRecord的数组会根据持有的锁数量增长或缩小。

二.lock_object():

接下来我们来一起看看lock_object()方法:

//在interp_masm_x86_64.cpp文件中
void InterpreterMacroAssembler::lock_object(Register lock_reg) {
  assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1");
  //判断是否强制使用重锁,默认是false
  if (UseHeavyMonitors) {
    call_VM(noreg,
            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
            lock_reg);
  } else {
    //定义完成标签
    Label done;
    const Register swap_reg = rax;
    const Register obj_reg = c_rarg3;
    //声明一些偏移量
    const int obj_offset = BasicObjectLock::obj_offset_in_bytes();
    const int lock_offset = BasicObjectLock::lock_offset_in_bytes ();
    const int mark_offset = lock_offset + BasicLock::displaced_header_offset_in_bytes();
    Label slow_case;
    // 传入的basicObjectLock中的对象地址存到obj_reg中,即c_rarg3寄存器中
    movptr(obj_reg, Address(lock_reg, obj_offset));
    //使用偏向锁
    if (UseBiasedLocking) {
      //偏向锁加锁方法
      biased_locking_enter(lock_reg, obj_reg, swap_reg, rscratch1, false, done, &slow_case);
    }
    //后面的方法是关于偏向锁撤销和升级的,不是本文重点,本文先略过
    movl(swap_reg, 1);
    orptr(swap_reg, Address(obj_reg, 0));
    movptr(Address(lock_reg, mark_offset), swap_reg);
    assert(lock_offset == 0,
           "displached header must be first word in BasicObjectLock");
    if (os::is_MP()) lock();
    cmpxchgptr(lock_reg, Address(obj_reg, 0));
    if (PrintBiasedLockingStatistics) {
      cond_inc32(Assembler::zero,
                 ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));
    }
    jcc(Assembler::zero, done);
    subptr(swap_reg, rsp);
    andptr(swap_reg, 7 - os::vm_page_size());
    movptr(Address(lock_reg, mark_offset), swap_reg);
    if (PrintBiasedLockingStatistics) {
      cond_inc32(Assembler::zero,
                 ExternalAddress((address) BiasedLocking::fast_path_entry_count_addr()));
    }
    jcc(Assembler::zero, done);
    bind(slow_case);
    call_VM(noreg,
            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
            lock_reg);
    bind(done);
  }
}

三.biased_locking_enter()

1).参数

lock_object()调用的biased_locking_enter()方法中才是真正偏向锁逻辑,我们这里介绍下传入的几个参数(便于我们后续分析):

lock_reg:lock_object方法传入的空的lockRecord,其内部已经保存了当前要加锁的对象

obj_reg:持有对象的寄存器,其内部保存了将要加锁的对象

swap_reg:目前是一个空的寄存器,会用于保存中间值

rscratch1: 临时寄存器,用于保存中间值

done:done标签方便直接跳到方法结束

&slow_case:slow_case标签方便直接跳到锁升级逻辑

比较重要的是前两个参数,分别保存我们要判断的lockRecord和对象,后面两个参数其实是方便我们直接跳转到对应逻辑的标签。

2).概念

这个方法中还会涉及到一些概念,网上也有一些介绍,笔者先简单介绍下,方便大家阅读:

markword:一般用二进制表示,对象头中的markword,主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode

这张图就表示markword的几个状态。

klassword:一个指向方法区中Class信息的指针一般用二进制表示,通过指针可以获取其相应的klass对象(即方法区中表示class信息的对象)

偏向模式:表示对象是否当前是偏向状态,即markword最后三位是否是101,这里需要注意的是不光普通对象具有偏向模式,klass对象也有偏向模式,具体可以在systemDictionary.cpp 的update_dictionary方法中可以看到,所有创建的klass锁状态起始是001,然后会被更新为101。创建普通对象时会将klass中的markword填充到oop对象中。Klass对象除了再刚开始创建时锁状态时001,再进行批量偏向锁撤销的时候也会恢复成001(这部分不是本文重点,具体细节就先不分析)。所以当一个对象是偏向模式时,其不一定是持有偏向锁的,因为对象刚创建出来其markword后三位即101,需要通过线程ID,epoch来判断其是否持有偏向锁。

3).源码

让我们继续看biased_locking_enter()方法:

//调用的是macroAssembler_x86.cpp中的方法
int MacroAssembler::biased_locking_enter(Register lock_reg,
                                         Register obj_reg,
                                         Register swap_reg,
                                         Register tmp_reg,
                                         bool swap_reg_contains_mark,
                                         Label& done,
                                         Label* slow_case,
                                         BiasedLockingCounters* counters) {
  ......
  bool need_tmp_reg = false;
  //noreg是一个宏,表示空的寄存器
  if (tmp_reg == noreg) {
    need_tmp_reg = true;
    tmp_reg = lock_reg;
  } else {
    assert_different_registers(lock_reg, obj_reg, swap_reg, tmp_reg);
  }
  //定义一些地址,分别是markword,klass和lockRecord中的锁对象地址
  Address mark_addr      (obj_reg, oopDesc::mark_offset_in_bytes());
  Address klass_addr     (obj_reg, oopDesc::klass_offset_in_bytes());
  Address saved_mark_addr(lock_reg, 0);
  // 偏向锁逻辑开始
  // 分支1:查看当前对象是否开启偏向模式
  Label cas_label;
  int null_check_offset = -1;
  //swap_reg_contains_mark传入的是false,表示swap_reg不包括markword地址
  if (!swap_reg_contains_mark) {
    null_check_offset = offset();
    //将对象的markword放入swap_reg
    movl(swap_reg, mark_addr);
  }
  if (need_tmp_reg) {
    push(tmp_reg);
  }
  //将对象的markword放入tmp_reg
  movl(tmp_reg, swap_reg);
  //取其锁标记位(与指令)
  //markOopDesc::biased_lock_mask_in_place=111 这里是取markword的后三位到tmp_reg寄存器中
  andl(tmp_reg, markOopDesc::biased_lock_mask_in_place);
  //判断是否有锁(比较指令)
  //markOopDesc::biased_lock_pattern=101
  cmpl(tmp_reg, markOopDesc::biased_lock_pattern);
  if (need_tmp_reg) {
    pop(tmp_reg);
  }
  //如果不相等则表示没有开启对象偏向模式(即已经是轻量级锁)则跳到cas_label标签到方法末尾
  jcc(Assembler::notEqual, cas_label);
  // 分支2:相等则表示对象markword后三位是101即现在对象是偏向锁模式(但不一定持有偏向锁)
  // 这部分的逻辑是将线程id和epoch信息做比对,判断是否已经持有偏向锁
  movl(saved_mark_addr, swap_reg);
  if (need_tmp_reg) {
    push(tmp_reg);
  }
  //获取线程id
  get_thread(tmp_reg);
  //对象的markword与线程id异或,若线程id部分一样则线程id部分会变成0
  xorl(swap_reg, tmp_reg);
  if (swap_reg_contains_mark) {
    null_check_offset = offset();
  }
  //将klass放入tmp_reg寄存器
  movl(tmp_reg, klass_addr);
  //与klass的markword异或,若两者同位部分一样则同位会变成0,这里是为了判断epoch和锁标志位是否与klass一样
  xorl(swap_reg, Address(tmp_reg, Klass::prototype_header_offset()));
  //设置分代年龄掩码即年龄为0
  andl(swap_reg, ~((int) markOopDesc::age_mask_in_place));
  if (need_tmp_reg) {
    pop(tmp_reg);
  }
  if (counters != NULL) {
    cond_inc32(Assembler::zero,
               ExternalAddress((address)counters->biased_lock_entry_count_addr()));
  }
  //前面已经处理过markword,将其关键信息已经存入swap_reg中,后面只使用swap_reg进行判断
  //如果swap等于0,则表明线程id是本线程id,且epoch和锁标志位都与klass中的一样,即已经偏向本线程,跳到加锁结束
  jcc(Assembler::equal, done);
  //定义撤销偏向锁标签
  Label try_revoke_bias;
  //定义重偏向锁标签
  Label try_rebias;
  //若不等则证明线程id,epoch和锁标志位有不一样的
  //分支3:先判断锁标志位,即判断类的偏向模式是否是关闭
  //test可以理解为与运算
  //因为之前已经判断过对象的是偏向模式,而klass与对象的锁标记位不等,则证明klass对象不是偏向模式
  //如果类偏向模式是关闭,表明正在进行批量撤销偏向锁的行为,即正在进行锁升级
  //所以需要cas替换修复对象的markword,修复成类的markword,跳到撤销标签
  testl(swap_reg, markOopDesc::biased_lock_mask_in_place);
  jcc(Assembler::notZero, try_revoke_bias);
  //分支4:再判断是否是epoch过期,过期则跳到重偏向标签
  testl(swap_reg, markOopDesc::epoch_mask_in_place);
  jcc(Assembler::notZero, try_rebias);
  //分支5:到这里只剩线程id并不是本线程,进行一次cas替换尝试加偏向锁
  //将对象的markword读到swap_reg中
  movl(swap_reg, saved_mark_addr);
  //进行与运算,获取对象markword的锁标志位和age,epoch用来构造一个新的带锁的markword
  andl(swap_reg,
       markOopDesc::biased_lock_mask_in_place | markOopDesc::age_mask_in_place | markOopDesc::epoch_mask_in_place);
  if (need_tmp_reg) {
    push(tmp_reg);
  }
  get_thread(tmp_reg);
  //将线程id也加入到构造的markword中
  orl(tmp_reg, swap_reg);
  //判断是否是多核cpu如果是则加锁——执行Lock命令
  if (os::is_MP()) {
    lock();
  }
  //cas替换对象的对象的markword为刚刚构造的持有锁信息的markword
  //Address(obj_reg, 0)表示对象的markword位置
  cmpxchgptr(tmp_reg, Address(obj_reg, 0));
  if (need_tmp_reg) {
    pop(tmp_reg);
  }
  if (counters != NULL) {
    cond_inc32(Assembler::zero,
               ExternalAddress((address)counters->anonymously_biased_lock_entry_count_addr()));
  }
  //cas不为0则证明偏向我们失败,意味着有另一个线程成功偏向,有竞争
  //则进入slow逻辑,跳转到slow_case标签,执行撤销升级逻辑
  if (slow_case != NULL) {
    jcc(Assembler::notZero, *slow_case);
  }
  //成功证明已经偏向成功,跳转到done标签
  jmp(done);
  //epoch过期,重新偏向标签
  bind(try_rebias);
  if (need_tmp_reg) {
    push(tmp_reg);
  }
  //获取当前线程ID
  get_thread(tmp_reg);
  movl(swap_reg, klass_addr);
  //或运算,以klass的markword为基础和线程id组合构成新的markword
  orl(tmp_reg, Address(swap_reg, Klass::prototype_header_offset()));
  movl(swap_reg, saved_mark_addr);
  if (os::is_MP()) {
    lock();
  }
  //将新构造的markword cas替换 对象的markword
  cmpxchgptr(tmp_reg, Address(obj_reg, 0));
  if (need_tmp_reg) {
    pop(tmp_reg);
  }
  if (counters != NULL) {
    cond_inc32(Assembler::zero,
               ExternalAddress((address)counters->rebiased_lock_entry_count_addr()));
  }
  //偏向失败则证明有另外的线程偏向成功,需要撤销偏向
  if (slow_case != NULL) {
    jcc(Assembler::notZero, *slow_case);
  }
  //跳到结束
  jmp(done);
  //撤销偏向,将对象markword重置为klass(类)的markword
  //这里只有判断类的markword不是偏向标记才会进入,所以会将对象的markword重置为非偏向标记
  bind(try_revoke_bias);
  movl(swap_reg, saved_mark_addr);
  if (need_tmp_reg) {
    push(tmp_reg);
  }
  //获取对象klass的markword
  movl(tmp_reg, klass_addr);
  movl(tmp_reg, Address(tmp_reg, Klass::prototype_header_offset()));
  if (os::is_MP()) {
    lock();
  }
  //用对象klass的markword cas替换对象的markword
  cmpxchgptr(tmp_reg, Address(obj_reg, 0));
  if (need_tmp_reg) {
    pop(tmp_reg);
  }
  //无论cas的结果成功与否,都证明有线程撤销成功,所以继续执行
  if (counters != NULL) {
    cond_inc32(Assembler::zero,
               ExternalAddress((address)counters->revoked_lock_entry_count_addr()));
  }
  bind(cas_label);
  return null_check_offset;
}

看完了源码我们可以这样理解偏向锁,添加偏向锁的过程即是在对象处于可偏向模式时,在对象的markword中cas替换对应的线程id标记位,即表示当前线程持有了对象的偏向锁。完整的偏向锁处理逻辑已经分析完了,这里面分支比较多,我们来画图帮助理解下:

从图中我们可以看到若对象持有偏向锁且锁不是偏向本线程,则会最少会进行一次cas替换,若cas替换失败则会进入偏向锁的撤销升级逻辑。因为偏向锁cas替换后会进入撤销升级的逻辑,所以从效率上看偏向锁更适合一个线程不断的获取锁的场景,而事实上偏向锁正是设计用于应对一个线程获取锁的场景。

当然synchronized的执行逻辑还没有结束,本篇博客我们只着重分析偏向锁相关逻辑。笔者后续还会继续分析synchronized的轻量级锁和重量级锁的逻辑,尽量还原最原汁原味的synchronized。

以上就是从汇编代码开始全面解析synchronized还原最真实的偏向锁的详细内容,更多关于汇编代码解析synchronized还原偏向锁的资料请关注我们其它相关文章!

(0)

相关推荐

  • 聊聊Java并发中的Synchronized

    1 引言 在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了,本文详细介绍了Java SE1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程. 2 术语定义 术语 英文 说明 CAS Compare and Swap 比较并设置.用于在硬件层面上提供原子性操作.在 Intel 处理器中,比较并交换通过指令cmpxch

  • 面试总结:秒杀设计、AQS 、synchronized相关问题

    1.面试官:如何设计一个秒杀系统?请你阐述流程? 这一面试题答案参考自三太子敖丙的文章:阿里面试官问我:如何设计秒杀系统?我给出接近满分的回答 秒杀系统要解决的几个问题? ① 高并发 秒杀的特点是时间极短. 瞬间用户量大.在秒杀活动持续时间内,Redis 服务器需要承受大量的用户请求,在大量请求条件下,缓存雪崩,缓存击穿,缓存穿透这些问题都是有可能发生的. 一旦缓存失效,或者缓存无效,每秒上万甚至十几万的QPS(每秒请求数)直接打到数据库,基本上都要把库打挂掉,而且你的服务不单单是做秒杀的还涉及

  • Java synchronized最细讲解

    目录 前言 Synchronization实现原理 先理解Java对象头与Monitor 1.对象头:锁的类型和状态和对象头的Mark Word息息相关: jdk6 之后做了改进,引入了偏向锁和轻量级锁: 1.无锁到偏向锁转化的过程 2.偏向锁升级轻量级 3.轻量级到重量级 总结 前言 线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据. 因此为了解决这个问题,我们可能需要这样一个方案,当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在

  • Java并发编程深入理解之Synchronized的使用及底层原理详解 上

    目录 一.线程安全问题 1.临界资源 2.线程安全问题 3.如何解决线程安全问题 二.synchronized使用介绍 三.synchronized实现原理 1.synchronized底层指令:monitorenter和monitorexit 2.Object Monitor(监视器锁)机制 一.线程安全问题 1.临界资源 多线程编程中,有可能会出现多个线程同时访问同一个共享.可变资源的情况,这个资源我们称之其为临界资源:这种资源可能是:对象.变量.文件等. 共享:资源可以由多个线程同时访问

  • 从汇编代码开始全面解析synchronized还原最真实的偏向锁

    目录 前言 一.TemplateTable::monitorenter() 二.lock_object(): 三.biased_locking_enter() 1).参数 2).概念 3).源码 前言 我们都知道java之所以跨平台能力强,是因为java在编译期没有被编译成机器码,而是被编译成字节码.早期的jvm会将编译好的字节码翻译成机器码解释执行,我们在jvm的源码中还可以看到早期的解释器——bytecodeInterpreter.cpp(虽然已经不再使用).对于字节码这种总数固定,解释逻辑

  • 解析四则表达式的编译过程及生成汇编代码

    1.前序这是编译原理的实验,自认为是上大学以来做过的最难的一个实验.实验用到的基础知识:C语言.数据结构.汇编(只需简单的了解).开发工具:VC 2.问题描述编译整数四则运算表达式,将整数四则运算表达式翻译为汇编语言代码.消除左递归后的文法:E→TE'E'→+TE' |εT→FT'T'→*FT' |εF→(E) | i消除左递归后的翻译模式:E ::=     T    {E'.i:=T.nptr}E'    {E.nptr:=E'.s}E'::=      + T  {E'1.i:=mknod

  • c语言中if语句是怎么变成汇编代码的详解

     1. 要编译的测试代码:  int a; int b = 3; int main(void) { if (3) a = 4; else b = 5; } 2. 词法分析 词法分析将c源代码解析成一个个的token. 关键的,将if两个字符解析成一个if token,后续语法分析的输入就从两个字符减少为1个token,减小了语法分析的难度. 3. 语法分析 if (equal(tok, "if")) { Node *node = new_node(ND_IF, tok); tok =

  • C语言中的数组和指针汇编代码分析实例

    今天看<程序员面试宝典>时偶然看到讲数组和指针的存取效率,闲着无聊,就自己写了段小代码,简单分析一下C语言背后的汇编,可能很多人只注重C语言,但在实际应用当中,当出现问题时,有时候还是通过分析汇编代码能够解决问题.本文只是为初学者,大牛可以飘过~ C源代码如下: 复制代码 代码如下: #include "stdafx.h" int main(int argc, char* argv[]) {        char a=1;        char c[] = "

  • HTTP状态代码及其定义解析 Ajax捕捉回调错误参考

    HTTP状态代码及其定义解析 Ajax捕捉回调错误参考 状态行包含HTTP版本.状态代码.与状态代码对应的简短说明信息.在大多数情况下,除了Content-Type之外的所有应答头都是可选的.但Content-Type是必需的,它描述的是后面文档的MIME类型.虽然大多数应答都包含一个文档,但也有一些不包含,例如对HEAD请求的应答永远不会附带文档.有许多状态代码实际上用来标识一次失败的请求,这些应答也不包含文档(或只包含一个简短的错误信息说明). 当用户试图通过 HTTP 访问一台正在运行 I

  • iOS汇编入门教程之在Xcode工程中嵌入汇编代码的方法

    简介 上一篇文章ARM64汇编基础中介绍了汇编在iOS开发中的应用以及ARM汇编基础知识,本文将介绍在C或Objective-C构成的工程中如何嵌入汇编代码. 注意 在调试ARM汇编时,Xcode的Build对象必须为真机,如果对象为模拟器则是x86汇编. 内联汇编 汇编与C间接通信 在函数中可以直接插入汇编代码来影响函数的运行逻辑,使用的语法为编译指令 __asm__ ,注意插入汇编有可能会被编译器忽略,因此需要加入 __volatile__ 修饰符保证汇编代码有效. 下面给出一个简单的例子,

  • springboot多文件上传代码实例及解析

    这篇文章主要介绍了springboot多文件上传代码实例及解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一说明 spingMVC支持文件上传,我们通过Apach 的 commons-fileupload 包的CommonsMultipartResolver 去实现了 spingMVC的MultipartResolver . 本文章的示例是个简单的多文件上传,根据不同的业务自行修改. 二pom.xlm <dependencies> <

  • Java基础异常处理代码及原理解析

    这篇文章主要介绍了java基础异常处理代码及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 异常的定义:中断了正常指令流的事件. try..catch..finally结构: class Test{ public static void main(String[] args){ System.out.println(1); try{ System.out.println(2); int i = 1 / 0; System.out.pri

  • 详解如何实现C++虚函数调用汇编代码

    虚函数(代码段地址)被存放在虚函数表中,调用虚函数的流程是这样子的:先获取虚函数表的首地址,然后根据目标虚函数在虚函数表的位置(offset偏移)取出虚函数表中的虚函数地址,最后去call这个虚函数(地址),就完成虚函数的调用.这个虚函数调用的流程在汇编代码中可以最直观的反映出来. 在排查软件异常或崩溃时,我们时常要借助汇编代码的上下文去辅助分析问题.读懂C++虚函数调用的汇编代码实现,对于搞懂汇编代码的上下文时很有好处的.今天我们就来看看虚函数调用的汇编代码实现. 比如如下的C++代码: //

  • PHP开发框架laravel代码提示示例解析

    public function getTable() { return $this->table ?? Str::snake(Str::pluralStudly(class_basename($this))); } class_basename 拿到实例对应的类名 pluralStudly 把类名转成复数 snake 把驼峰转化成下划线的格式 这里是默认根据类名加下划线加上复数建议不用改但是如果删除了 复数也可以拿到表名 public function getTable() { return $

随机推荐