Linux | 调试器GDB的详细教程【纯命令行调试】

news2025/1/18 20:12:20

在这里插入图片描述
在这里插入图片描述

文章目录

  • 一、前言
  • 二、调试版本与发布版本
    • 1、见见gdb
    • 2、程序员与测试人员
    • 3、为什么Release不能调试但DeBug可以调试❓
  • 三、使用gdb调试代码
    • 1、指令集汇总
    • 2、命令演示
      • ⌨ 行号显示
      • ⌨ 断点设置
      • ⌨ 查看断点信息
      • ⌨ 删除断点
      • ⌨ 开启 / 禁用断点
      • ⌨ 运行 / 调试
      • ⌨ 逐过程和逐语句
      • ⌨ 打印 / 追踪变量
      • ⌨ 查看函数调用
      • ⌨ 修改变量的值
      • 排查问题三剑客🗡
        • ⌨ 指定行号跳转
        • ⌨ 强制执行函数
        • ⌨ 跳转到下一断点
  • 四、实战演练:Swap Two Numbers
  • 五、总结与提炼

一、前言

  • 学习了【vim】知道了如何编辑一个代码文本
  • 学习了【gcc】知道了如何编译一个代码文本
  • 学习了【make/Makefile】知道了如何自动化构建一个代码文本

但是如何对一段代码去进行调试呢,此时就要使用到Linux下的调试器gdb了。对于这个调试器来说,不像是VS中那样的图形化界面形式,而是采用纯命令行的形式进行调试。可能我的讲解会比较晦涩难懂,在学习的过程中主要是会一些gdb下基本的操作即可

二、调试版本与发布版本

1、见见gdb

下面是本次调试所要使用到的代码

  1 #include <stdio.h>
  2 
  3 int AddToTop(int top)
  4 {
  5     printf("Enter AddToTop\n");
  6 
  7     int count = 0;
  8     for(int i = 1;i <= top; ++i)
  9     {
 10        count += i;
 11     }
 12 
 13     printf("Quit AddToTop\n");                                                                         
 14     return count;
 15 }
 16 
 17 int main(void)
 18 {
 19     int top = 100;
 20     int ret = AddToTop(top);
 21 
 22     printf("ret = %d\n", ret);
 23     return 0;
 24 }

下面是Makefile中的内容,用于自动化编译

  1 mytest:test.c
  2     gcc -o mytest test.c -std=c99                                                                      
  3 .PHONY:clean
  4 clean:
  5     rm -rf mytest

注:-std=c99表示以c99的标准来编译代码


  • 如果要进入gdb开始调试,那直接gdb + 可执行程序即可
  • 不过进去之后发现似乎有一些奇怪的内容,【no debugging symbols found】,翻译过来就是没有调试信息。那这是为何呢?是gdb出问题了吗?

在这里插入图片描述

  • 先不要着急,如果有经常调试的通过就可以知道只有在【DeBug】的环境下才会有我们想要的调试信息,所以可以初步推断这可能不是一个【DeBug】版本的可执行程序
  • 先使用q(quit)退出gdb

让我们先看下去,了解一下其他的知识再来解决这个问题

2、程序员与测试人员

接下去我们就来说说有关【DeBug】和【Release】版本的不同之处

📚【Debug】—— 调试版本
📚【Release】—— 发布版本

  • 在使用VS的时候我们可以直接使用鼠标来进行操作,当前程序以DeBug或者是Release的形式进行运行,那么运行出来的可执行程序版本也是不同的,我们程序员在编写代码后运行一般是使用【DeBug】环境进行运行。因为在企业里写软件项目,将代码写完后程序员自己要做简单的测试,保证代码没有问题
  • 当程序员自己测试完没有问题之后,就会将这个可执行程序给到测试人员进行测试,而且会给出自己的单元测试报告。对于测试人员来说所处的模式是【Release】,也就是将来客户要使用的这款软件的发布版本
  • 当测试在测的过程中,一定会发现一些问题。此时测试人员就会把报告再打回研发部。研发部做修改重新生成Release版本的可行性程序给到测试人员继续测试
  • 最后只有当测试通过了,再将生成的【单元测试报告】与产品经理进行核对之后没有问题,那这个软件才可以真正地面向市场👉

