04-makefile和文件IO
makefile和文件IO
gdb调试
直接生成可执行文件:gcc *.c -o app -g
调试过程
启动gdb gdb app
- start:程序开始执行且只一步
- n: 单步调试执行(next,会跳过函数,直接执行下一行) 遇到for循环会很不舒服
- s : 单步(step) 可进入函数内部
- c: 直接停在断点的位置(continue)
- run(/r):程序执行至结束时
查看程序:
-
l 回车(查看的是包含main函数的文件)(因gdb是个文字界面而非图形界面,所以只能手动设置断点,要先查看程序,设置断点)
-
l xxx.c:row (函数名)-- 查看其他程序中指定行 :l 10 加函数名后会停在入口的位置
-
l xxx.c:functionname :查看其他程序中指定函数:(只显示部分,继续按l显示额外10行,继续回车不断显示 )
设置断点:
-
设置当前函数断点:
break row
或b row
-
设置指定文件断点:b filename:row (函数名)
-
设置条件断点:
b 15 if i==15
(第十五行for循环,当i=15时断点,设置在循环函数里面,而非for函数那一行) -
查看断点信息:
info b
或i b
- num:断点序号 type:标识类型 enb :表示断点状态(只有是y的时候才可用)what 说明断点在哪个函数的哪一行的位置 可能附加条件断点的确切描述 (stop only if …)
-
删除断点:del(/d/delete) varseq
- varseq是断点编号
查看设置的断点:
info b
开始 执行gdb调试
-
执行一步操作:start
-
继续执行:n
-
执行多步,停在断点处:c(直接往后执行,continue的意思)
-
退出gdb调试: quit
单步调试:
-
进入函数体内部具体查看内容:s (step的意思) 想看源代码: l(中文会被显示乱码
-
从进入到的函数体内部回到当前文件,即跳出当前函数:finish(要提前删除断点)
-
不进入函数体内部调试:n(next的意思)
-
退出当前循环/跳出单次循环:u( 类似于continue)
查看变量的值
p var—print 接下来再n,则每执行一次会把对应变量的值打印出来
查看变量的类型
ptype 变量名
设置变量值:
set var 变量名=赋值 set var i=10 ,再n,则会看到i=11时的查看结果
设置追踪变量
-
设置追踪:display var 查看详细辅助信息
-
取消追踪:undisplay varseq (获取编号借助b info)
makefile的编写
makefile是项目的源代码管理工具,可以把所有代码编译的命令写到makefile中,之后,只需要执行makefile中的命令,那么程序就可以做个编译,编译生成可执行文件(makefile由架构师等编写)
.o是中间文件,是从.c到app的过渡
命名方式
- makefile – 纯小写
- Makefile – 第一个字母大写,加下来小写
makefile的规则
规则中三要素:目标,依赖,命令
版本一:仅一条规则
-
抽象语法表达:目标:依赖条件
<cr>
命令:目标就是想产生的可执行文件的名字。依赖就是所有的.c文件。命令是生成文件的指令。需要一个table缩进。makefile里面可能有多个规则,每个规则对应一个目标。
app:main.c add.c sub.c mul.c (makefile和.c在同一个目录下,否则需额外指定)
gcc main.c add.c sub.c mul.c -o app
-
写完makefile后可以直接make,是能够执行makefile里的命令。make的作用:执行一个个目标,借助于目标下写好的命令和依赖文件完成执行。
-
存在缺陷,很多文件时编译很耗时。改动一个,全部内容重新编译。效率低。看版本二。
版本二:
对每个文件分别编译,改动哪个,只重新编译哪个,其他不改动。依赖条件需要变为.o。多个规则。默认第一条规则的是终极目标,其余是多个子目标,当生成终极目标时会查找依赖,依赖没有时向下查找对应的规则,执行规则下命令,最后执行终极目标下命令。
-
app:main.o add.o sub.o mul.o -o app
gcc main.o add.o sub.o mul.o -o app
-
main.o:main.c
gcc -c main.c
-
add.o:add.c
gcc -c add.c
-
sub.o:sub.c
gcc -c sub.c
-
mul.o:mul.c
gcc -c mul.c
-
此时make,会执行五条命令。
makefile的原理
根据文件修改时间判断是否更新。对比目标和各个依赖的修改时间关系。检索所有目标和依赖的关系。目标最新,则不需要更新,目标晚于依赖,则需要更新生成新的目标。
版本三&&makefile中的变量
在makefile中定义变量,变量不需要数据类型,只需要名字。
obj=main.o add.o sub.o mul.o
target=app
$(target)=$(obj)($ 用于输出变量的值)
gcc $(obj) -o $(target)
-
模式规则
%.o:%.c (区别于之前*作为通配符)
gcc -c $< -o $@
使用末行模式替换法进行替换操作
-
makefile中的自动变量
$<: 规则中的第一个依赖
$@:规则中的目标
$^:规则中所有的依赖
只能在规则的命令中使用,也即只能写在第二行,第一行不能加这些符号
-
makefile中自己维护的变量
格式一般都是大写CC= gcc(就是gcc)
CPPFLAGS: 预处理器需要的选项 如 :-I
CFLAGS: 编译的时候使用的参数 -Wall -g -c
LDFALGS: 链接库使用的选项 -L -l
版本四&&makefile 中的函数
所有makefile中的函数都有返回值,我们对makefile函数的调用也是为了获取它的返回值。
-
获取指定目录下.c文件
src=$(wildcard ./*.c) (函数名是wildcard,是查找函数,参数是查找指定目录下的指定文件)
obj= ( p a r t s u b s t . / (partsubst ./%.c,./%.o, (partsubst./(src))(partsubst是匹配替换函数,把所有的.c匹配替换为.o,.c来源于第三个参数$(src))(参数之间有,)
-
新定义规则,删除.o (可以不加依赖)
(.PHONY:clean)(添加该句是为了指定clean为伪目标,在执行时不会与磁盘内内容比较,即便本地磁盘有clean文件,执行make clean时不会产生影响)
clean:
rm $(obj) $(target) -f(该参数表示强制删除,无需提醒文件是否存在)
操作方法:make + 目标 make clean
如果直接make ,默认生成终极目标
细节补充
在命令前加上-,可以在命令执行失败时跳过该命令,继续向下执行,比如makedir /aa(根目录下创建aa,则此时必然没有权限执行,加上-即可忽略)
系统IO函数
C库函数/文件描述符
- 通过fopen获取文件指针,用以后续操作 。
- 文件指针包含三部分内容:
- 文件描述符FD(整型值) 用于索引到对应的磁盘文件,比如fopen打开一个文件,必然需要传一个文件的名字,而这个文件名字对应于磁盘上某个位置,用文件描述符去找到。
- 文件读写位置指针FP_POS:指向文件的起始位置,当写入一些数据时,文件指针会向后移动。写入内容完毕后,重置指针位置才能读取到文件。用
fseek
。时刻注意文件读写位置指针的位置。 - I/O缓冲区 BUFFER: 是一块内存,保存的是内存的地址,内存地址的作用是通过内存指针找到对应的指针块,然后在里面保存一些内容。缓冲区默认大小是:8KB。指定缓冲区的目的是减少对硬盘的操作次数。读取的内容先放到缓冲区,等缓冲区满,再放入硬盘。
- 缓冲区内容刷到硬盘上三种方法:
- 强制刷新缓冲区内容到硬盘上:fflush
- 缓冲区已满
- 正常关闭文件
>.fclose
>.return(main函数)
exit(main函数)
- 注意linux系统函数没有I/O缓冲区,要操作需要用户提供,而C库函数则在内部封装了这块缓存,为了提高用户的效率
虚拟地址空间&&文件描述符
每个程序执行起来之后,那么就相当于执行了一个进程。系统都会为其分配一个虚拟地址空间。32位系统分配的是2^32是4G的空间。分为两部分。
- 用户区:0~3G 归程序员使用。
- Linux内核区:3G~4G 。
Linux内核区&&PCB
访问不到,不允许用户操作。而文件描述符就位于这个区。内核区含PCB进程控制块,其中就有文件描述符表(本质是数组,0~1023
,每个位置分别表示打开一个文件,也即一个进程最多打开1024-3个文件,因为0~2
默认被占用,标准输入,标准输出,标准错误)。
用户区
打开了一个可执行文件a.out,系统分配0~3G内存空间,Linux下可执行文件格式ELF,通过file app可以查看可执行文件信息。
-
ELF含三部分主要内容
- .text代码段,二进制机器指令,存放源代码。
- .data 放的是已初始化全局变量。
- .bss 放的是未初始化全局变量。注:未初始化全局变量等于0。局部变量未初始化就不好说了。
- 其他端包括有:只读数据段,符号段等。
- CPU执行时,先从main函数的代码段开始, 发现局部变量,对应栈空间,向下生长,新的去下面。如果发生了new和malloc,则从堆上分配内存,由下往上生长。加载的fread和fseek等来源于.so文件,其在被调用时被放入动态库/共享区,起始位置不定,故采用的是相对地址。而代码段放置的是绝对地址,不会变。
-
命令行参数:包括有int main(int argc,char* argv[])
- 环境变量: env命令
-
ELF段下面还有一个0-4K的受保护地址。(不允许用户访问,例如#define NULL (void*) 0 指的就是这段地址 )
虚拟地址空间所解决的问题
内存里的空间是断断续续的。借助虚拟内存做一个映射,实现内存连续性。
- 1.系统安排程序的地址分布
程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲 区 - 2.方便进程之间隔离
不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另ー进程使用的物理内存。 - 3.方便OS使用你那可怜的内存
程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时, 内存管理器会将物理内存页(通常大小为4KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
虚拟地址空间实际上是使用多少才实际上少多少空间,而非已经开辟了4g。只有当它实际的数据是4g时才会少于4g。
C库函数与系统函数的关系
用户使用的printf并不能直接操作硬件,必须一层层调用。系统API也分为三层。一层层调用。
open
man man
发现第二章是关于系统函数。查看第二章内容: man 2 open
发现有三个参数。
open(const char *pathname,int falgs)
open(const char *pathname,int flags,mode_t mode)。(flags指打开方式,mode指给创建的文件指定访问权限)。
提示man文档有很多奇怪的数据类型,如size-t,来源于早期unix的数据类型,也是int类型。
open函数的返回值是文件描述符(每打开一个新的文件,就占用一个位置)或者是-1,返回-1后errno(errornumber)这个全局的变量,会被赋一个值。当函数被调用失败后,这个值对应的是它的错误信息。
open函数的使用
-
先是三个头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;//打开已经存在的文件
fd=open();//不知道怎么用某个函数,光标放其上。shift+k就可查询对应man文档 ,shift 2 K。
fd=open("hello.c",O_RDWR | O_CREAT,0777);
}
-
FLAGS的例子:
-
O_CREAT
文件权限:本地有一个掩码
umask
查看umask 022
就可以修改本地掩码为022,实际上这是对文件权限的保护文件的实际权限=给定的权限与本地掩码取反后的按位与操作
-
O_ERCL
与O_CREAT一起使用,判断文件是否存在。以实现文件存在与不存在状态下执行不同的命令。fd=open(“xxx”,O_RDWR| O_CREAT| O_ERCL)
-
O_TRUNK
将文件截断为0。fd=open(“xxx”,O_RDWR|O_TRUNK)。清除内容。
-
记得判断返回值。
errno
- 定义在头文件errno.h中,在/usr/include/errno.h。进入后,/errrno 就能查找,所有的都高亮显示。按n键向下翻找,看到extern int errno,说明是全局变量。则,今后再用errno时,只需包含errno.h即可。
- 全局变量
- 任何标准C库函数都能对其修改(Linux系统函数更可以)
- 错误宏定义位置
- 第1-34个错误定义:/usr/…
- 第35-133个错误定义
- 是记录系统的最后一次错误代码,代码是一个int型的值
perror
能返回 error对应整形值的错误描述。
- 函数定义:void perror(const char *s)
read
size_t是unsigned int ,ssize_t是signed int。(因为有时候要返回-1)
-
头文件是#include<unist.h>
-
返回值
-1 读文件失败
0 文件读完
>0
读取的字节数 相同与fread
write
-
头文件是#include<unist.h>
-
参数:ssize_t write(int fd,const void *buf,size_t count);
const void *表示第二个参数,缓冲区必须要有内容,写到磁盘上一个位置,count是buffer的字节。
lseek
-
功能:移动文件指针/获取文件长度
-
头文件:#include <sys/types.h>
#include<unistd.h>
-
off_t lseek(int fd,off_t offset, int whence)
fd是文件描述符。也即要先打开文件才能用lseek
offset是指文件指针的偏移量。whence和文件指针一样,也是三个宏。
- SEEK_SET
- SEEK_CUR
- SEEK_END