C开发编译与调试

一、gcc编译流程

在这里插入图片描述

1.1 预处理阶段

宏定义展开,宏定义替换,展开include的文件

gcc -E -o hello.i hello.c

例如源文件hello.c内容如下:

#include <stdio.h>
int main()
{
	printf("hello test");
	return 0;
}

经过预处理后生成的hello.i内容如下:

# 1 "hello.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 391 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "hello.c" 2
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/stdio.h" 1 3 4
# 64 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/stdio.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_stdio.h" 1 3 4
# 68 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_stdio.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 1 3 4
# 649 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/_symbol_aliasing.h" 1 3 4
# 650 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 2 3 4
# 715 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/_posix_availability.h" 1 3 4
# 716 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h" 2 3 4
# 69 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_stdio.h" 2 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 1 3 4
# 135 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityVersions.h" 1 3 4
# 136 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 2 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternal.h" 1 3 4
# 137 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 2 3 4
# 70 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_stdio.h" 2 3 4

# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_types.h" 1 3 4
# 27 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_types.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/_types.h" 1 3 4
# 33 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/_types.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/machine/_types.h" 1 3 4
# 34 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/machine/_types.h" 3 4
# 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/arm/_types.h" 1 3 4
# 13 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/arm/_types.h" 3 4
typedef signed char __int8_t;



typedef unsigned char __uint8_t;
typedef short __int16_t;
typedef unsigned short __uint16_t;
typedef int __int32_t;
typedef unsigned int __uint32_t;
typedef long long __int64_t;
typedef unsigned long long __uint64_t;

///内容太长了....这里省略....

typedef long __darwin_intptr_t;
typedef unsigned int __darwin_natural_t;
# 46 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/arm/_types.h" 3 4
typedef int __darwin_ct_rune_t;

# 2 "hello.c" 2
int main()
{
 printf("hello test");
 return 0;
}

可以看到预处理阶段后,源码的内容变多了,因为这里面包含了宏定义和include的文件的内容

1.2 预编译阶段

在这个阶段,gcc才会去检测代码的规范,语法是否有错误,gcc会把代码翻译成汇编

gcc -S -o hello.s hello.i

生成的汇编代码在hello.s文件内

	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 11, 0	sdk_version 11, 3
	.globl	_main                           ; -- Begin function main
	.p2align	2
_main:                                  ; @main
	.cfi_startproc
