1. gdb调试
2. makefile的编写
3. 系统IO函数
1. gdb调试: 0. 编译带调试的程序
gcc *.c -o app -g
1. 启动gdb gdb app
start -- 只执行一步
n -- next
s -- step(单步) -- 可以进入到函数体内部(l显示函数内部代码)
c - continue -- 直接停在断点的位置
2. 查看代码:
l -- list(列出包括main函数的文件)
l 10(函数名)
l filename:行号(函数名)
3. 设置断点:
设置当前文件断点:
b(简写) -- break
break 10(函数名)
break fileName:行号(函数名)
设置指定文件断点:
设置条件断点:
break 10 if value==19
删除断点:
delete -- del - d
delete 断点的编号(info获取)
获取断点信息: info -- i(简写)
info break
4. 查看设置的断点 info
5. 开始 执行gdb调试
执行一步操作:
继续执行:
执行多步, 直接停在断点处:
5. 单步调试
进入函数体内部: s(step)
从函数体内部跳出: finish(把断点删除之后可以直接跳出,否则要等循环结束才能跳)
不进入函数体内部:
n(next)
退出当前循环: u
6. 查看变量的值: p -- print
7. 查看变量的类型: ptype 变量名
8. 设置变量的值: set var 变量名 = 赋值
9. 设置追踪变量
display + 变量
取消追踪变量 undisplay + 编号(通过info 获取编号)
获取编号: info display
10. 退出gdb调试
quit
2. makefile的编写:
1. makefile(一般写一些代码要对他们进行整合,这样编译起来就比较轻松,否则会很麻烦)
2. makefile的规则:(在同一级目录下)
规则中的三要素: 目标, 依赖, 命令
(1)目标:依赖条件 例如:app:main.c add.c sub.c
命令 gcc main.c add.c sub.c -o app(缩进)
(2) 第一个每次都要重新编译,比较繁琐,我改一个也要都重新编译。
app:main.o add.o sub.o
gcc main.o add.o sub.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
(3)替换:
obj=main.o add.o sub.o(在同一级目录下)
target=app
$(target):$(obj)
gcc(可以替换为$(CC)) $(obj) -o $(target)
%.o:%.c
gcc -o $< -o $@
子目标和终极目标的关系:附属关系。
更新目标的原则:根据.c和.o的时间来对比。
3. makefile的两个函数
上面obj定义的,如果程序太多的话,这样手写会比较麻烦,所以需要一个函数来代替
wildcard
src=$(wildcard ./*.c)(找出当前目录下的所有.c文件)
patsubst
obj=$(patsubst ./%.c,./%.o)(将当前目录下的.c换成.o文件)
#obj=main.o add.o sub.o(在同一级目录下)(注释起来)
src=$(wildcard ./*.c)
obj=$(patsubst ./%.c,./%.o,$(src))(.c是src下拿的)
target=app
$(target):$(obj)
gcc(可以替换为$(CC)) $(obj) -o $(target)
%.o:%.c
gcc -o $< -o $@
clean:
rm $(obj) $(target)
4. makefile的三个自动变量
app:main.o sub.o mul.o
gcc main.o sub.o mul.o -o app
gcc $^ -o $@(和上面的一样)
%.o:%.c(%相当于main或sub表示泛指)
gcc -c $< -o $@
makefile中的自动变量
$<: 规则中的第一个依赖
$@: 规则中的目标
$^: 规则中的所有依赖
只能在规则的命令中使用;
3. 系统IO函数
1>. 一些概念
文件描述符
PCB
C库函的IO缓冲区
2>. open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdlib.h>
#include<stdio.h>
int main()
{
int fd;
fd=open("bucuns",O_RDWR);//(文件不存在)//对存在的函数操作用这个
if(fd==-1)
{
perror("open file");
exit(1);
}
fd=open("bucuns",O_RDWR|O_CREATE,0777);//(文件不存在)//创建函数用这个
if(fd==-1)
{
perror("open file");
exit(1);
}
printf("%d",fd);
//关闭文件
close(fd);//fd文件描述符;
}
打开方式:
必选项:
O_RDONLY
O_WRONLY
O_RDWR
可选项:umask(看掩码)
O_CREAT
文件权限: 本地有一个掩码
文件的实际权限:
给定的权限
本地掩码(取反)
&
实际的文件权限
777
111111111
111111101
111111101
775
O_TRUNC 清空文件里面的内容
O_EXCL O_RDWR|O_CREATE|O_EXCL //判断创建的文件是否存在
O_APPEND
3>. read
返回值:
1. -1 读文件失败
2. 0 文件读完了
3. >0 读取的字节数
4>. write
返回值: 文件大小;(2 K)直接查看linux下系统函数
5>. lseek
返回值:
6>. close
返回值:
cpu 为什么要使用虚拟地址空间与物理地址空间映射?解决了什么样的问题?
1.方便编译器和操作系统安排程序的地址分布。
程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
2.方便进程之间隔离
不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程使用的物理内存。
3.方便OS使用你那可怜的内存。
程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,
内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
大文件
两种方式:
read write -- 每次读1个byte
getc putc -- 每次读一个byte
两种方式, 哪个效率高