1.安装命令 sudo apt install gcc g++
查看版本 gcc/g++ -v/–version
2.GCC常用参数选项
gcc编译选项 | 说明 |
---|---|
-E | 预处理指定的源文件,不进行编译 |
-S | 编译指定的源文件,但是不进行汇编 |
-c | 编译、汇编指定的源文件,但是不进行链接 |
-o [file1] [file2] / [file2] -o [file1] | 将文件 file2 编译成可执行文件 file1 |
-I directory | 指定 include 包含文件的搜索目录 |
-g | 在编译的时候,生成调试信息,该程序可以被调试器调试 |
-D | 在程序编译的时候,指定一个宏 |
-w | 不生成任何警告信息 |
-Wall | 生成所有警告信息 |
-On | n的取值范围:0~3。编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高 |
-l | 在程序编译的时候,指定使用的库 |
-L | 指定编译的时候,搜索的库的路径 |
-fPIC/fpic | 生成与位置无关的代码 |
-shared | 生成共享目标文件,通常用在建立共享库时 |
-std | 指定C方言,如:-std=c99,gcc默认的方言是GNU C |
3.在编译阶段,g++ 会调用 gcc,对于 C++ 代码,g++ 和 gcc 是等价的,但是因为 gcc 命令不能自动和 C++ 程序使用的库链接,所以通常用 g++ 来完成链接。
编译可以用 gcc/g++,而链接可以用 g++ 或者 gcc -lstdc++。
编译可执行文件test01:g++ test01.cpp -o test01
4.静态库在程序的链接阶段被复制到程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态库加载到内存中供程序调用。
5.静态库的制作:
gcc -c file1.c file2.c file3.c
ar rcs libxxx.a file1.o file2.o file3.o
6.查看文件目录树的命令是tree
sudo apt install tree
7.静态库的使用:
要头文件(放在include目录下)和libcalc.a文件(放在lib目录下)。
gcc main.c -o app -I ./include -l calc -L ./lib
注:calc是库的名字,libcalc.a是库文件的名字。
8.动态库的制作:
在Linux下,动态库是一个可执行文件。
9.动态库的使用:
gcc main.c -o app -I ./include -L ./lib -l calc
动态库可以实现进程间资源共享。两个进程加载同一个动态库,共享一份内存空间,但共享的是代码段,并不是数据段。
方式一:配置环境变量(临时的):
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/bread/testcode/lib
echo $LD_LIBRARY_PATH
方式二:配置环境变量(永久的,用户级别):
vim .bashrc
最后一行加上export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/bread/testcode/lib
保存退出后 source .bashrc
方式三:配置环境变量(系统级别):
sudo vim /etc/profile
最后一行加上export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/bread/testcode/lib
保存退出后 source /etc/profile
方式四:间接配置/etc/ld.so.cache文件列表的方式:
sudo vim /etc/ld.so.conf
最后一行加上/home/bread/testcode/lib
保存退出后 sudo ldconfig
10.Makefile文件命名和规则:
例子:
vim Makefile
app:file1.c file2.c file3.c
gcc file1.c file2.c file3.c -o app
sudo apt install make
make
11.Makefile工作原理:
app:file1.o file2.o file3.o
gcc file1.o file2.o file3.o -o app
file1.o:file1.c
gcc -c file1.c -o file1.o
file2.o:file2.c
gcc -c file2.c -o file2.o
file3.o:file3.c
gcc -c file3.c -o file3.o
12.Makefile中的变量:
模式匹配:
app:file1.c file2.c file3.c
gcc -c file1.c file2.c file3.c
# 自动变量只能在规则的命令中使用
app:file1.c file2.c file3.c
$(CC) -c $^ -o $@
#定义变量
src=file1.o file2.o file3.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
13.Makefile中的函数:
%
,表示任意长度的字串。如果中也包含%
,那么,中的这个%
将是中的那个%所代表的字串。(可以用\
来转义,以\%
来表示真实含义的%
字符)#定义变量
#获取file1.c file2.c file3.c
src=$(wildcard ./*.c)
objs=$(patsubst %.c, %.o, $(src))
target=app
$(target):$(objs)
$(CC) $(objs) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
.PHONY:clean
clean:
rm $(objs) -f
删除.o文件:make clean
14.GDB调试:
-O
), 并打开调试选项(-g
)。另外,-Wall
在尽量不影响程序行为的情况下选项打开所有warning,也可以发现许多问题,避免一些不必要的BUG。-g
选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时才能保证 gdb 能找到源文件。15.GDB命令 – 启动、退出、查看代码:
gcc test.c -o test -g
ll -h test // test加了编译选项-g后,编译的可执行文件变大
gdb test
(gdb) set args 10 20
(gdb) show args
(gdb) list // 输入list(或者l)默认显示源文件test.c的前10行
(gdb) list // 输入list(或者l或者直接回车)继续往下显示源文件test.c
(gdb) help
(gdb) help all
(gdb) help set
(gdb) quit
16.GDB 命令 – 断点操作
g++ main.cpp -o main -g
gdb main
(gdb) break 9 // 在第9行打断点,但是第9行还没有执行的
(gdb) info break // 查看断点信息
(gdb) break main // 在main函数所在行打断点
(gdb) delete 2 // 删除编号为2的断点
(gdb) break 10 if i==5 // 在第10行当i==5的时候断点
17.GDB 命令 – 调试命令
g++ main.cpp -o main -g
gdb main
(gdb) start // 相当于默认在第一行打了断点,start之后直接停在了第一行
(gdb) c // 第一行后面都没有设置断点,因此c之后直接运行完了程序
(gdb) break 9 // 在第9行打断点
(gdb) run // 停在第9行
(gdb) print i // 打印第9行变量i的值
(gdb) ptype i // 打印第9行变量i的类型
(gdb) set var i=1 // 设置变量i的值等于1
18.文件IO
Linux系统IO函数是没有缓存区的,不同于标准C库的IO函数。
在终端输入 man 2 open 查看Linux库的帮助文档。
在终端输入 man 3 fopen 查看标准C库的帮助文档。
Linux 系统 IO 函数
int open(const char *pathname, int flags);
打开一个已经存在的文件.
flags是打开文件后对文件的操作权限.
int open(const char *pathname, int flags, mode_t mode);
创建一个新的文件.
mode是文件的权限属性,但最终权限是 mode&~umask .
可以在终端直接输入umask看当前用户的umask的值。
umask的作用就是抹去某些权限。
int fd = open(“a.txt”, O_RDWR | O_CREAT, 0777);
普通用户的话,得到a.txt的权限一般是0775,因为umask一般是0002。
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-设置文件指针的偏移量、SEEK_CUR-当前位置+offset的偏移量、SEEK_END-文件大小+offset的偏移量。
作用:
移动文件指针到文件头部 lseek(fd, 0, SEEK_SET);
获取当前文件指针的位置 off_t curIndex = lseek(fd, 0, SEEK_CUR);
获取文件长度 off_t len = lseek(fd, 0, SEEK_END);
拓展文件的长度 lseek(fd, nLen, SEEK_END); 最后还要写入数据,文件的长度才会变长。
int stat(const char *pathname, struct stat *statbuf);
获取文件的一些属性信息。
也可以直接在终端输入 stat a.txt 查看文件的信息。
int lstat(const char *pathname, struct stat *statbuf);
获取软链接文件的一些属性信息。
直接在终端输入创建软连接 ln -s a.txt b.txt
如果用stat b.txt获取到的是a.txt的文件信息。
文件权限
文件属性操作函数
int access(const char *pathname, int mode);
作用:判断某个文件是否有某个权限或者判断文件是否存在。
mode:R_OK判断是否有读权限、W_OK判断是否有写权限、X_OK判断是否有执行权限、F_OK判断文件是否存在。
int chmod(const char *filename, int mode);
作用:修改文件的权限。
int chown(const char *path, uid_t owner, gid_t group);
作用:修改文件的所在组或所有者。
查看用户id:vim /etc/passwd
查看组id:vim /etc/group
或者查看id用:id xiaoming
int truncate(const char *path, off_t length);
作用:对文件尺寸缩减或者扩展至指定大小。
length:需要最终文件变成的大小。
目录操作函数
一般来说,Web服务器的逻辑根目录并非文件系统的根目录“/”,而是站点的根目录(对于Linux的Web服务来说,该目录一般是/var/www/).
int rename(const char *oldpath, const char *newpath);
int chdir(const char *path);
作用:修改进程的工作目录。
char *getcwd(char *buf, size_t size);
作用:获取进程当前的工作目录。
返回值:返回的其实就是第一个参数。
int mkdir(const char *pathname, mode_t mode);
作用:创建一个目录。
int rmdir(const char *pathname);
作用:删除一个空目录。
int chroot(const char* path);
path参数指定要切换到的目标根目录。该函数并不改变进程的当前工作目录。此外,只有特权进程才能改变根目录。
目录遍历函数
DIR *opendir(const char *name);
作用:打开一个目录,返回目录流。
struct dirent *readdir(DIR *dirp);
作用:读取目录中的数据,返回读取到的文件的信息。如果读取到了末尾或者失败了,返回NULL。
int closedir(DIR *dirp);
dup和dup2函数
int dup(int oldfd);
复制得到一个新的文件描述符(从空闲的文件描述符表中找一个最小的),与oldfd指向同一个文件。
int dup2(int oldfd, int newfd);
重定向文件描述符。调用函数成功后,newfd close掉原来它所指向的文件,然后指向和oldfd所指向的文件。
返回值和newfd的值相同。
注意:通过dup和dup2创建的文件描述符并不继承原文件描述符的属性,比如close-on-exec和non-blocking等。
readv函数和writev函数
sendfile函数
splice函数
tee函数
fcntl 函数
其他
int fd = open("a.txt", O_RDONLY);
if(-1 == fd)
perror("Error"); //如果没有这个文件,会输出Error:No such file or directory
19.模拟实现ls -l a.txt命令
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
if(argc < 2) // 判断输入的参数是否正确
{
printf("%s filename\n", argv[0]);
return -1;
}
// 通过stat函数获取用户传入的文件的信息
struct stat st;
int ret = stat(argv[1], &st);
if(ret == -1)
{
perror("stat");
return -1;
}
// 获取文件类型和文件权限
char perms[11] = {0}; // 用于保存文件类型和文件权限的字符串
switch(st.st_mode & S_IFMT) {
case S_IFLNK:
perms[0] = 'l';
break;
case S_IFDIR:
perms[0] = 'd';
break;
case S_IFREG:
perms[0] = '-';
break;
case S_IFBLK:
perms[0] = 'b';
break;
case S_IFCHR:
perms[0] = 'c';
break;
case S_IFSOCK:
perms[0] = 's';
break;
case S_IFIFO:
perms[0] = 'p';
break;
default:
perms[0] = '?';
break;
}
// 判断文件的访问权限
// 文件所有者
perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';
// 文件所在组
perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';
// 其他人
perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';
int linkNum = st.st_nlink; // 硬连接数
char * fileUser = getpwuid(st.st_uid)->pw_name; // 文件所有者
char * fileGrp = getgrgid(st.st_gid)->gr_name; // 文件所在组
long int fileSize = st.st_size; // 文件大小
char * time = ctime(&st.st_mtime); // 获取修改的时间
char mtime[512] = {0};
strncpy(mtime, time, strlen(time) - 1);
char buf[1024];
sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]);
printf("%s\n", buf);
return 0;
}