; %bb.0:
	sub	sp, sp, #32                     ; =32
	stp	x29, x30, [sp, #16]             ; 16-byte Folded Spill
	add	x29, sp, #16                    ; =16
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	mov	w8, #0
	str	w8, [sp, #8]                    ; 4-byte Folded Spill
	stur	wzr, [x29, #-4]
	adrp	x0, l_.str@PAGE
	add	x0, x0, l_.str@PAGEOFF
	bl	_printf
	ldr	w0, [sp, #8]                    ; 4-byte Folded Reload
	ldp	x29, x30, [sp, #16]             ; 16-byte Folded Reload
	add	sp, sp, #32                     ; =32
	ret
	.cfi_endproc
                                        ; -- End function
	.section	__TEXT,__cstring,cstring_literals
l_.str:                                 ; @.str
	.asciz	"hello test"

.subsections_via_symbols

1.3 汇编阶段(最耗时)

把.s文件翻译成二进制.o文件(机器指令)

gcc -c -o hello.o hello.s

1.4 链接阶段

计算逻辑地址,合并数据段,有些函数是在另外一个so库文件中的

gcc -o hello hello.o

经过这一步之后就会生成可执行文件hello了.然后在控制台上输入./hello就可以执行了.

通常用的比较多的就是-o指定生成可执行文件的名字, 例如gcc -o hello hello.c就可以生成可执行文件hello了

二、gcc的相关参数

  • -I参数
    -I (大写的i):用于指定当前的.c文件用到的.h文件所在的目录,这样就不会报找不到头文件的错误了.
    例如某个目录下有一个a.h文件
#pragma once

int sum(int a,int b); //声明sum方法

然后hello.c文件如下:

#include<stdio.h>
#include "a.h" //包含a.h头文件

int main()
{
	int ret = sum(1,2);
	printf("ret=%d\n",ret);
	return 0;
}

int sum(int a,int b) //实现sum方法
{
	return a+b;
}

如果直接用gcc hello.c会报这个错误
在这里插入图片描述
然后使用-I指定a.h的所在的目录

 gcc hello.c -I ../12/

这样编译就OK了
在这里插入图片描述

  • -o选项
    用于指定编译后生成的文件名称,不指定的话默认是a.out

  • -D选项
    通过-D可以在编译的时候设置宏定义
    例如hello.c的代码如下:

#include<stdio.h>
#include "a.h"

int main()
{
	int ret = sum(1,2);
	#ifdef DEBUG
		printf("hello world\n");
	#endif
		printf("ret=%d\n",ret);
	return 0;
}

int sum(int a,int b)
{
	return a+b;
}

上面代码的意思就是如果存在DEBUG宏,那么就会多打印hello world
通过下面命令可以编译的时候添加宏定义
在这里插入图片描述

  • -L选项
    用于指定包含库的路径

  • -l选项(小写的L)
    用于指定库的名(通常是libxxx.so或者libxxx.a 使用时是-lxxx)

  • -g选项
    用于gdb调试,不加此选项不能调试

  • -Wall选项
    用于显示更多提示

  • -lstdc++选项
    用于编译c++文件,当然c++文件一般用g++编译.

  • -O选项
    优化代码的选项,有1-3级别,例如-O1表示用1级别优化

  • -c选项
    将.c文件或.s汇编文件编译生成.o二进制文件

  • -fPIC选项
    将.c文件编译成与位置无关的代码的.o文件 (PIC是Position Independent Code的简写)

  • -shared
    将与位置无关代码的.o文件打包成.so动态库文件.

三、Linux下静态库的制作和使用

3.1 制作静态库

静态库的命名规范为:libXxx.a 对应Windows的.lib文件.
制作步骤:
a.通过gcc -c选项将.c文件编译成.o文件
b.使用ar rcs 命令将.o文件打包,使用命令:

ar  rcs   libxxx.a   file1.o file2.o ...

假设当前工程的.c和.h文件结构如下
在这里插入图片描述
其中a,b,d,e的c文件都包含了include目录下的.h文件,那么打包静态库的步骤如下:
首先,将所有的.c文件编译成.o文件
在这里插入图片描述
用-I(大写的i)指定头文件的路径,用*.c代替src目录下的所有.c文件, 当然你不嫌麻烦的话也可以逐个进行.o文件的编译.然后,将所有的.o文件进行打包,库名叫做libCalc.a
在这里插入图片描述
同样,如果你不嫌麻烦也可以这样打包: ar rcs libCalc.a a.o b.o d.o e.o

如何查看.a库文件的内容呢?
通过nm命令可以查看.a文件内包含了哪些.o文件,以及用到了什么函数,例如:
在这里插入图片描述

3.2 使用静态库

将上面步骤生层的libCalc.a文件移动到工程的lib目录内,同时在工程的根目录下创建main.c文件,内容如下:

#include <stdio.h>
#include "head.h"

int main()
{
	int ret1 = add(10,10);
	int ret2 = mul(10,10);
	int ret3 = div(10,10);
	int ret4 = sub(10,10);
	printf("10+10=%d\n",ret1);
	printf("10*10=%d\n",ret2);
	printf("10/10=%d\n",ret3);
	printf("10-10=%d\n",ret4);

	return 0;
}

此时的工程目录结构如下:
在这里插入图片描述
然后编译main.c文件的时候需要输入下面命令

gcc -o main main.c -I ./include/ -L ./lib/ -lCalc

在这里插入图片描述
然后输入./main就可以运行程序了.
在这里插入图片描述

ps:使用-l的时候,格式是固定的,库文件需要省略前缀lib和后缀.a,并且需要和-l(小写的L)连着写.

四、Linux下动态库的制作和使用

4.1 制作动态库

动态库的命名规范为:libXxx.so 对应Windows的.dll文件.

制作步骤:
a.通过gcc -c -fPIC 将.c文件编译与位置无关的代码的.o文件,关键参数 -fPIC (PIC是Position Independent Code的简写)
b.通过gcc -shared -o将位置无关的.o文件打包.so文件,so文件的命名格式:libXxx.so

ps:上面2个步骤也可以合成一个步骤,就是 -fPIC 和 -shared 一起使用

例如还是上面那个工程
在这里插入图片描述
下面开始制作动态库
首先,将.c文件生成位置无关代码的.o文件,进入src目录,使用下面命令

gcc -c *.c -fPIC -I ../include/

在这里插入图片描述
然后,将所有.o文件打包成一个.so文件,使用下面命令

gcc -shared -o libCalc.so *.o

在这里插入图片描述
然后将生成的libCalc.so文件拷贝到当前工程的lib目录内, 然后在工程根目录,使用下面命令进行编译生成可执行程序

gcc -o main main.c -I include/ -L lib/ -lCalc

在这里插入图片描述
此时的项目结构:
在这里插入图片描述
现在你尝试./main运行会发现运行出错,并且使用ldd命令查看main会发现libCalc.so文件找不到
在这里插入图片描述
这时就需要介绍如何使用动态库了

4.2 使用动态库

方式一,将libCalc.so文件直接拷贝到系统的/lib或者/usr/lib目录内,或者创建软连接(不推荐)

方式二,将库路径添加到环境变量 LD_LIBRARY_PATH中(这种方式是临时的,下次登录就没了,不推荐),使用如下命令添加

export LD_LIBRARY_PATH=/home/chenys/work/WorkSpace/C/14/day01/lib:$LD_LIBRARY_PATH

当然这种方式还可以将配置添加到home目录下的.bashrc文件中,这样就是永久的.

方式三,配置/etc/ld.so.conf文件,在文件末尾增加库路径,例如/home/chenys/work/WorkSpace/C/14/day01/lib (ps:路径随你定,只要有.so文件存在这个路径即可.)

然后执行sudo ldconfig -v刷新下配置.
在这里插入图片描述
配置完成后,就可以运行main程序了,并且用ldd命令也能查看到libCalc.so正确找到了.
在这里插入图片描述

五、makefile的编写

makefile的好处是一次编写,终身受益
a.命名规则:makefile 或者 Makefile
b.三要素: 目标,依赖,规则, 其中目标是必须的,其它2个非必须.
c.写法:

目标:依赖
tab键规则命令

d.如何执行makefile文件:
控制台输入make [目标] ,如果不写目标,那么默认执行makefile的第一个目标,带上目标后则执行指定的目标

5.1 创建makefile生成模板

除了手动创建makefile文件外,我们还可以拷贝makefile的模板来生成,为了操作方便,我这里通过在~/.bashrc文件中添加一个alias命令来简化这一过程,添加如下语句到~/.bashrc文件中:
alias echomake='cat ~/template/makefile.template >> makefile' 注意等号2边不要留有空白符,然后在创建~/template/makefile.template文件

SrcFiles=$(wildcard *.c)

TargetFiles=$(patsubst %.c,%,$(SrcFiles))

all:$(TargetFiles)

%:%.c
	gcc -o $@ $< -g

.PHONY:clean

clean:
	-@rm -rf $(TargetFiles)

最后在控制台输入echomake就可以自动使用模板创建一个makefile了
在这里插入图片描述

5.2 使用演示

假设工程的目录结构如下
在这里插入图片描述
其中head.h的内容如下:

int add(int a,int b);
int mul(int a,int b);
int div(int a,int b);
int sub(int a,int b);

a.c、b.c、d.c、e.c的内容如下:

//a.c
#include "../include/head.h"

int add(int a,int b){
    return a+b;
}

//b.c
#include "../include/head.h"

int mul(int a,int b){
    return a+b;
}

//d.c
#include "../include/head.h"

int div(int a,int b){
    return a+b;
}

//e.c
#include "../include/head.h"

int sub(int a,int b){
    return a+b;
}

main.c内容如下:

#include <stdio.h>
#include "include/head.h"

int main(int argc,char* args[])
{
	if(argc >1)
	{
		for(int i=1;i<argc;i++)
		{
			printf("第%d个参数=%s\n",i,args[i]);
		}
	}
	int ret1 = add(10,10);
	int ret2 = mul(10,10);
	int ret3 = div(10,10);
	int ret4 = sub(10,10);
	printf("10+10=%d\n",ret1);
	printf("10*10=%d\n",ret2);
	printf("10/10=%d\n",ret3);
	printf("10-10=%d\n",ret4);

	return 0;
}

然后在工程根目录下通过vi makefile打开编辑文本,内容例如:

main:main.c ./src/a.c ./src/b.c ./src/d.c ./src/e.c
	gcc -o main -I ./include main.c ./src/a.c ./src/b.c ./src/d.c ./src/e.c

注意命令前是一个tab键
保存退出后,输入make命令会执行makefile的内容.
在这里插入图片描述
可以看到make执行的内容,就是makefile定义的规则,同时在当前目录下生层了main可执行文件.
如果执行后看到Makefile:2:*** missing separator. Stop.错误,那么很有可能是你的makefile文件在命令前的tab键格式不对,需要在vi编辑模式下按下tab,不要自己打空格.

上面这种编写方式的缺点是,如果更改其中一个文件,那么所有的源码都需要重新编译.

5.3 改进makefile文件的编写

考虑采用编译过程分解,先生成.o文件,然后使用.o文件得到结果.
在这里插入图片描述
修改项目结构,同时修改a.c、b.c、d.c、e.c里面的include路径
在这里插入图片描述
在项目根目录下重新创建一个makefile文件

main:main.o a.o b.o d.o e.o
	gcc -o main -I ./include main.o a.o b.o d.o e.o
main.o:main.c
	gcc -c main.c -I ./include
a.o:a.c
	gcc -c a.c -I ./include 
b.o:b.c
	gcc -c b.c -I ./include 
d.o:d.c
	gcc -c d.c -I ./include 
e.o:e.c
	gcc -c e.c -I ./include 

保存后,输入make,等到下面结果
在这里插入图片描述
执行main,得到结果.
在这里插入图片描述
这样看似和之前的没啥区别, 但是当我们修改a.c代码后, 再次执行make命令,你会发现输出的命令变少了
在这里插入图片描述
可以看到仅输出了2条,第一条是生成a.o的; 第2条是生成main可执行程序的, 这样就实现了增量编译了.效率也得到了提升.

六、makefile的语法

  • 1)定义变量
    变量名=值

  • 2)注释
    #号表示注释

  • 3)使用变量
    $(变量名)

例如:

#定义一个变量,存放依赖的路径
objFile = main.o a.o b.o d.o e.o
#通过&(变量名)来使用变量
main:$(objFile)
	gcc -o main -I ./include main.o a.o b.o d.o e.o
main.o:main.c
	gcc -c main.c -I ./include
a.o:a.c
	gcc -c a.c -I ./include 
b.o:b.c
	gcc -c b.c -I ./include 
d.o:d.c
	gcc -c d.c -I ./include 
e.o:e.c
	gcc -c e.c -I ./include 
  • 4)函数
    wildcard:可以进行文件匹配,用法: $(wildcard 要查找的文件)
    patsubst:内容替换(翻译:subst 字符串替换 . patsubst 带模式的字符串替换),用法:
    $(patsubst 替换前的规则,替换后的规则,需要替换的内容)

例如:

#查找当前目录下所有的.c文件,保存到SrcFiles变量中
SrcFiles=$(wildcard *.c)
#将SrcFiles变量内的.c后缀修改成.o后缀,然后保存到ObjFiles变量中,这里用到了%通配符
ObjFiles=$(patsubst %.c,%.o,$(SrcFiles))

#main的依赖以及规则中用到的文件名就可以直接使用ObjFiles变量的内容了来写活了.
main:$(ObjFiles)
	gcc -o main -I ./include $(ObjFiles)
main.o:main.c
	gcc -c main.c -I ./include
a.o:a.c
	gcc -c a.c -I ./include 
b.o:b.c
	gcc -c b.c -I ./include 
d.o:d.c
	gcc -c d.c -I ./include 
e.o:e.c
	gcc -c e.c -I ./include
  • 5)模式推导和makefile变量结合使用
    使用%号来代替符合一定规律的内容,相当于通配符, 通常会和makefile的变量一起使用,常用的makefile变量有:
