GCC
gcc -E a.c -o a.i 预处理
gcc -S a.c -o a.s 编译
gcc -c a.c -o a.o 汇编
gcc a.c -o a.out
gcc 和 g++ 区别
- 后缀为 .c 的,gcc 把它当作是 C 程序,而 g++ 当作是 c++ 程序
- 后缀为 .cpp 的,两者都会认为是 C++ 程序
- 编译阶段(链接之前统称编译阶段),g++ 会调用 gcc,对于 C++ 代码,两者是等价的,但是因为 gcc 命令不能自动和 C++ 程序使用的库联接,所以通常用 g++ 来完成链接
gcc test.cpp -c会生成.o文件.
gcc test.cpp会报错,无法自动连接库 stdc++
gcc -lstdc++ test.cpp正确
gcc编译选项
-D选项 g++ a.cpp -D DEB
int main(){
#ifdef DEB
printf("deb\n");
#endif
cout<< "hello"<<endl;
return 0;
}
静态库的制作
静态库在程序的链接阶段被复制 到了程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加 载到内存中供程序调用。
库的好处:1.代码保密 2.方便部署和分发
命名规则:
Linux : libxxx.a
Windows : libxxx.lib
静态库的制作:
- gcc 获得 .o 文件
- 将 .o 文件打包,使用 ar 工具(archive)
ar rcs libxxx.a xxx.o xxx.o
使用
gcc main.c -I ./include/ -l xxx -L ./lib
-I 指定头文件目录
-L 指定库文件目录
-l 指定链接的静态库, 名字不带前后缀
优缺点
优点:
- 静态库被打包到应用程序中加载速度快
- 发布程序无需提供静态库,移植方便
缺点: - 消耗系统资源,浪费内存
- 更新、部署、发布麻烦
动态库的制作
命名规则
Linux : libxxx.so
Windows : libxxx.dll
制作
gcc -c –fpic/-fPIC a.c b.cgcc -shared a.o b.o -o libcalc.so-fpic 生成的代码没有绝对地址只有相对地址
如果有同名的,有比如libxxx.a和libxxx.so同时存在,链接.so
链接之后.so放哪里无所谓,让ld程序找到就行(配置环境变量)
查看程序依赖的动态库
root@aliyun:# ldd a.out
配置环境变量
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路 径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是 由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 ——> 环境变量 LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib/,/usr/lib 目录找到库文件后将其载入内存。
DT_RPATH段 虚拟地址空间的,操作不了
LD_LIBRARY_PATH
修改配置文件,追加export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:real_path
用户级别 root@aliyun:# vim ~/.bashrc文件
系统级别 root@aliyun:# vim /etc/profile 文件
.bashrc 实际是一个shell程序, 登录时执行, 会执行我们写的export
/etc/ld.so.cache文件列表
root@aliyun:# vim /etc/ld.so.conf 直接追加real_path
root@aliyun:# ldconfig 更新
/lib/,/usr/lib
在这两个目录放入.so; 不建议,容易和系统的冲突
优缺点
优点:
可以实现进程间资源共享(共享库)
更新、部署、发布简单
可以控制何时加载动态库
缺点:
加载速度比静态库慢
发布程序时需要提供依赖的动态库
IO
标准C库的函数是可以跨平台的,根据不同平台去实现,内部调用不同系统的api
所有标准C库的IO函数效率高于linux系统api,因为带有缓冲区
文件描述符具体原理
struct task_struct{
...
//指向所谓的文件描述符表
struct files_struct* files;
...
}
struct files_struct{
atomic_t count; //引用计数
struct fdtable* fdt;
struct fdtable fdtab;
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;
struct embedded_fd_set close_on_exec_init;
struct embedded_fd_set open_fds_init;
//所谓的文件描述符表, 文件描述符实际就是数组索引
struct file* fd_array[NR_OPEN_DEFAULT];
}
//文件表结构体, 对应一个iNode节点, 一个iNode节点对应磁盘上的一个文件
struct file{
mode_t f_mode; //操作文件的权限(是否可读或可写)
dev_t f_rdev; //用于/dev/tty
off_t f_ops;//当前文件位移
unsigned short f_flags;//文件标志: O_RDONLY,O_NONBLOCK,0_SYNC
unsigned short f_count;//打开的文件数目
unsigned short f_readda;
struct inode* f_inode; //指向iNode的结构指针
struct file_operations* f_op; //文件操作索引指针
};
虚拟地址空间
fopen
fopen 返回的是一个指向FILE结构体的指针
内核会维护进程的文件描述符表, fd对应一个文件描述结构体
Linux io函数
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
- flags:对文件的操作权限和其他的设置
- 必选项:O_RDONLY, O_WRONLY, O_RDWR 这三个之间是互斥的
- 可选项:O_CREAT 若文件不存在,创建新文件
- 按位或
- mode:八进制的数,表示创建出的新的文件的操作权限,比如:0775
- 最终的权限是:mode & ~umask 结果就是相减,比如0777 & 0002 = 0775
int close(int fd);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);
- offset:偏移量(字节)
- whence:
- SEEK_SET 设置偏移量:开头位置 + 第二个参数offset的值
- SEEK_CUR 设置偏移量:当前位置 + 第二个参数offset的值
- SEEK_END 设置偏移量:文件大小 + 第二个参数offset的值
- 返回值:返回文件指针的位置
- 常用:
1.移动文件指针到文件头 lseek(fd, 0, SEEK_SET);
2.获取当前文件指针的位置 lseek(fd, 0, SEEK_CUR);
3.获取文件长度 lseek(fd, 0, SEEK_END);
4.拓展文件的长度 lseek(fd, 100, SEEK_END)
- 注意:需要写一次数据(不然不会扩展)
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
stat和lstat的区别:当文件是一个符号链接时,lstat返回的是该符号链接本身的信息;
而stat返回的是该链接指向的文件的信息。
struct stat {
dev_t st_dev; // 文件的设备编号
ino_t st_ino; // 节点
mode_t st_mode; // 文件的类型和存取的权限
nlink_t st_nlink; // 连到该文件的硬连接数目
uid_t st_uid; // 用户ID
gid_t st_gid; // 组ID
dev_t st_rdev; // 设备文件的设备编号
off_t st_size; // 文件字节数(文件大小)
blksize_t st_blksize; // 块大小
blkcnt_t st_blocks; // 块数
time_t st_atime; // 最后一次访问时间
time_t st_mtime; // 最后一次修改时间
time_tst_ctime; // 最后一次改变时间(指属性)
};
stat 结构体中的 st_mode 变量 (文件的类型和存取的权限)
文件属性操作函数
int access(const char *pathname, int mode);
- 作用:判断某个文件是否有某个权限,或者判断文件是否存在
- 参数:
- pathname: 判断的文件路径
- mode:
- R_OK: 判断是否有读权限
- W_OK: 判断是否有写权限
- X_OK: 判断是否有执行权限
- F_OK: 判断文件是否存在
- 返回值: 成功返回0, 失败返回-1
int chmod(const char *filename, int mode);
int chown(const char *path, uid_t owner, gid_t group);
int truncate(const char *path, off_t length);
目录操作函数
int rename(const char *oldpath, const char *newpath);
int chdir(const char *path);
char *getcwd(char *buf, size_t size);
- buf : 存储的路径,指向的是一个数组(传出参数)
- size: 数组的大小
- 返回值:buf
int mkdir(const char *pathname, mode_t mode);
int rmdir(const char *pathname);
目录遍历函数
目录流理解为一个流, 有读写指针(实际就是offset)
每readdir()一次读写指针后移
每次readdir()的单位是一个文件信息(struct dirent)
DIR *opendir(const char *name);
- name: 需要打开的目录的名称
- 返回值:
- DIR *类型,理解为目录流
- 错误返回NULL
struct dirent *readdir(DIR *dirp);
- 参数:dirp是opendir返回的结果, 目录流
- 返回值:
- struct dirent *,代表读取到的文件的信息
- 读取到了末尾或者失败了,返回NULL
- 执行成功向下走一个
int closedir(DIR *dirp);
long telldir(DIR *dirp);//返回当前目录流读写指针的位置
void seekdir(DIR *dirp, long loc);//修改当前目录流读写指针的位置
void rewinddir(DIR *dirp); //将目录流读写指针归零
dirent 结构体和 d_type
struct dirent{
// inode
ino_t d_ino;
// 目录文件开头至此点的位移(读写指针的位置)
off_t d_off;
// d_name 的长度, 不包含NULL字符
unsigned short int d_reclen;
// d_name 所指的文件类型
unsigned char d_type;
// 文件名
char d_name[256];
}
/*
d_type
DT_BLK - 块设备
DT_CHR - 字符设备
DT_DIR - 目录
DT_LNK - 软连接
DT_FIFO - 管道
DT_REG - 普通文件
DT_SOCK - 套接字
DT_UNKNOWN - 未知
*/
// 读取某个目录下所有的普通文件的个数
int main(int argc, char * argv[]) {
if(argc < 2) {
printf("%s path\n", argv[0]);
return -1;
}
int num = getFileNum(argv[1]);
printf("普通文件的个数为:%d\n", num);
return 0;
}
int getFileNum(const char * path) {
DIR * dir = opendir(path);// 1.打开目录流
if(dir == NULL) {
perror("opendir");
exit(0);
}
struct dirent *ptr;
int total = 0; //普通文件个数
while((ptr = readdir(dir)) != NULL) {
char * dname = ptr->d_name;// 获取名称
if(!strcmp(dname, ".") || !strcmp(dname, "..")) {
continue;// 忽略掉. 和..
}
if(ptr->d_type == DT_DIR) { //判断是否是普通文件还是目录
// 目录,需要继续读取这个目录
char newpath[256];
sprintf(newpath, "%s/%s", path, dname); //拼接目录
total += getFileNum(newpath);
}
if(ptr->d_type == DT_REG) {
total++; // 普通文件
}
}
closedir(dir);
return total;
}
dup、dup2、fcntl 函数
int dup(int oldfd); //复制文件描述符
- 作用:复制一个新的文件描述符
int fd1 = dup(fd),
通过dup的fd1 和 fd 指向同一个文件描述结构体,共享读写指针
int dup2(int oldfd, int newfd); //重定向文件描述符
- 调用函数成功后:newfd 指向 oldfd 所指的文件描述结构体
- newfd的值是不变的
- 返回值就是newfd,失败-1
- oldfd 必须是一个有效的文件描述符
int fcntl(int fd, int cmd, ... /* arg */ );
- 作用: 复制文件描述符 设置/获取文件的状态标志
- 参数:
- fd : 表示需要操作的文件描述符
- cmd: 表示对文件描述符进行如何操作
- F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
- int ret = fcntl(fd, F_DUPFD);
- F_GETFL : 获取指定的文件描述符文件状态flag
- 获取的flag和我们通过open函数传递的flag是一个东西。
- F_SETFL : 设置文件描述符文件状态flag
- 必选项:O_RDONLY, O_WRONLY, O_RDWR 三选一
- 可选性:O_APPEND, O_NONBLOCK
- O_APPEND 表示追加数据
- O_NONBLOK 设置成非阻塞
Makefile
文件命名 makefile 或者 Makefile
Makefile规则
一个 Makefile 文件中可以有一个或者多个规则
目标 ...: 依赖 ...
命令(Shell 命令) ...
app:main.c a.c b.c
gcc -c main.c a.c b.c
Makefile 中的其它规则一般都是为第一条规则服务的。默认只会执行第一条规则,如果需要执行后边无关的规则,需要我们指定(make + 规则的目标)可以指定执行某条规则 比如
$: make clean
更新规则
命令在执行之前,需要先检查规则中的依赖是否存在
- 如果存在(也要检查更新),执行命令
- 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令 检测更新: 在执行规则中的命令时,会比较目标和依赖文件的时间
- 如果依赖的时间比目标的时间晚,需要重新生成目标
- 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
自定义变量
变量名=变量值 var=hello
Makefile中没有数据类型,都是字符串的形式
查看变量的值 $(变量名)
预定义变量
AR : 归档维护程序的名称,默认值为ar
CC : C 编译器的名称,默认值为cc
CXX : C++ 编译器的名称,默认值为g++
$@ : 目标的完整名称
$< : 第一个依赖文件的名称
$^ : 所有的依赖文件
makefile
src=$(patsubst %.c, %.o, $(wildcard *.c))
target=app
$(target):$(src)
$(CC) $^ -o $@ -I include
%.o:%.c
$(CC) $< -c -o $@ -I include
.PHONY:clean
clean:
rm $(src) -f
$(wildcard PATTERN...)函数
功能:获取指定目录下指定类型的文件列表
参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,用空格间隔
返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
示例: $(wildcard *.c ./sub/*.c)
返回值格式: a.c b.c c.c d.c e.c f.c
$(patsubst <pattern>,<replacement>,<text>) 函数
功能:查找<text>中的单词(单词以“空格”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。
<pattern>可以包括通配符%,表示任意长度的字串。
如果<replacement>中也包含%,那么,<replacement>中的这个%也是<pattern>中的那个%所代表的字串。(可以用\来转义,以\%来表示真实含义的%字符)
返回:函数返回被替换过后的字符串 示例:
$(patsubst %.c, %.o, x.c bar.c)
返回值: x.o bar.o
伪目标
.PHONY:clean
clean:
rm *.o -f
伪目标不会生成文件,这样防止目录中有一个clean文件,而会认为clean不需要更新(因为没有依赖)从而不执行规则
root#: make clean
gdb
gcc -g -Wall program.c -o program
-g 选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb能找到源文件。
启动和退出
root#: gdb a.out
(gdb) q
给程序设置参数/获取设置参数
(gdb) set args 10 20
(gdb) show args
查看当前文件代码
(gdb) l (从默认位置显示)
(gdb) l 行号 (从指定的行显示)
(gdb) l 函数名(从指定的函数显示)
查看非当前文件代码
(gdb) l 文件名:行号
(gdb) l 文件名:函数名 文件名都是可以带路径的
设置显示的行数
(gdb) show list/listsize
(gdb) set list/listsize 行数
设置断点
(gbd) b 行号
(gdb) b 函数名
(gdb) b 文件名:行号
(gdb) b 文件名:函数
查看断点
(gdb) i b
删除断点
(gdb) d 断点编号
设置断点无效
(gdb) dis 断点编号
设置断点生效
(gdb) ena 断点编号
设置条件断点(一般用在循环的位置)
(gdb) b 10 if i==5
运行GDB程序
(gdb) start(程序停在第一行)
(gdb) run(遇到断点才停)
继续运行,到下一个断点停
(gdb) c
向下执行一行代码(不会进入函数体)
(gdb) n
变量操作
(gdb) p 变量名(打印变量值)
(gdb) ptype 变量名(打印变量类型)
向下单步调试(遇到函数进入函数体)
(gdb) s
(gdb) finish(跳出函数体,函数体不能有断点)
自动变量操作
(gdb) display 变量名(自动打印指定变量的值)
(gdb) i/info display
(gdb) undisplay 编号
其它操作
(gdb) set var 变量名=变量值 (循环中用的较多)
(gdb) until (跳出循环, 循环中不能有断点)
core
ulimit -a 查看core打开没有
ulimit -c unlimited 默认core文件大小是0
gcc core.c -o coreapp -g
./coreapp
gdb coreapp
(gdb) core-file core 会显示程序崩溃信息