3、为什么Release不能调试但DeBug可以调试❓

其实对于我们刚才直接make自动化生成的可执行程序是通过gcc直接编译产生得到的,它是一个【Release】版本的可执行程序,因此无法进行调试

  • 若是我们想要使用gcc/g++去生成一个可执行程序时,默认是【Release】版本的,而不是【DeBug】
  • 但若是我们想要去生成一个【DeBug】版本的可执行程序也是可以的,只需要修改一下我们的Makefile即可,给gcc后面带上一个-g的命令选项,此时再去make一下的话生成的就是【DeBug】版本的了

  • 为了之前的【Release】版本不被覆盖,我们将其重命名一下为mytest-release
  • 在生成【DeBug】版本后一样对其进行一个重命名为mytest-debug

在这里插入图片描述

  • 通过观察上图中两个可执行文件的大小便可以发现虽然它们都是可执行程序,但是容量大小却不一样,这是为什么呢❓

  • 因为以Release版本发布的软件是给客户的,客户是不需要调试信息

  • 往可执行程序里添加很多的调试信息意味着软件的体积会变大

    • 一方面,用户下载需要时间了
    • 另一方面,用户下载好之后将软件启动、运行都需要更多的时间,体验不好。一般能不加就不加
  • 但是对于DeBug来说会自动加调试信息,容量体积比Release大

但是就这么说说太抽象了,我们得看看这个可执行文件里的调试信息究竟是怎样的💻

readelf -S mytest-debug
  • 在C生万物 | 程序环境和预处理中,我有说到过对于【可执行文件】它是一个二进制文件,若是查看它的源码就可以发现里面都是一堆乱码

在这里插入图片描述

当我们面对一堆二进制乱码措手不及的时候,给大家提到过一个东西叫做readelf,其实它是Linux中的一个指令,可以用来读取【elf】格式的文件

  • 加上-S的命令选项以符号表的形式来进行读取这个文件,就以一个列表的形式展现出了这个带调试信息的可执行程序
  • 下面展现几个比较常见的。例如这里的
    • .rodata就是read only data即只读全局数据区
    • .data就是已初始化全局数据区
    • .bss就是未初始化全局数据区

在这里插入图片描述

  • 不过这些呢是这个可执行文件中的所有内容,若是我们只是先要查看一些debug的调试信息,就要对这些东西进行一个筛选才行
  • 此时就可以使用到grep命令来进行一个筛选。便可以查看到所有的debug调试信息了

在这里插入图片描述

  • 上面是查看【DeBug】版本下的调试信息,在【Release】版本有没有呢
  • 我们也是读取并搜寻一下这个文件便可以发现对于【Release】版本来说是不存在调试信息的,所以什么都没有被打印出来

在这里插入图片描述
【总结一下】

  • 程序的发布方式有两种,debug模式和release模式
  • Linux gcc/g++出来的二进制程序,默认是release模式
  • 要使用gdb调试,必须在源代码生成二进制程序的时候, 加上-g选项

好,说到这里,对于调试相关背景就全部讲完了,接下去我们正式进入【gdb】的学习⌨️

三、使用gdb调试代码

1、指令集汇总

因为这个调试器是在Linux环境下的,是纯命令行模式,所以会有很多的指令,做好心里准备😢

