这原本是用Word写的,可是后来我换了系统因此只能用markdown迁移而后写了......
$\qquad$本文主要投食给那些在Windows下活了好久而后考试时发现须要用命令行来操做时困惑万分以及以为GDB很好吃的人
$\qquad$以及----linux
$\qquad$常常眼瞎看不见i++和j++的区别
$\qquad$常常访问a[-1]然而使编译器迫不得已(除非在使用O2的状况下的明显访问越界)的人
...
$\qquad$正式地说,本文介绍GCC&&GDB命令在OI中的应用。c++
$\qquad$这须要提要吗?本文就讲GCC和GDB。
$\qquad$注意本文介绍的只是在OI中的应用,一些命令可能会有更高级的用法可是并不会介绍,有一些概念多是做者糊出来的能够帮助你更好的理解。
$\qquad$本文做为一个教程在语文语法方面不会很是严格,例如命令和指令常常混在一块儿用(其实我也不知道有什么区别),可是保证内容都是可用的。express
$\qquad$本文的有些部分操做在Windows x64下完成,有些在Linux x64下完成的。其中会对比赛的操做系统Noi Linux进行说明(其实大部分状况下都是同样的)。
$\qquad$Windows下的编译器:gcc version 7.1.0 (i686-win32-dwarf-rev0, Built by MinGW-W64 project),7.1.0版本支持到c++17的标准。命令行用的是cmder(可有可无)。
$\qquad$Linux下用的编译器:Thread model: posix
gcc version 6.4.0 20170724 (Debian 6.4.0-2),支持到c++17的标准
$\qquad$Noi Linux请参考NOI Linux的说明。
$\qquad$下面的指令说明部分${…}不是你要输入的部分,它表明参数。指令后面的括号表示简写。
$\qquad$接下来的一份比较简单的演示源文件,名为XiaPi.cpp。数组
#include <cstdio> int main(){ int ans = 0; for(int i = 1; i <= 10; i++) ans ++; if(ans) std::puts("Hello World"); return 0; }
$\qquad$通常统称GCC。然而通常编译咱们用的GCC/G++编译器,其实二者都能“编译”C/C++源代码,主要是由于编译时二者会相似于“互相调用”。惟一不一样的是GCC没法连接C++的库,因此gcc连接时须要加-lstdc++。反正G++是万能的,那么就用G++吧。
$\qquad$而后推荐一个好东西:makefile。由于笔者通常用的编译命令比较长,例如:
g++ XiaPi.cpp -g -Wall -Wextra -std=c++17
$\qquad$因而你在源代码目录下建立makefile文件,而后里面的语法参照:markdown
all:XiaPi.cpp g++ XiaPi.cpp -g -Wall -Wextra -std=c++17
$\qquad$也就是说XiaPi.cpp为你的源文件名,后面是你的编译命令。注意第二行的缩进是必须的。数据结构
$\qquad$G++在OI中经常使用命令解析:ide
g++ -v
$\qquad$就是把你的编译器的信息打印出来。
$\qquad$不须要加文件名。
$\qquad$首先编译文件的话要以g++ XiaPi.cpp开头。
$\qquad$接下来是各类开关。全部开关均可以调换顺序,可是g++后面必定接文件名(除了-v)。函数
-g2(-g)/-g3
$\qquad$打开调试开关,这样这份代码就能够被调试。
$\qquad$-g3能够调试宏定义的代码工具
-O0/-O1/-O2/-Os/-O3
$\qquad$编译器优化,通常本身不须要加,由于在O2及以上的等级都会致使不愉快的调试。
$\qquad$然而打开O2编译等以上的优化能够找出一些越界等问题。
$\qquad$Os为O2.5优化,也就是打开O2但不增长代码长度,有时候会有问题优化
-Wall -Wextra -Werror
$\qquad$三个命令都是指给代码提供警告,其中:第一个为基础警告,第二个为详细的警告,第三个为把全部警告视为错误。
$\qquad$对于比较粗心的人有极佳的辅助效果。
-std=${standard(c++11 / c++14 / c++17)}
$\qquad$指定C++标准。然而通常来讲考试都不会加这个选项,因此通常平时加加就差很少了
$\qquad$主要是锻炼一些装逼写高级代码的能力。
$\qquad$注意不是全部的编译器版本都支持各类标准,目前经常使用标准有C++11,C++14,C++17。
$\qquad$这些标准的区别在于语法不一样,例如:
$\qquad$register标识在c++17下会被提示禁用(其实很多标识都会被忽略)
for(auto it : vector){ ... }
$\qquad$在C++11及以上可使用
-o ${filename}
$\qquad$后面可接文件名,表示输出的可执行文件名。可是千万别写相似于-o XiaPi.cpp的东西......
-E
$\qquad$输出预处理器处理过的东西,通常是须要在指令后面加上 > XiaPi.e才会输出到文件。通常没什么用,可是你能够观察到前人对你的代码作出了多大的努力。
$\qquad$例以下面一张图是-E处理后的XiaPi.cpp。
[预处理][预处理]
$\qquad$这个有几千行,然而源代码只有几行。
-S
$\qquad$出编译后的汇编代码,有时候能够做为分析优化的工具。会输出一个.s的文件,像这样:
.file "XiaPi.cpp" .def ___main; .scl 2; .type 32; .endef .section .rdata,"dr" LC0: .ascii "Hello World\0" .text .globl _main .def _main; .scl 2; .type 32; .endef _main: LFB44: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $32, %esp call ___main movl $0, 28(%esp) movl $1, 24(%esp) L3: cmpl $10, 24(%esp) jg L2 addl $1, 28(%esp) addl $1, 24(%esp) jmp L3 L2: cmpl $0, 28(%esp) je L4 movl $LC0, (%esp) call _puts L4: movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE44: .ident "GCC: (i686-win32-dwarf-rev0, Built by MinGW-W64 project) 7.1.0" .def _puts; .scl 2; .type 32; .endef
$\qquad$而后就是GDB部分。GDB的安装(若是和编译器分开了)应和编译器配套,能够经过gdb -v查看你的版本信息。同时在Windows下彷佛没有Linux下那么流畅。有时候莫名崩溃,例如在GDB内运行程序以后应该等待(至少个人两台Windows都会这样)线程都开出来了才行,即显示了相似于
[New Thread 11064.0x3d2c] [New Thread 11064.0x3dbc] [New Thread 11064.0x1f00] [New Thread 11064.0x3dc4]
的东西再输入数据,不然可能崩溃。
$\qquad$首先咱们在命令行内使用 gdb ${可执行文件名}
进入GDB,例如不加-o的状况下,Windows可以使用gdb a(可省略后缀名),Linux下可以使用gdb a.out进入。若是使用了-o,则后面接你的可执行文件名。
$\qquad$gdb每次接受一条命令,回车则为重复执行上一条命令。
$\qquad$通常若是当前行前面有(gdb)
那么就是在等待用户命令,不然多是等待输入或者等待程序运行。
$\qquad$(扩展命令)若是不在gdb内的程序进入了一个死循环什么的,而后你想在当前状态迅速调试它,那么能够再开一个终端,而后输入gdb ${可执行文件名} ${进程pid}
去控制它
中止点
$\qquad$可使正在运行的程序中断在某个点上。有breakpoint(对应操做break)和watchpoint(对应操做watch)之分(还有一个catchpoint,可是OI中不用)。breakpoint接受一个位置,当到达这个位置的时候停下。Watchpoint接受一个表达式,当表达式的值有变化的时候暂停程序。
查看
$\qquad$一直监视一个变量,对应操做display。
$\qquad$有时候gdb接受一个编号,有时候接受一个编号或范围,编号就是一个数字,范围写做i-j
$\qquad$接下来的操做都是在gdb内执行的。
help
help help ${function_name}
最重要的命令,获取即时帮助
file
file ${command_name}
$\qquad$这个命令是当你gdb后面没有输入文件名或者输入错误的时候能够载入文件,当gdb中文件有变化(不要太大的变化,可执行文件不能变)的时候能够重载文件。
list
list(l) list(l) ${function_name} list(l) ${line_number}
$\qquad$打印代码。第一个是自动连续显示,第二个从某行开始打印。list会有个相似于计数器的东西,每次打印一片,Ctrl+C退出打印,回车继续打印。例如:
#include <cstdio> int main(){ int ans = 0; for(int i = 1; i <= 10; i++) ans ++; if(ans) std::puts("Hello World"); return 0; }(gdb)
$\qquad$若是打印到了文件尾部,则会有:
Line number 9 out of range; XiaPi.cpp has 8 lines.
$\qquad$此时若是继续list会一直提示。因此咱们要list 1回到文件首部。
run(r)
run(r)
$\qquad$这个命令会运行程序,直到遇到错误或遇到断点。运行此命令前请保证已经设置了断点,除非你利用这个检查你程序的错误。
$\qquad$注意到咱们断点设置在几行(例如第五行),程序会在第五行暂停,但注意程序此时并无运行第五行,例如:
Thread 1 hit Breakpoint 1, main () at XiaPi.cpp:5 5 for(int i = 1; i <= 10; i++) ans ++;
$\qquad$表示程序并无运行到第五行。即在之后看到行号n的时候程序尚未运行第n行的。
next(n)
next(n) nexti(ni)
$\qquad$向下走一步(一行),但不进入函数
$\qquad$后面那个是在汇编中下一行
step(s)
step(s) stepi(si)
$\qquad$向下走一步(一行),且若是有函数就进去,内联或宏定义不会进去
$\qquad$后面那个是在汇编中下一行,而且进入call
skip
skip ${function_name} skip ${file_name} skip delete\disable\enable
$\qquad$在step时跳过这个函数或文件
$\qquad$delete\disable\enable能够参考其余命令的意思
finish
finish
$\qquad$运行直到退出这个函数
until
until
$\qquad$运行直到当前循环结束
continue(c)
continue(c)
$\qquad$继续运行直到下一个断点
jump
jump ${line_number}
$\qquad$直接跳到某行,若是发生了函数栈的逻辑错误,例如这个函数没有执行完而后直接调到另外一个函数,可是命令不会改变栈的内容,因此会有一些不可预料的错误
break(b)
break(b) break(b) ${function_name} break(b) (${file_name}::) ${line_number} break(b) ${line_number} ${expression}
$\qquad$断点操做,第一个是查看全部断点,注意断点都有编号。
$\qquad$第二个是在某个函数开头设置断点。
$\qquad$第三个使在某行设置断点,但程序并不会运行该行。
$\qquad$例如
Breakpoint 1 at 0x401611: file XiaPi.cpp, line 6.
表示在XiaPi.cpp中运行到了第6行,但尚未执行第六行的代码
$\qquad$若是赶上多文件(例如交互题)怎么办?你能够用break ${file_name}::${line_number}
来设置指定文件的断点
$\qquad$(扩展用法)最后一个是一个高级用法:当expression知足的时候断点生效,$数据结构等比较麻烦的东西特效$
save && source
save breakpoints ${file_name} source ${file_name}
$\qquad$从某DL那听来的,好像也比较实用,就是在每次调试完以后但愿下一次调试使用一样的断点
$\qquad$就能够首先保存到文件,而后下一次打开的时候source一下
condition
condition ${break_number} ${expression}
$\qquad$上面命令的一个附属命令,修改某个断点上的判断表达式
commands
commands ${break_number} ${commands} end
$\qquad$在遇到某个断点而且被停住的时候执行命令(GDB内的命令,例如打印某个变量)
$\qquad$配合if能够很好地用在一个复杂的程序提取关键信息
$\qquad$例如一个使用状况:
(gdb) commands 1 Type commands for breakpoint(s) 1, one per line. End with a line saying just "end". >print ans >end
ignore
ignore ${break_number} ${times}
$\qquad$忽略某个断点的条件若干次
watch
watch ${expression} rwatch ${expression} awatch ${expression}
$\qquad$设置观察点,当一个表达式(变量)发生一下时间的事情时中断程序
$\qquad$watch:表达式值有变化,rwatch:表达式被读,awatch:b表达式被读或被写(后面两个通常都对于表达式),其中awatch比较适用于一些你认为一个变量被玄学地改动的时候能够用的
clear
clear clear ${line_number} clear ${function_name}
$\qquad$删除中止点(不仅有断点)
$\qquad$后面就是指定了行数或函数名
delete(d)
delete(d) delete(d) ${break_number}
$\qquad$删除断点,支持范围(这个意思是这个命令能够把编号替换成为范围)
disable\enable
disable ${break_number} enable ${break_number} enable ${break_number} once enable ${break_number} delete
$\qquad$暂时禁止某个断点,启用某个断点,once表示用一次以后disable,delete表示用一次以后delete
$\qquad$支持范围
checkpoint
checkpoint
$\qquad$在当前位置设置一个检查点,以便于从新调试程序的时候快速恢复状态,注意,它的简单原理就是拷贝进程,因此当前checkpoint进程跑完以后就没有checkpoint,若是须要重复使用就在restart以后立刻checkpoint
restart
restart ${check_number}
$\qquad$在第几个checkpoint重启程序
print
print print (${file_name}/${function_name}::)${expression} print {array_pointer}@${length} printf ${format},${expression}...
$\qquad$第一个是输出历史查看过的变量
$\qquad$而后是打印表达式的值(开了-g3可使用部分宏定义),同时能够指定文件或函数(能够跨函数),表达式内容许使用函数,而且可使用相似于a=1
的表达式来赋值等等
$\qquad$而后是查看某个数组指针开始的多少个值,若是是二维数组,能够用print *a[10]@100
查看
$\qquad$后面那个是按照printf的格式打印变量
$\qquad$注意有些STL函数或结构体函数不能被查看,这个也没有办法总结,本身按照经验吧
display(disp)
display(disp) ${expression} undisplay(undisp) ${display_number} delete display ${display_number} disable ${display_number} enable ${display_number}
$\qquad$创建查看,一直打印变量,只要有程序停下或者输入命令都会显示,注意不要弄得太多太复杂了,不然会很卡
$\qquad$后面两个都是删除查看
$\qquad$后面两个和前面的同样
call
call ${expression}
$\qquad$执行某个表达式(函数),而且打印返回值,和print不一样的是当返回值为void的时候call不会打印值
set
set ${var_name}=${number} set var ${var_name}=${number} set args ${args}...
$\qquad$赋值,若是赋值对象是gdb内的关键字的名词,那么须要用var声明
$\qquad$最后一条能够设置程序的参数
ptype
ptype ${struct/union/class/expression}
$\qquad$能够查看某结构体的具体类型(具体到union/int[2]),能够检查一些隐式转换以防止被卡浮点什么的
whatis
whatis ${struct/union/class/expression}
$\qquad$能够查看某结构体的具体类型(具体到const number)
$\qquad$和上一命令的区别举例:
(gdb) ptype d type = const union { int i[2]; double d; } (gdb) whatis d type = const number
d是某个库内定义的东西
info
info 查看全部info可用参数 info address ${var_name} -- 打印指定变量在内存中的位置 info all-register -- 打印全部寄存器状态 info args -- 打印当前函数栈的参数 info breakpoints (${break_number}_ -- 答应断点信息 info checkpoints (${check_number}) -- 打印checkpoint信息 info display (${display_number}) -- 打印display的信息 info functions -- 打印函数信息 info handle -- 打印处理signal的信息 info line (${line_number}) -- 打印行信息,默认当前行 info program -- 打印程序状态 info registers (${register_name}) -- 打印寄存器状态,不要"$",例如`info register eax` info signals -- 打印信号信息 info skip -- 查看skip info source -- 查看源码信息 info sources -- 查看源码以及依赖库信息 info stack -- 查看栈信息 info symbol -- 查看某个地址上的变量 info types -- 打印全部定义过的类型 info variables -- 打印全部变量 info watchpoints -- 打印观察点信息
就在上面
breacktrace(bt)
breacktrace(bt) breacktrace(bt) ${number}
$\qquad$查看栈信息,后面的数字表示查看几层,正数从栈顶开始,不然从栈底开始(一次从栈顶/栈底开始)
frame
frame ${stack_numbwe} down/up ${number}
$\qquad$跳到第几层栈(不会对栈形成影响)
$\qquad$向上/下移动当前栈
handle
handle ${signal_name} ${keyword}
$\qquad$对某个信号进行处理
$\qquad$signal_name有
SIGABRT -- 进程中止运行 SIGALRM -- 警告钟 SIGFPE -- 算述运算例外 SIGHUP -- 系统挂断 SIGILL -- 非法指令 SIGINT -- 终端中断 SIGKILL -- 中止进程(此信号不能被忽略或捕获) SIGPIPE -- 向没有读的管道写入数据 SIGSEGV -- 无效内存段访问 SIGQOUT -- 终端退出 SIGTERM -- 终止 SIGUSR1 -- 用户定义信号 SIGUSR2 -- 用户定义信号 SIGCHLD -- 子进程已经中止或退出 SIGCONT -- 若是被中止则继续执行 SIGSTOP -- 中止执行 SIGTSTP -- 终端中止信号 SIGTOUT -- 后台进程请求进行写操做 SIGTTIN -- 后台进程请求进行读操做
$\qquad$keyword有
nostop 发出信息,不中止程序 stop 发出信息病中止程序 print 仅发出信息 noprint 不发出信息 pass/noignore 程序处理信号 nopass/ignore 不让程序处理信号
$\qquad$有时候但愿能够暂时忽略某个信号而继续程序可使用,但不保证每次均可以
print ${var_name}@${length}
$\qquad$查看内存中某个变量后面的值。用这个能够检测你的错误是否和下标越界有关
search
$\qquad$只是查找源文件的文本而已
max-value-size
$\qquad$这个是限制gdb一次可以查看的数组的大小的限制
$\qquad$通常其实够用了,可是若是你的数组内重复数据比较多的话那么就能够把这个开到尽量大
set max-value-size ${size} set max-value-size unlimited
$\qquad$第一个能够设定限制大小
$\qquad$能够取消限制
也许是完结了吧