$@ :代表目标
$^ :代表全部依赖
$< :代表第一个依赖,当只有一个依赖的时候用$<和$^都可以.
$? :第一个变化的依赖

注意:这些变量只能在规则中使用.

例如:

#get all c files
SrcFiles=$(wildcard *.c)
#all c files -> o files
ObjFiles=$(patsubst %.c,%.o,$(SrcFiles))

#主目标	
main:$(ObjFiles)
	gcc -o main -I ./include $(ObjFiles)

#子目标:依赖 tab键规则
%.o:%.c
	gcc -o $@ -c $< -I ./include

执行结果如下:
在这里插入图片描述

  • 6)@和-符号在规则中的作用
    @在规则前可以使规则中的命令不在控制台中显示, 但是还是会执行
    -在规则前可以使规则中的命令执行失败时不影响makefile后面的指令执行.

例如设置一个clean目标
这样做的好处是,可以清理一些临时文件,例如.o文件,可以这样编写

#get all c files
SrcFiles=$(wildcard *.c)
#all c files -> o files
ObjFiles=$(patsubst %.c,%.o,$(SrcFiles))
	
main:$(ObjFiles)
	gcc -o main -I ./include $(ObjFiles)

%.o:%.c
	gcc -o $@ -c $< -I ./include

clean:
	-@rm -rf *.o
	-@rm -rf main