注:()括号里面是该指令的全称

  • l(list) 行号/函数名 —— 显示对应的code,每次10行

  • r(run) —— F5【无断点直接运行、有断点从第一个断点处开始运行】

  • b(breakpoint) + 行号 —— 在那一行打断点

  • b 源文件:函数名 —— 在该函数的第一行打上断点

  • b 源文件:行号 —— 在该源文件中的这行加上一个断点吧

  • info b —— 查看断点的信息

    breakpoint already hit 1 time【此断点被命中一次】

  • d(delete) + 当前要删除断点的编号 —— 删除一个断点【不可以d + 行号】

    • 若当前没有跳出过gdb,则断点的编号会持续累加
  • d + breakpoints —— 删除所有的断点

  • disable b(breakpoints) —— 使所有断点无效【默认缺省】

  • enable b(breakpoints) —— 使所有断点有效【默认缺省】

  • disable b(breakpoint) + 编号 —— 使一个断点无效【禁用断点】

  • enable b(breakpoint) + 编号 —— 使一个断点有效【开启断点】

    • 相当于VS中的空断点
  • enable breakpount —— 使一个断点有效【开启断电】

  • n(next) —— 逐过程【相当于F10,为了查找是哪个函数出错了】

  • s(step) —— 逐语句【相当于F11,】

  • bt —— 看到底层函数调用的过程【函数压栈】

  • set var —— 修改变量的值

  • p(print) 变量名 —— 打印变量值

  • display —— 跟踪查看一个变量,每次停下来都显示它的值【变量/结构体…】

  • undisplay + 变量名编号 —— 取消对先前设置的那些变量的跟踪

排查问题三剑客🗡

  • until + 行号 —— 进行指定位置跳转,执行完区间代码
  • finish —— 在一个函数内部,执行到当前函数返回,然后停下来等待命令
  • c(continue) —— 从一个断点处,直接运行至下一个断点处【VS下不断按F5】

2、命令演示

看了上面的这些命令后,相信你一定回到了刚开始学习Linux指令的时候那种恐惧感,不过没关系,我会一一地演示这些指令,让你在看完本文后有一个基本的调试能力💪

  • 首先我们进入到gdb,然后它会等待我们输入指令

在这里插入图片描述

⌨ 行号显示

l(list) 行号/函数名 —— 显示对应的code,每次10行

  • 首先若是直接【L】的话便会随机显示出该源文件中的随机10行内容,这不是我们想要的

在这里插入图片描述

  • 若是【L 0】或者是【L 1】的话那就是从第一行开始往下列10行的内容
    • 注意这里的L是小写,而且与数字之间要有一个空格

在这里插入图片描述

  • 接下去若是想要看到我们所写的全部代码,只需要多Enter几次就可以了,gdb会自动记忆你上次敲入的指令

在这里插入图片描述

⌨ 断点设置

b + 行号 —— 在那一行打断点

在这里插入图片描述
b 源文件:函数名 —— 在该函数的第一行打上断点

在这里插入图片描述

b 源文件:行号 —— 在该源文件中的这行加上一个断点

在这里插入图片描述

⌨ 查看断点信息

info b —— 查看断点的信息

  • 若是直接执行【info】的话,出来的就是所有的调试信息

在这里插入图片描述

  • 但若是我们只想查看一下所打的断点的信息,那就在后面加个b/breakpoint

在这里插入图片描述
来简要介绍一下断点的一些字段信息

  • Num —— 编号
  • Type —— 类型
  • Disp —— 状态
  • Enb —— 是否可用
  • Address —— 地址
  • What —— 在此文件的哪个函数的第几行

在这里插入图片描述

  • 最后的话就是每个断点信息的下面这块breakpoint already hit 1 time即此断点被命中1次

⌨ 删除断点

d + 当前要删除断点的编号 —— 删除一个断点【不可以d + 行号】

在这里插入图片描述
d + breakpoints —— 删除所有的断点

在这里插入图片描述

  • 此时若继续将这个20行的断点打上时,就可以发现其编号为【4】,而并不是从1开始,这是因为我们没有退出过gdb,所以会持续上一次的编号继续往下

在这里插入图片描述

⌨ 开启 / 禁用断点

disable b(breakpoints) —— 使所有断点无效【默认缺省】

在这里插入图片描述
enable b(breakpoints) —— 使所有断点有效【默认缺省】

在这里插入图片描述
disable b(breakpoint) + 编号 —— 使一个断点无效【禁用断点】

