热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

理解C语言(零)导读(下):有用的C语言工具-从Make说起1Make在GNU中提供了一个用于管理多个C源代码文件的项目管理工具,用户只需按照一定的语法规则编写这个Makefile文件。输入

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

1 Make

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

1.1 Make规则

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

它的核心在于只要依赖的文件时间比目标更新,则执行产生目标的命令,不存在执行后续既定的命令。注:这里的目标既可以是一个目标文件,也可以是可执行文件,也可能是伪目标(命令必须从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的线程
b xxx.c:5 thread all 在xxx.c第5行处为所有经过这里的线程设置断点
set scheduler-locking off/on/step 使用单步或继续命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行

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


参考
  • Make篇参考- [GNU/Linux编程]第5章第2小节
  • GDB中应该知道的几个调试方法

推荐阅读
  • 对于以压缩包形式发布的软件,其目录中通常包含一个配置脚本 `configure`。该脚本的主要功能是确定编译所需的各项参数,如头文件的位置和链接库的路径,并生成相应的 `Makefile` 以供编译使用。通过运行此脚本,开发者可以确保软件在不同环境下的正确编译与安装。此外,该脚本还能够检测系统依赖项,进一步提高编译过程的可靠性和兼容性。 ... [详细]
  • 在CentOS上部署和配置FreeSWITCH
    在CentOS系统上部署和配置FreeSWITCH的过程涉及多个步骤。本文详细介绍了从源代码安装FreeSWITCH的方法,包括必要的依赖项安装、编译和配置过程。此外,还提供了常见的配置选项和故障排除技巧,帮助用户顺利完成部署并确保系统的稳定运行。 ... [详细]
  • 在Linux环境下编译安装Heartbeat时,常遇到依赖库缺失的问题。为确保顺利安装,建议预先通过yum安装必要的开发库,如glib2-devel、libtool-ltdl-devel、net-snmp-devel、bzip2-devel和ncurses-devel等。这些库是编译过程中不可或缺的组件,能够有效避免编译错误,确保Heartbeat的稳定运行。 ... [详细]
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • 深入解析Tomcat:开发者的实用指南
    深入解析Tomcat:开发者的实用指南 ... [详细]
  • 如何在Java中高效构建WebService
    本文介绍了如何利用XFire框架在Java中高效构建WebService。XFire是一个轻量级、高性能的Java SOAP框架,能够简化WebService的开发流程。通过结合MyEclipse集成开发环境,开发者可以更便捷地进行项目配置和代码编写,从而提高开发效率。此外,文章还详细探讨了XFire的关键特性和最佳实践,为读者提供了实用的参考。 ... [详细]
  • JVM参数设置与命令行工具详解
    JVM参数配置与命令行工具的深入解析旨在优化系统性能,通过合理设置JVM参数,确保在高吞吐量的前提下,有效减少垃圾回收(GC)的频率,进而降低系统停顿时间,提升服务的稳定性和响应速度。此外,本文还将详细介绍常用的JVM命令行工具,帮助开发者更好地监控和调优JVM运行状态。 ... [详细]
  • 在 Linux 系统中,`/proc` 目录实现了一种特殊的文件系统,称为 proc 文件系统。与传统的文件系统不同,proc 文件系统主要用于提供内核和进程信息的动态视图,通过文件和目录的形式呈现。这些信息包括系统状态、进程细节以及各种内核参数,为系统管理员和开发者提供了强大的诊断和调试工具。此外,proc 文件系统还支持实时读取和修改某些内核参数,增强了系统的灵活性和可配置性。 ... [详细]
  • 如何利用Apache与Nginx高效实现动静态内容分离
    如何利用Apache与Nginx高效实现动静态内容分离 ... [详细]
  • 深入解析Linux基础目录结构——顾咏丰的追梦之旅
    在深入解析Linux基础目录结构的过程中,顾咏丰详细探讨了其与Windows目录结构的显著差异。Linux目录结构如同一棵树,根目录位于最顶层,统领整个系统。每个文件和目录都具备特定的访问权限,确保系统的安全性和稳定性。此外,他还介绍了常见的目录及其功能,如 `/bin`、`/etc` 和 `/home`,帮助读者更好地理解和管理Linux系统。 ... [详细]
  • 如何在浏览器中高效调用本地应用程序的方法与技巧
    为了在浏览器中高效调用本地应用程序,本文介绍了一种实用方法。通过创建并运行一个注册表文件(如 `test.reg`),可以实现点击浏览器按钮后自动启动指定的本地应用。具体步骤包括编写特定的注册表条目,并确保其正确配置以支持浏览器与本地应用之间的无缝交互。此外,文章还探讨了安全性和兼容性方面的注意事项,为开发者提供了全面的指导。 ... [详细]
  • 多种实现 Windows 定时自动执行任务的专业技巧与方案
    在Windows系统中,实现定时自动执行任务有多种专业技巧和方案。常见的方法包括:使用Windows任务计划程序、开发Windows服务以及利用SQL Server Agent作业。这些方法被广泛应用于各种自动化场景,多数技术人员对此都有所了解。 ... [详细]
  • 本指南详细介绍了如何使用 `apt-get` 命令在 Ubuntu 系统上部署 MySQL 5、Apache 2、PHP 5 及 phpMyAdmin。首先,建议读者查阅 Ubuntu 的官方文档以获取更多背景信息。通过本文,您将逐步了解每个软件包的安装过程及其配置方法,确保系统环境的稳定性和安全性。此外,还将提供一些常见问题的解决方案,帮助用户顺利完成部署。 ... [详细]
  • 本文深入探讨了层叠样式表(CSS)的核心原理与应用技巧,旨在帮助读者全面理解CSS的工作机制。从选择器、属性到布局模式,文章详细解析了CSS的关键概念,并通过实例展示了如何高效运用这些技术,提升网页设计与开发的水平。 ... [详细]
  • 本文详细介绍了使用响应文件在静默模式下安装和配置Oracle 11g的方法。硬件要求包括:内存至少1GB,具体可通过命令`grep -i memtotal /proc/meminfo`进行检查。此外,还提供了详细的步骤和注意事项,确保安装过程顺利进行。 ... [详细]
author-avatar
luhd88112010_254
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有