可以看到mak主目标的时候会生成很多临时文件,执行make clean的时候就会清理掉这些临时文件.
在这里插入图片描述

  • 7)设置伪目标
    假设有些目标我们只是想执行一些命令,并不是用来生成某个目标文件,那么这个时候就可以定义伪目标.
    用法: .PHONY:目标名1 目标名2 ...
    好处:这样可以避免目标名和当前目录下存在同名的文件名起冲突.

例如,我们可以将clean目标设置为伪目标

#get all c files
SrcFiles=$(wildcard *.c)
#all c files -> o files
ObjFiles=$(patsubst %.c,%.o,$(SrcFiles))
	
main:$(ObjFiles)
	gcc -o main -I ./include $(ObjFiles)

%.o:%.c
	gcc -o $@ -c $< -I ./include

#定义伪目标
.PHONY:clean

clean:
	-@rm -rf *.o
	-@rm -rf main

  • 8)all目标
    all目标不需要指定规则,它通常用来指定多个依赖,并且作为makefile的首目标, 这样在执行的make的时候就会默认指向all目标.然后就可以让多个依赖都得到执行.
    通常会将all目标设置伪伪目标
    例如:
#获取当前目录下所有.c文件,保存到SrcFiles变量中
SrcFiles=$(wildcard *.c)
#将变量SrcFiles中的所有字符串的.c后缀修改为.o后缀
ObjFiles=$(patsubst %.c,%.o,$(SrcFiles))
#all目标
all:main