在这里插入图片描述
enable b(breakpoint) + 编号 —— 使一个断点有效【开启断点】

在这里插入图片描述

其实对于禁用断点和启用断点,VS中也是有的,它叫做【空断点】。我们一起来看看

在这里插入图片描述
在这里插入图片描述

⌨ 运行 / 调试

r(run) —— F5【无断点直接运行、有断点从第一个断点处开始运行】

  • 首先若是将断点删除掉,使用【r】指令运行的话就会直接运行到程序结束

在这里插入图片描述

  • 再加上断点去运行的话就会在打的断点处停下来

在这里插入图片描述

⌨ 逐过程和逐语句

n(next) —— 逐过程【相当于F10,为了查找是哪个函数出错了】

  • 可以看到,我从第一个断点处也就是20行的位置开始执行,按下【n】之后因为在其后即22行有一个断点,此时就会直接运行到断点处

在这里插入图片描述
s(step) —— 逐语句【相当于F11,一次走一条代码,可进入函数,同样的库函数也会进入】

  • 此时我们按下【s】,也就相当于是【step】,让程序一步一步地走,继而进入了AddToTop这个函数,若是你在printf()语句要执行时按下【s】的话gdb就会进入printf()库函数内部去执行,这里就不展示了

在这里插入图片描述

  • 接下去我们可以就继续【n】,然后进行逐过程调试,来到for循环中,那么逐过程也就是变量i的累加和计数器count的累加,所以会反复执行(通过图中最左侧可以看出是第8行和第10行在反复执行
  • 可以看到后面我没有再按【n】了,但是依旧会执行上面的步骤,这点上面也有提到过,因为gdb会自动化记忆你上一次执行过的命令,所以若是不想再敲了,直接Enter就可以了

在这里插入图片描述

⌨ 打印 / 追踪变量

p(print) 变量名 —— 打印变量值

  • 都执行了那么多次了,不知道【i】和【count】发生了怎样的变化,将它们打印出来看看吧💻

在这里插入图片描述

  • 通过继续执行【n】,然后再去打印就可以发现i的值和count的值发生了变化

在这里插入图片描述

但是你不觉得这样每次去打印会显得很繁琐吗,那一定会的,所以我们有更好的办法💡

display —— 跟踪查看一个变量,每次停下来都显示它的值【变量/结构体…】

在这里插入图片描述

  • 我们也可以去追踪一下这两个变量的地址,不过可以看到对于地址来说是不会发生改变的

在这里插入图片描述
undisplay + 变量名编号 —— 取消对先前设置的那些变量的跟踪

  • 但是呢,每次都追踪打印这么多内容又太多了,我想把它们取消了可以吗?答:当然是可以的
  • 既然有display,那就有undisplay

在这里插入图片描述

⌨ 查看函数调用

bt —— 看到底层函数调用的过程【函数压栈】

  • 通过仔细观察刚才追踪的4个变量最左侧的编号,就可以看到它们的排列的顺序是倒着的。因为变量i和变量count是我们先追踪的,它们的地址是我们后追踪的,所以可以看出这很像是一个压栈的过程

在这里插入图片描述

  • 其实不仅是对于它们,AddToTop函数和main函数也呈现这样的关系。此时我们就可以通过【bt】这个指令来查看函数压栈的过程,此时便可以看到因为

在这里插入图片描述

⌨ 修改变量的值

set var —— 修改变量的值

  • 对于这个修改变量的值,很像是在VS里调试之前设置的那种条件断点,可以使调试开始后直接运行到此断点处。不过对于【set var】而言是在调试过程中进行设置

在这里插入图片描述

我们也可以到VS中来看一下条件断点是如何设置的

在右击选择【条件】后,就可以输入你本次执行代码想要从哪个条件开始,然后按下Enter,启动调试之后就会从你设置的条件处开始执行

在这里插入图片描述

排查问题三剑客🗡

掌握了上面的这些,你就可以在Linux下调一些简单的代码了,不过想做到高效地进行调试,就需要学习一下【三剑客】

⌨ 指定行号跳转

until + 行号 —— 进行指定位置跳转,执行完区间代码

  • 可以看到,当前在for循环内容执行累加的逻辑,但若是我们一直这么执行下去,就没有时间排错了,除了上面的哪一种【set var】之外,还有一种方法其实起到直接结束当前循环的作用,那就是进行指定行号跳转
  • 通过观察下图可以看到,当我们运行了until 13之后,程序直接就给出了我们最终的结果count,而且即将要执行最后的打印语句,说明我们跳转成功了

在这里插入图片描述

  • 不过呢,在我使用这个【until】的时候,遇到了一个bug,此时我在循环内部,也就是第10行的位置打了一个断点,用过VS调试器的都知道一直按F5就可以立即执行下一次循环。
  • 我在上面是没有打上断点的,所以使用【until】的时候也没有出现问题,但是在下面我在循环内部设置了一个断点,此时再去使用【until 13】进行跳转的时候就出现问题了,并没有马上跳转到指定的行号,而是接着执行循环中的内容

在这里插入图片描述

⌨ 强制执行函数

finish —— 在一个函数内部,执行到当前函数返回,然后停下来等待命令

  • 有时候我们会有这样的需求,在初步排查的时候推断可能是某个函数内部的逻辑出了问题,但是呢又不想一步步地进到函数内部进行调试,在VS中其实很简单,只需要在函数下方设个断点,然后F5直接运行到断点处即可
  • 但是在Linux下的gdb中,我们可以使用【finsh】指令来直接使一个函数执行完毕。从下图我们可以看到,首先【s】进到函数内部,接下去我直接使用finish,可以看到它直接回到了调用函数的位置,returned了一个返回值

在这里插入图片描述

  • 然后可以看到,在获取到返回值后,也就直接进行了printf打印

在这里插入图片描述

⌨ 跳转到下一断点

c(continue) —— 从一个断点处,直接运行至下一个断点处【VS下不断按F5】

  • 这点也是我刚才在上面有提到过的,在VS中,我们要直接跳转到下一个断点处只修要按下F5即可,那在gdb中该如何操作呢,你需要敲个【c】就可以了
  • 从下图我们可以看出,对于这个指令的用处可谓是非常大,当我处于第一个断点也就是20行的时候,直接敲下【c】,就可以运行到第二个断点处也就是第10行。之后若反复敲【c】,因为这是一个单语句的循环,所以循环的下一次还是会执行到此处。上面的这两个功能就和我们在VS中用的F5是一个道理

在这里插入图片描述

四、实战演练:Swap Two Numbers

📖纸上得来终觉浅,绝知此事要躬行🔨
————————————

以下是本次调试所要使用到的代码,相信你一定非常熟悉了,也就是使用指针交换两数,如果想看细节讲解可以看看我的函数章节

  1 #include <stdio.h>
  2 
  3 void swap(int* x, int* y)
  4 {
  5     int t = *x;
  6     *x = *y;
  7     *y = t;                                                                                            
  8 }
  9 int main(void)
 10 {
 11     int a = 10;
 12     int b = 20;
 13 
 14     printf("a = %d, b = %d\n", a, b);
 15 
 16     swap(&a, &b);
 17 
 18     printf("a = %d, b = %d\n", a, b);
 19     return 0;
 20 }

  • 首先我们在程序第16行设置上一个断点,然后【r】从第15行开始运行

在这里插入图片描述

  • 然后我们使用【s】进入到swap函数中,因为我首先不想调试,想先立马看看运行结果,但是此时又已经进入调试了,那么我们就可以使用到【finish】来立马执行完这个函数,然后观察一下结果
  • 可以看到,最后打印出结果的时候a和b的值确实发生了交换

在这里插入图片描述

  • 既然清楚了二者会进行一个交换,接下去我们就逐语句【n】进行一个单步追踪吧
  • 因为提前看了执行结果,所以我们要重新开始调试,按下【r】即可,它会询问你是否需要重新开始调试,选择y之后就可以重新从16行开始进行调试

在这里插入图片描述

  • 首先通过【display】记录一下两个变量的值和地址

在这里插入图片描述

  • 接着按【s】进入到swap函数里,追踪一下指针x和指针y的内容,也就是它们所存放的地址,就可以看到,函数内部已经接受到了这两个变量的地址

在这里插入图片描述

  • 但是我们后面不想再查看它们了,只需要看值是否被修改,此时就可以取消对它们的跟踪【undisplay】

在这里插入图片描述

  • 然后对我们要观察的值变化继续做一个追踪
  • 并且在执行完第一个语句t = *x时,临时变量t中已经存放了变量a的值,也就是指针所指向的那块空间中的值

在这里插入图片描述

  • 接下去执行*x = *y,此时*x中的值就发生了变化,因为指针x可以直接找到变量a的地址,所以可以对其中的内容做修改,就变为了20

在这里插入图片描述

  • 接下去执行*y = t,同理,指针y可以直接找到变量b的地址,所以可以对其中的内容做修改,将原本保存在临时变量t中的10赋值给到*y,也就修改了其中的内容

在这里插入图片描述

  • 再按【n】的话这个swap函数就执行结束了,回到了main函数,就可以清楚地看到函数内部的修改带动了函数外部值的变化,真正地通过【传址调用】交换了两个数

在这里插入图片描述

敲黑板:重点来了

  • 但是我不仅想让你看到这些,更加重要的一点不知你有没有发现,刚才我们在main函数中追踪的变量a和变量b的值与地址,到了swap()中就不见了,这是为什么呢?
  • 其实就是因为两个函数的函数栈帧不同,变量a和变量b位于main函数的函数栈帧中;而指针变量x和指针变量y位于swap函数的函数栈帧中,是互不相干的,临时变量除了函数栈帧就会被销毁【如果听不懂这些请看反汇编深挖【函数栈帧】的创建和销毁】
  • 但是因为在swap()内部接受到了外部两个变量的地址,所以在函数内部就可以通过这两个指针找到这两个变量的地址,从而对这两个空间中的内容做一个修改的操作

通过以上的实验演练,你是否对调试器GDB的使用更上一层楼🐧

五、总结与提炼

最后来总结一下本文所学习的内容📖

  • 在本文的开始,我们在初见GDB的时候发现了【no debugging symbols】的报错信息,于是便谈到了一个可执行程序的【DeBug】版本和【Release】版本,知道了对于前者来说是我们程序员使用的环境,是存在调试信息的;对于后者来说是测试人员所处的环境,是不存在调试信息的。而对于gcc/g++而言默认生成的可执行程序就是【Release】版本的,因此我们要加上一个-g命令选项使其在make之后生成一个【DeBug】版本的可执行程序,这样就可以进行调试了
  • 接着我们正式进入到了调试器GDB的使用,介绍了很多相关的指令,这些都是我整理出来的常见指令,其实对于GDB来说还有很多指令,但是真正常用的也就这些,只要你认真地看下来,将它们都使用熟练了,相信你的调试功底会大幅度提升,对你在VS中的调试也是有所帮助的
  • 最后,在学习了GDB的许多调试指令后,我们便进行了【Swap Two Numbers】的实战演练,不仅巩固了C语言中有关传址调用以及函数栈帧的相关指知识,而且使我们对于调试器GDB的使用更上一层楼↑

以上就是本文要介绍的所有内容,感谢您的阅读❤️

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/387012.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C/C++开发,无可避免的多线程(篇二).thread与其支持库

一、原子类型与原子操作 1.1 原子类型与操作介绍 在前一篇博文中&#xff0c;多线程交互示例代码中&#xff0c;给出了一个原子类型定义&#xff1a; // 原子数据类型 atomic_llong total {0}; 那么什么事原子数据类型呢&#xff0c;和c的基础数据类型有什么不同呢&#xff1a…

实验一 Python编程基础

目录 一、实验目标 二、实验内容 1.绘制如下图形 &#xff0c;一个正方形&#xff0c;内有三个红点&#xff0c;中间红点在正方形中心。 2.使用turtle库绘制如下图形&#xff1a; 3.绘制奥运五环图 4.回文问题 5.身份证性别判别 6.数据压缩 7.验证哥德巴赫猜想 8.使…

JVM常用指令

JVM常用指令1.准备工作2.jps3. jconsole4.jstat5.jstack6.jmap7.jvisualvm工具8.自动dump内存信息1.准备工作 在idea中编写代码 public class JVMTest {Testpublic void test() throws InterruptedException {while (true) {Thread.sleep(1000);System.out.println(123);}} }…

Unity 入门精要01---标准光照模型

本节基础知识结构 基础光照部分 环境光 在标准光照模型中&#xff0c;我们会环境光来代替间接光照 Cambient g amient 我们可以在Windows->Rendering->Lighting->Enviroment进行修改Ambient 的Color 自发光 直接在最后片元着色器输出颜色之前把材质的自发光颜色添…

深圳大学计软《面向对象的程序设计》实验13 运算符重载

A. 三维坐标点的平移&#xff08;运算符重载&#xff09; 题目描述 定义一个三维点Point类&#xff0c;利用友元函数重载"“和”–"运算符&#xff0c;并区分这两种运算符的前置和后置运算。 要求如下&#xff1a; 1.实现Point类&#xff1b; 2.编写main函数&a…

关于2023年造林施工、林业设计资质,新办、年审的最新通知!

一、资质类别省林学会本年度开展认定的资质种类包括&#xff1a;造林绿化类&#xff08;含施工资质、监理资质&#xff09;、林业有害生物防治类&#xff08;含防治资质、监理资质&#xff09;和林业调查规划设计类。二、认定标准资质认定执行以下标准&#xff1a;1.造林绿化施…

边缘计算:万字长文详解高通SNPE inception_v3安卓端DSP推理加速实战

本文是在以下文章的基础上编写&#xff0c;关于SNPE环境部署和服务器端推理可以参考上一篇文章&#xff1a; 边缘计算&#xff1a;万字长文详解高通SNPE inception_v3推理实战_seaside2003的博客-CSDN博客 本文最/关键的是利用SNPE在安卓环境不同的runtimes&#xff08;CPU/G…

高通 Android10/12 4 6dof Camera+2RGBCamera异常处理经验总结

1 背景&#xff1a;此需求apk距离之前更改时间将近9个月&#xff0c;我们这边原来跟驱动那边对接指令和角度 后续没有改过&#xff0c;测试部说apk cameaid提示信息不正确。 2 原因&#xff1a;因为之前用的1.0基线&#xff08;Android 10) 后面由于客户功能需求变更&#xff…

进程信号生命周期详解

信号和信号量半毛钱关系都没有&#xff01; 每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2 查看信号的机制&#xff0c;如默认处理动作man 7 signal SIGINT的默认处理动作是终止进程&#xff0c;SIGQUIT的默认处理…

23届春招结束_分享java岗面试心得

23届春招结束_分享java岗面试心得 从一月10日开始投简历&#xff0c;经历了两个月的面试与学习&#xff0c;成功拿到了12k13薪的国企offer&#xff0c;春招结束了 一、经历秋招&#xff0c;被所谓的金九银十给坑惨了 在秋招的时候&#xff0c;经过网友&#xff08;美团java岗…

九龙证券|利好政策密集发布,机构扎堆看好的高增长公司曝光

新能源轿车销量和保有量快速增长&#xff0c;带来了充电桩商场的微弱需求。 日前&#xff0c;商务部部长王文涛表明&#xff0c;本年将在落实好方针的一起&#xff0c;活跃出台新方针办法&#xff0c;比方辅导当地展开新能源轿车下乡活动&#xff0c;优化充电等使用环境&#x…

Vue3中的h函数

文章目录简介简单使用参数使用计数器进阶使用函数组件插槽专栏目录请点击 简介 众所周知&#xff0c;vue内部构建的其实是虚拟DOM&#xff0c;而虚拟DOM是由虚拟节点生成的&#xff0c;实质上虚拟节点也就是一个js对象事实上&#xff0c;我们在vue中写的template,最终也是经过…

Unity RectTransform Scale Handler - 如何在Runtime运行时拖动缩放窗口尺寸

文章目录简介变量说明实现光标移入移出鼠标拖动距离Anchor 锚点目标尺寸扩展方向简介 本文介绍如何在Runtime运行时拖动缩放UI窗口的尺寸&#xff0c;如图所示&#xff0c;在示例窗口的左上、上方、右上、左方、右方、左下、下方、右下&#xff0c;分别放置了一个拖动柄&#…

Spring之基于注解方式实例化BeanDefinition(1)

最近开始读Spring源码&#xff0c;读着读着发现里面还是有很多很好玩的东西在里面的&#xff0c;里面涉及到了大量的设计模式以及各种PostProcessor注入的过程&#xff0c;很好玩&#xff0c;也很复杂&#xff0c;本文就是记录一下我学习过程中的主干流程。 在开始我们源码解读…

2023年湖北武汉中级工程师怎么申请?申报渠道有哪些?启程别

2023年湖北武汉中级工程师怎么申请?申报渠道有哪些&#xff1f;启程别 武汉市中级工程师怎么报名&#xff1f;很多人不知道中级职称怎么申请&#xff0c;在哪里申请&#xff0c;那么启程别来告诉大家&#xff0c;启程别是谁&#xff0c;进入百度搜索启程别就知道啦 武汉中级工…

【学习Docker(七)】详细讲解Jenkins部署SpringCloud微服务项目,Docker-compose启动

Jenkins部署SpringCloud微服务项目&#xff0c;Docker-compose启动 座右铭&#xff1a;《坚持有效输出&#xff0c;创造价值无限》 本文介绍使用Jenkins部署SpringCloud微服务项目&#xff0c;Docker-compose启动。 之前写过安装Jenkins的过程&#xff0c;这里就不写安装细节了…

[oeasy]python0099_雅达利大崩溃_IBM的开放架构_兼容机_oem

雅达利大崩溃 回忆上次内容 个人计算机浪潮已经来临 苹果公司迅速发展微软公司脱离mits准备做纯软件公司IBM用大型机思路制作的5100惨败 Commodore 64 既做计算机又做游戏机 计算机行业和游戏行业 跟随着底层技术不断迭代已经进入了战乱纷纷的年代最终又会如何呢&#xff1f…

31 openEuler使用LVM管理硬盘-管理物理卷

文章目录31 openEuler使用LVM管理硬盘-管理物理卷31.1 创建物理卷31.2 查看物理卷31.3 修改物理卷属性31.4 删除物理卷31 openEuler使用LVM管理硬盘-管理物理卷 31.1 创建物理卷 可在root权限下通过pvcreate命令创建物理卷。 # pvcreate [option] devname ...其中&#xff1…

【linux工具】Tmux简明教程

A Quick and Easy Guide to tmux (hamvocke.com) 一、说明 我们常用ubuntu&#xff0c;用altctlT实现终端窗口弹出。然而当需要多个终端一起工作&#xff0c;切换的效率就成了问题。 tmux是ubuntu下&#xff0c;终端窗口工具。该工具能实现多窗口分屏显示、多个会话在一个终端…

【13】linux命令每日分享——groupadd建立组

大家好&#xff0c;这里是sdust-vrlab&#xff0c;Linux是一种免费使用和自由传播的类UNIX操作系统&#xff0c;Linux的基本思想有两点&#xff1a;一切都是文件&#xff1b;每个文件都有确定的用途&#xff1b;linux涉及到IT行业的方方面面&#xff0c;在我们日常的学习中&…