• 理解C语言(零) 导读(下):有用的C语言工具从Make说起


    理解C语言(零) 导读(下):有用的C语言工具-从Make说起


    1 Make

    在GNU中提供了一个用于管理多个C源代码文件的项目管理工具,用户只需按照一定的语法规则编写这个Makefile文件。输入make命令,系统会自动的根据当前文件的修改情况确定哪些文件需要重编译,一旦文件被修改,make工具只会执行依赖于该文件的一系列规则,这样节省了整个编译和链接时间。

    1.1 Make规则

    Makefile是由若干规则组成,每个规则定义了生成对应目标文件和它的依赖关系、产生目标文件需要执行的命令。

    它的核心在于只要依赖的文件时间比目标更新,则执行产生目标的命令,不存在执行后续既定的命令。注:这里的目标既可以是一个目标文件,也可以是可执行文件,也可能是伪目标(命令必须从tab键开头)

    目标: 依赖文件列表
    <tab>命令 
    

    如执行

    #Makefile 文件1
    appex: main.o app.o mod.o lib.o
    	@echo "正在编译模块..."
    	gcc -o appex main.o app.o mod.o lib.o
    
    main.o: main.c app.h
    	gcc -c main.c 
    app.o: app.c app.h
    	gcc -c app.c
    mod.o: mod.c
    	gcc -c mod.c
    lib.o: lib.c lib.h
    	gcc -c lib.c
    
    clean:
    	rm -f *.o
    

    有几点需要说明:

    • 关于GCC的参数使用,参考理解C语言(零) 导读(上)的第一节
    • Make目标规则中支持三个通配符:*、 ?、 [...]
    • 若未指定目标,则运行make命令默认执行第一个目标。运行make clean命令,清除所有的目标文件(clean是一个伪目标,并不生成clean这个文件,它只是一个标签)。还有很多这样的命令,如
      all: 一般是编译所有的目标;
      install: 把已经编译号的目标执行文件拷贝到指定目标中去
      tar: 把源程序打包备份,tar文件;
      dist: 创建一个压缩文件
      TAGS: 更新所有的目标,以备完整的编译使用

    为避免和目标文件重名的情况,通常使用一个特殊的标记.PHONY来显式地指明这是一个伪目标,如:

    .PHONY: doc
    doc:
    	command
    
    .PHONY: distclean clean
    distclean: clean
    	$(MAKE) -C test distclean
    	rm -rf autom4te.cache/
    	rm -f Makefile
    	rm -f $(CILLYDIR)/App/$(CILLYMOD)/CilConfig.pm
    	rm -f config.h
    	rm -f config.log
    	rm -f config.mk
    	rm -f config.status
    	rm -f doc/header.html
    	rm -f doc/index.html
    	rm -f src/machdep-ml.c src/cilversion.ml
    	rm -f stamp-h
    
    clean: $(CILLYDIR)/Makefile
    	rm -rf $(OBJDIR)
    	rm -f $(BINDIR)/$(CILLY).*
    	rm -rf lib/cil share/
    	rm -f META
    	rm -rf doc/html/
    	rm -rf doc/cilcode.tmp/
    	rm -f doc/cil.version.*
    	rm -f doc/cilpp.*
    	$(MAKE) -C $(CILLYDIR) clean
    	rm -f $(CILLYDIR)/App/$(CILLYMOD).pm
    	rm -f $(CILLYDIR)/Makefile.old
    	$(MAKE) -C test clean
    
    • 规则支持多目标,因为有可能我们的多个目标同时依赖于一个文件,我们就把它合并起来,建议使用自动变量$@表示目前规则中所有的目标集合,如:
    bigoutput littleoutput : text.g
    	generate text.g -$(subst output,,$@) > $@
    
    • 定义多目标规则-使用到了自动化变量$< (表示所有的依赖目标集),$@(表示所有的目标集合)。例如
    objects= foo.o bar.o
    all: $(objects)
    $(objects): %.o : %.c 
    	$(CC) -c $(CFLAGS) $< -o $@
    

    该例子的意思是:我们的目标从$objects中获取-所有.o结尾的目标,就是foo.o/bar.o,依赖的模式是对应的%.c文件。目标文件较多时,采取这种静态模式规则更为方便,灵活

    • 字符在命令行前表示在命令执行前输出信息到屏幕上
    • 如果命令模式里含有多个命令需要连续执行,应使用;分隔命令
    • 嵌套make执行-每个子目录中都有一个Makefile,根目录有一个Makefile,根目录的Makefile应如下书写,它表示先进入这个子目录中,再执行make命令
    search: 
    	cd suddir && $(MAKE)
    

    在Makefile中,主要包含了以下内容: 变量定义、显式规则、隐含规则。

    1.2 变量

    A. 变量基础
    变量名的命名规则:变量名=变量值(字符串),引用变量时在变量前加上$符号,也可用"()"或者"{}"把变量给包起来(为了安全使用)。如果使用变量来定义变量的值,有两种方式: "="或者":="

    # =符号允许变量可以使用后面的变量定义,但出现递归引用,就不行
    foo = $(bar)
    bar = $(ugh)
    ugh = Huh?
    all :
    	echo $(foo)
    
    x := foo
    y := $(x) bar
    x := later
    # 使用:=,前面的变量就不能使用后面的变量
    
    

    操作符"?="表示如果变量没有定义过,则变量值就是后面的形式,若先前被定义,什么都不做。追加变量sh值使用"+=",如:

    # 结合条件选择是否追加相应参数,摘自CIL代码
    CILHOME := ..
    CILLY := $(CILHOME)/bin/cilly
    
    ifdef _MSVC
    	include Makefile.msvc
    else
    ifdef _GNUCC
    	include Makefile.gcc
    endif
    endif
    
    CILLY += --mode=$(COMPILERNAME) --decil
    CILLY += --save-temps $(EXTRAARGS)
    
    

    目标变量:我们还可以为目标设置局部变量,这个变量只会作用在这条规则和连带规则中,而不影响其他规则链以外的值。如:

    prog: CFLAGS = -g
    prog: prog.o foo.o
    	$(CC) $(CFLAGS) prog.o foo.o -o prog
    
    prog.o : prog.c
    	$(CC) $(CFLAGS) prog.c
    
    

    如果我们想在多个目标中定义,则使用模式变量,如%.o: CFLAG= -O

    B. 条件判断

    • ifeq (arg1,arg2)... else ...endif 如果两个参数的值相等,则表达式为真,相反的是ifneq
    • ifdef var-name ... else ... endif 如果定义了某变量值非空,则表达式为真,相反的是ifndef

    例如:

    ifeq ($(CC),gcc)
    	libs=$(libs_for_gcc)
    else
    	libs=$(normal_libs)
    endif
    
    ifndef NOCHECK
      CILLY += --strictcheck
    endif
    
    ifdef OCAMLDEBUG
      CILLY+= --ocamldebug
    endif
    

    1.3 隐含规则、模式规则及其使用的变量

    GNU make中定义了内置各种隐含规则,在不给出产生目标文件的命令时由make自动添加,例如未定义如何产生目标的命令,如:

    demo.o: demo.c app.h
    # make会自动添加如下规则
    # $(CC) $(CFLAGS) $(CPPFLAGS) ... -c $< -o $@
    

    我们看到在隐含规则中基本上都使用了一些预定义的变量,你可以在文件中进行重新定义。只要设定了这些预定义变量,就会对隐含规则起作用。这些预定义变量分为两种类型:命令相关,参数相关。

    命令相关:

    • AR: 函数库打包程序,默认命令ar;AS: 汇编语言编译程序,默认as
    • CC: C编译器,默认cc; CXX: C++编译器,默认g++; CPP: C程序的预处理,默认$(CC) -E
    • RM: 删除文件命令,默认rm -f

    参数相关:

    • CFLAGS: C编译器参数,可添加加入非标准的目录-I dir或者调试信息-g选项
    • CPPFLAGS: C预处理参数;CXXFLAGS: C++编译器参数
    • LDFLAGS: 链接器参数,通常可添加-lxxx指定的库文件(如-lm)或者指定的库搜索路径-L dir
    • LEX: 词法分析器,默认lex; 语法分析器,默认yacc

    还注意到刚才已经多次提到了自动变量,它的值是与规则中的目标和依赖对象有关,即把模式中定义的一些列文件自动地取出,直至所有的符合模式的文件都取完,自动化变量只出现在规则的命令中。如下:

    • $@ : 匹配规则中的目标文件集合
    • $^ : 所有的依赖目标集合,以空格分隔,有重复去除($+,不去除重复)
    • $< : 第一个依赖文件,如果依赖目标是以模式%定义的,表示符合模式的一系列文件
    • $* : 不包含扩展名的目标文件名称
    • $? : 所有比目标新的依赖目标的集合,以空格分隔。

    希望只对更新过的依赖文件操作,$?就很有用,例如一个库文件lib,由其他几个目标文件更新,那么把几个目标打包的高效率的规则如下:

    lib : x.o y.o z.o
    	ar r lib $?
    

    结合这些采取不同的规则修改上面我们定义的Makefile文件

    • 使用自动变量
    OBJS= main.o app.o mod.o lib.o
    appex: $(OBJS)
    	@echo "正在编译模块..."
    	$(CC) -o $@ $^
    
    main.o: main.c app.h
    	$(CC) -c -o $@ $<
    app.o: app.c app.h
    	$(CC) -c -o $@ $<
    mod.o: mod.c
    	$(CC) -c -o $@ $<
    lib.o: lib.c lib.h
    	$(CC) -c -o $@ $<
    
    clean:
    	rm -f *.o
    
    • 使用隐含规则
    OBJS= main.o app.o mod.o lib.o
    appex: $(OBJS)
    	@echo "正在编译模块..."
    	$(CC) -o $@ $^
    
    main.o: main.c app.h
    app.o: app.c app.h
    mod.o: mod.c
    lib.o: lib.c lib.h
    
    clean:
    	rm -f *.o
    
    
    • 使用模式规则,把具有相同行为特点的规则,进行通配表示
    *.o : *.c
    	$(CC) -c $< -o $@
    	
    OBJS= main.o app.o mod.o lib.o
    appex: $(OBJS)
    	@echo "正在编译模块..."
    	$(CC) -o $@ $^
    
    main.o: main.c app.h
    app.o: app.c app.h
    mod.o: mod.c
    lib.o: lib.c lib.h
    
    clean:
    	rm -f *.o
    

    2 GDB

    例如我有三个文件:stack.h、stack.c、teststack.c,通过Makefile编译或(gcc -g -o stack),生成可执行文件stack ,下面将进行调试

    2.1 运行程序

    加载可执行文件:gdb stack
    如果显示No symbol table is loaded,其实是因为GCC编译时没加入-g选项。

    解决办法是在编译时一定要加-g选项以加入调试信息,即修改Makefile里面的CFLAGS选项=-g,因为如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。

    加入-g参数后,会显示Reading symbols from stack...(no debugging symbols found)...done

    查看当前源代码:l func-name(函数名,默认为main)
    默认l(list) : 显示main函数,直接回车表示,表示重复上一次命令

    开始和停止命令:

    命令 效果
    r 运行程序,可在此给出命令行参数(r,run命令的缩写)
    q 退出GDB(q,quit命令的缩写)
    k 停止程序(k,kill命令的缩写)

    设置调试的命令行参数:
    gdb命令行中的gdb --args <运行文件> <参数>
    gdb环境中的set args命令
    run执行时加入参数

    2.2 设置断点和调试执行

    断点: b,break命令的缩写;d,delete命令的缩写

    命令 效果
    b 16 在16行处设置断点
    b sum 在函数sum入口处设置断点
    b *0x8048394 在地址0x8048394处设置断点
    d id 删除断点的标号(注:不是行号或函数名)
    d 删除所有断点
    info b 查看断点信息

    调试执行:

    命令 效果
    n 单步执行
    c 继续执行
    s 进入函数内部
    finish 运行直到当前函数返回,即跳出某个函数或断点

    2.3 检查代码和数据

    检查代码:

    命令 效果
    disas 反汇编当前函数
    disas sum 反汇编函数sum
    disas 0x8048394 0x80483a4 反汇编指定地址范围内的代码
    info frame 查看当前栈帧的信息
    bt 查看函数堆栈信息

    检查数据:p,print命令的缩写;x-输出地址信息

    命令 效果
    p 变量名 输出变量的值,总是需要一个变量名
    p 0x100 输出0x100的十进制表示
    p /x 555 输出555的八进制表示
    p /t y 输出y的二进制表示

    注:p显示的变量信息均是在当前n命令(c,还未执行到这一行语句)前的内容

    变量设置:直接使用set $name
    例如想逐个打印数组的元素,可以如下:

    (gdb) set $i=0
    (gdb) p arr[$i++]
    

    2.4 多线程调试

    建议使用多线程库时使用-lpthread定位解析多线程头文件

    命令 效果
    info thread 查看当前进程中的线程
    thread <ID> 切换调试的线程为指定ID的线程
    b xxx.c:5 thread all<ID> 在xxx.c第5行处为所有经过这里的线程设置断点
    set scheduler-locking off/on/step 使用单步或继续命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行

    参数说明:off 不锁定任何线程,所有线程都执行;on 只有当前被调试程序执行;step 单步的时候,除了next过一个函数意外只有当前线程会执行


    参考

  • 相关阅读:
    在springMVC的controller层获取view层的参数的方式
    springMVC创建基础变量
    javascript 继承
    CSS3的新属性的一下总结
    常用js函数整理--common.js
    function与感叹号
    javascript void运算符
    ui组件--弹出层layer的使用
    组件,控件,插件,库都是什么鬼啊
    jsdoc文档
  • 原文地址:https://www.cnblogs.com/xionghj/p/4319504.html
Copyright © 2020-2023  润新知