main:$(ObjFiles)
	gcc -o $@ -I ./include $(ObjFiles)

%.o:%.c
	gcc -o $@ -c $< -I ./include

#all目标也设置为伪目标,多个伪目标空格分开
.PHONY:clean all

clean:
	-@rm -rf *.o
	-@rm -rf main

在这里插入图片描述

七、gdb调试

修改main.c的代码如下:

#include <stdio.h>
#include "include/head.h"

int main(int argc,char* args[])
{
	if(argc >1)
	{
		for(int i=1;i<argc;i++)
		{
			printf("第%d个参数=%s\n",i,args[i]);
		}
	}
	int ret1 = add(10,10);
	int ret2 = mul(10,10);
	int ret3 = div(10,10);
	int ret4 = sub(10,10);
	printf("10+10=%d\n",ret1);
	printf("10*10=%d\n",ret2);
	printf("10/10=%d\n",ret3);
	printf("10-10=%d\n",ret4);

	return 0;
}

使用gdb调试可执行程序前,可执行程序编译的时候需要带-g选项,例如:

gcc -o main main.c a.c b.c d.c e.c -I include/ -g

然后启动gdb的命令是:gdb 可执行程序名称,例如

gdb main

会看到这样的界面,等待用户输入调试命令
在这里插入图片描述

7.1 常用的调试命令介绍

  • 1)run命令
    用于运行程序,可以简写为r,例如:
    在这里插入图片描述
    run命令后面还可以带参数,相当于给主函数的args设置参数,例如:
    在这里插入图片描述

  • 2)start命令
    让程序执行到main函数的第一条语句
    在这里插入图片描述
    此模式下按下n (next的简写)就会执行下一条指令,第二次的时候可以继续按n执行下一条,也可以直接按回车键表示重复上一个命令,也就是n命令.
    在这里插入图片描述
    如果某条语句调用了一个函数,如果想进入这个函数,那么可以按下step进入,但是系统的库函数进不了.

  • 3)quit命令
    退出gdb,也可以简写为q.

  • 4)set命令
    set命令用于指定main函数的参数,例如

set  args  [参数1] [参数2] ...

启动调试后,就可以使用set命令设置参数了
在这里插入图片描述
使用start命令启动程序后,使用set命令还可以给变量设置值,例如:
在这里插入图片描述

  • 5)list命令
    可以简写为l, 用于查看入口函数所在的源文件的代码,默认显示10行
    在这里插入图片描述
    此模式下按下"回车"可以继续查看后10行的内容. 如下所示,一直按回车就可以查看所有的代码了.
    在这里插入图片描述
    如果想查看其他源文件的代码,可以使用 list 目标文件名:行号 来查看,表示从目标文件的第1行开始查看10行内容,例如:
    在这里插入图片描述

  • 6)设置断点
    用法:b 行号 就可以设置断点了,b是break的简写,这种方式设置的断点默认设置的是主函数所在的文件,例如
    在这里插入图片描述
    表示在第17行设置了一个断点.当设置了断点后,输入run命令后会停留在断点处.
    在这里插入图片描述
    此时想要执行下一步,直接按n键即可.用法和start命令执行下一行一样.
    如果想指定文件设置断点,用法为: b 文件名:行号 例如:
    在这里插入图片描述
    还可以设置条件断点,用法为:b 行号 if 条件 ,通常用于设置循环语句的某个条件成立时插入断点,例如:
    在这里插入图片描述

  • 7)跳到下一个断点处
    输出 c命令, c是continue的意思,如下所示一直按c,直到程序结束
    在这里插入图片描述

  • 8)查看断点
    通过 info b命令查看所有设置的断点
    在这里插入图片描述

  • 9)删除断点
    d(del)断点的编号,例如:
    在这里插入图片描述

  • 10)查看变量的值
    通过p命令,也就是print命令查看变量的值
    在这里插入图片描述
    如果查看int类型数据想转成八进制查看,可以用p/o 变量名来查看,如果变量是一个结构体或者类对象,那么可以通过p 变量名.成员属性的方式查看结构体或者类的成员变量.

  • 11)查看变量的类型
    在这里插入图片描述

  • 12)display命令
    用于跟踪某个变量,每次执行下一条语句的时候都会显示被跟踪的变量的值.例如:
    在这里插入图片描述

  • 13)undisputed命令
    用于取消跟踪某个变量,通过info dispaly可以查看当前跟踪的变量及编号,然后就可以用undisplay 编号来取消了
    在这里插入图片描述

7.2 gdb跟踪core错误

  • 1)设置生成core文件

命令:ulimit -c unlimited (其中unlimited的意思是不限制大小,如果是具体的数字,那么大小就是具体的数值), 当设置了core之后, 程序报错就会生成一个core文件,记录错误信息.
假设main.c代码是这样的

#include <stdio.h>
#include "head.h"

int main(int argc,char* args[])
{
	char *bug = "I am bug";  
	bug[0] = '1'; //这里会发生core错误

	if(argc >1)
	{
		for(int i=1;i<argc;i++)
		{
			printf("第%d个参数=%s\n",i,args[i]);
		}
	}
	int ret1 = add(10,10);
	int ret2 = mul(10,10);
	int ret3 = div(10,10);
	int ret4 = sub(10,10);
	printf("10+10=%d\n",ret1);
	printf("10*10=%d\n",ret2);
	printf("10/10=%d\n",ret3);
	printf("10-10=%d\n",ret4);

	return 0;
}

编译的时候需要带上-g选项, 例如:

gcc -o main main.c a.c d.c b.c e.c -I include/ -g

main.c的代码编译没有报错,但是执行main程序的时候就core异常了,如果在此之前你设置了core跟踪,那么此时就会出现一个core文件,例如:
在这里插入图片描述
查看core的内容,使用命令 gdb 程序名称 core 查看,例如:
在这里插入图片描述

  • 2)指定core文件生成的文件名

编辑/proc/sys/kernel/core_pattern文件,不能使用vi编辑,需要切换到root用户,输入下面命令:

echo "core-%e-%p-%t" > /proc/sys/kernel/core_pattern

其中:
%p 表示添加pid
%u 表示添加当前uid
%g 表示添加当前gid
%s 表示添加导致生成core的信号
%t 表示添加core文件生成时的unix时间
%h 表示添加主机名
%e 表示添加命令名

此时再次设置core文件,并执行main程序,发生core错误时,生成的core文件命名格式就变成上面设置的格式了
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值