【提高】【Linux】【一】C++学习日记

gcc相关

工作流程

1、预处理 -E
预处理器(cpp)
       宏替换
       头文件展开
       注释去掉
       xxx.c -> xxx.i(C文件)
2、编译 -S
编译器(gcc)
       xxx.i->xxx.s(汇编文件)
3、汇编 -C
汇编器(as)
       xxx.s -> xxx.o(二进制文件)
4、链接
链接器(ld)
       xxx.0 -> xxx(可执行文件)

-o的是指明生成文件名字
gcc -E hello.c -o hello.i
gcc -S hello.i -o hello.s
gcc -C hello.s -o hello.o
gcc hello.o -o hello

gcc常用参数

-v/--version: 版本号
-I: 编译的时候指定头文件路径
-c: 生成二进制文件,得到.o文件
-o: 指定生成的文件名字
-g: gdb调试的时候需要加
-D: 编译的时候指定一个宏,测试程序时使用
-Wall: 添加警告提示信息
-On: O是优化代码,n是优化级别:1, 2, 3

库就是二进制文件
将源代码->二进制格式的源代码
相当于加密操作,别人能用但是不知道内部实现

静态库

命名规则:libxxx(库名).a 如libtest.a
制作步骤:1、原材料:源代码.c/.cpp
                   2、将.c文件生成.o
                   3、将.o文件打包,如

语法:ar rcs 静态库的名字 原材料
ar rcs libtest.a *.o
查看静态库内的文件:nm libtest.a

                   4、库放到lib目录中,将包含所有头文件的include目录和lib目录给用户
库的使用:gcc main.c -I ./include/ -L ./lib/ -ltest -o app

使用两个参数-L和-l
-L 指定库的路径
-l库的名字

动态库

命名规则:libxxx(库名).so 如libtest.so
制作步骤:1、原材料:源代码.c/.cpp
                   2、将.c文件生成.o
                   3、将.o文件打包,如

语法:gcc -shared -o 动态库的名字 原材料
gcc -shared -o libtest.so *.o

                   4、库放到lib目录中,将包含所有头文件的include目录和lib目录给用户
库的使用:gcc main.c -I ./include/ -L ./lib/ -ltest -o app
执行app可执行文件,出现错误,提示未找到库文件

错误原因

对于elf格式的可执行文件,是由ld-linux.so*完成的,它先后搜索elf文件的DT_RPATH段-------环境变量LD_LIBRARY_PATH------/etc/ld.so.cache 文件列表-------/lib//usr/libc目录找到库文件后将其载入内存

如何让系统找到动态库

1、临时设置,LD_LIBRARY_PATH:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
export LD_LIBRARY_PATH=库路径:$LD_LIBRARY_PATH

2、永久设置
     系统级别:将export放在/etc/profile中,也需要重启系统或者执行source /etc/profile命令
     用户级别:将export放在~/.bashrc中,需要重启终端或者执行source ~/.bashrc命令
3、更新/etc/ld.so.cache文件列表
将动态库的绝对路径写到/etc/ld.so.conf中,执行命令sudo ldconfig -v重新配置文件列表
4、函数调用调用动态库

dlopen
dlclose
dlsym

静态库,动态库优缺点

静态库

优点:1、静态库被打包到应用程序中加载速度快
           2、发布程序无需提供静态库,移植方便
缺点:1、销毁系统资源,浪费内存
           2、更新、部署、发布麻烦

动态库

优点:1、可实现进程资源共享
           2、程序升级简单
           3、程序员可以控制何时加载动态库
缺点:1、加载速度比静态库慢
           2、发布程序需要提供依赖的动态库

makefile

make:linux自带的构建器,构建规则在makefile中
makefile中由一条或多条规则组成
makefile文件命名makefile/Makefile
makefile编写规则:目标,依赖,命令

makefile第一个版本

语法规范:
目标:依赖
(tab缩进)命令

例如:gcc a.c b.c c.c -o app

app:a.c b.c c.c
	gcc a.c b.c c.c -o app

缺点:效率低,修改一个文件,所有文件会被全部重新编译

makefile第二个版本

app:a.o b.o c.o
	gcc a.o b.o c.o -o app
a.o:a.c
	gcc a.c -c
b.o:b.c
	gcc b.c -c
c.o:c.c
	gcc c.c -c

已解决问题:修改单个文件,该文件重新编译,其他文件不会
缺点:冗余

工作原理

1、检查依赖(文件)是否存在
      向下搜索下边的规则,如果有规则用来生成查找的依赖的,执行规则中的命令
2、依赖存在,判断是否需要更新
      原则:目标时间 >(大于) 依赖的时间
      反之,则更新

makefile第三个版本

自定义变量obj = a.o b.o c.o
变量的取值aa=$(obj)
makefile自带的变量:大写
自动变量$@表示规则中的目标,$<表示规则中的第一个依赖,$^表示规则中所有的依赖,只能在规则的命令中使用

obj = a.c b.c c.c
target = app
$(target):$(obj)
	gcc $(obj) -o $(target)
%.o:%.c
	gcc -c $< -o $@

缺点:可移植性比较差

makefile第四个版本

makefile所有函数都有返回值
src=$(wildcard)查找指定目录下指定类型的文件,例如

src=$(wildcard ./*.c)

obj=$(patsubst)匹配替换,三个参数a,b,c,使用c将a类型替换为b类型,例如

obj=$(patsubst %.c,%.o,%(src))
src = $(wildcard ./*.c)
obj = $(patsubst %.c, %.o, $(src))
target = app
$(target):$(obj)
	gcc $(obj) -o $(target)
%.o:%.c
	gcc -c $< -o $@

缺点:不能清理项目

makefile第五个版本

让make生成不是终极目标的目标:make 目标名

清理项目的规则语法
clean:
	rm *.o app
在终端执行
make clean

注意:在命令前加-,忽略执行失败的命令,并向下执行其他命令

声明伪目标

需要解决的问题:在目录中使用touch clean创建clean文件,会不执行清楚项目的规则

.PHONY:clean
clean:
	rm *.o app

gdb调试

gcc a,c b.c -o app不能gdb调试,没有函数名和变量名,只有地址
gcc a,c b.c -o app -g-g保留函数名和变量名

启动gdb

gdb 可执行程序的名字,例如gdb app
给程序传参:set args xxx xxxxxx

查看代码

show listsize显示使用l命令默认显示的行数
set listsize 20更改默认显示的行数
l默认显示mian函数文件
l 5显示文件第5行上下的部分
l 函数名显示文件函数的上下的部分
l 文件名:15显示指定文件的15行上下的部分
l 文件名:函数名显示指定文件的函数上下的部分

断点操作

b 15main文件的15行设置断点
i b查看设置过得断点
d 1删除断点,1是断点的编号,可用i b查看编号
d 4-7删除多个断点
dis 8使断点无效,8是断点的编号,可用i b查看编号
ena 7使断点生效,7是断点的编号,可用i b查看编号
b 17 if i == 10在17行设置断点,当i=10时停止运行

调试相关命令

r使程序跑起来,到第一个断点处
start程序跑起来,到程序的第一行
p i打印i的值
ptype i查看i的类型
n向下走一步,不会进入函数体
display i在运行时,自动显示i的值
i display显示所有display的编号
undisplay 1使编号为1的自动显示变量失效
continue/c走多步,到下一个断点
s进入函数体
finish出函数体,但是不能打断点
set var i=5设置变量的值为5,即i=5,相当于,直接走到i=5的位置
until跳出当前循环,但是不能打断点
q退出

文件IO

C语言的库函数会发生系统调用,调用系统函数

虚拟地址空间

Linux每一个运行的程序(进程)操作系统都会为其分配一个0~4G的地址空间(虚拟地址空间)
在这里插入图片描述

文件描述符

在这里插入图片描述
一个进程有一个文件描述符表:0~1023
前三个被占用
文件描述符作用:寻找磁盘文件

函数

open

函数原型:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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
                         创建文件时检测文件是否存在:O_EXCL
                         如果文件存在,返回-1
                         必须与O_CREAT一起使用
                   追加文件:O_APPEND
                   文件截断:O_TRUNC
                   设置非阻塞:O_NONBLOCK
    mode:8进制的数,指定权限
                传入的数字权限不一定时最终的权限,mode & ~umask的值才是最终权限
                umask默认为0002,可以使用umask 数值来改变它的临时值

close

函数原型:

#include >unistd.h>
int close(int fd);

read

函数原型:

ssize_t read(int fd, void *buf, size_t count);

size_t无符号的整型值
ssize_t有符号的整型值
参数:
    fd:open的返回值
    buf:缓冲区,存储要读取的数据
    count:缓冲区能够存储的做大字节数sizeof(buf)
返回值:
    -1:失败
    成功:
        >0:读出的字节数
        =0:文件读完了

wirte

函数原型:

ssize_t write(int fd, const void *buf, size_t count);

参数:
    fd:open的返回值
    buf:要写到文件的数据
    count:数据的有效字节数strlen(buf)
返回值:
    -1:失败
    成功:
        >0:写入到文件的字节数

lseek

函数原型:

off_t lseek(int fd, off_t offset, int whence);

whence参数:
    SEEK_SET头部
    SEEK_CUR当前位置
    SEEK_END尾部

// 文件库指针移动到头部
int len = lseek(fd, 0, SEEK_SET)
// 获取文件指针当前的位置
int len = lseek(fd, 0, SEEK_CUR)
// 获取文件长度
int len = lseek(fd, 0, SEEK_END)

实现文件拓展功能

// 文件原大小100k,拓展为1100k
lseek(fd, 1000, SEEK_END);
// 最后需要做一次额外的写操作
write(fd, "a", 1);

全局变量errno

调用函数失败时,设置errno的值,不同的值对应不同错误,perror()函数帮助我们打印错误信息,可以传入参数,注释是哪个函数的错误信息

阻塞和非阻塞

阻塞读终端

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
// 阻塞读终端
int main(void)
{
	char buf[10];
	n = read(STDIN_FILEND, buf, 10);
	if (n < 0)
	{
		perror("read STDIN_FILEND");
		exit(1);
	}
	write(STDOUT_FILEND, buf, n);
	return 0;
}

Linux系统中执行上面的C代码:
1.执行C文件,read函数堵塞,等待输入
在这里插入图片描述
2.输入hello, world,终端write
在这里插入图片描述
原因:
1.默认bash是前台程序
2../a.out启动了一个程序,前台程序
3../a.out变成了前台程序,bash变成了后台程序
4../a.out等待用户输入10个字符
5.实际输入字符个数大于10,剩下的还在缓冲区
6.read接触阻塞读缓冲区数据
7.write写数据到终端
8.程序结束
9.bash从后台程序变为前台程序,检测到了缓冲区数据,将数据作为shell命令去执行

非阻塞读终端

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define MSG_TRY "try again\n"
// 非阻塞读终端
int main(void)
{
	char buf[10];
	int fd, n;
	// /dev/tty --> 当前打开的终端设备
	fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
	if (fd < 0)
	{
		perror("open /dev/tty");
		exit(1);
	}
	tryagain:
	n = read(fd, buf, 10);
	if (n < 0)
	{
		// 如果write为非阻塞,但是没有数据刻度,此时全局变量errno被设置为EAGAIN
		if (errno == EAGAIN)
		{
			sleep(3);
			write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
			goto tryagain;
		}
		perror("read /dev/tty");
		exit(1);
	}
	write(STDOUT_FILENO, buf, n);
	close(fd);
	return 0;
}

Linux系统中执行上面的C代码:
1.执行C文件,每隔3秒读一次终端
在这里插入图片描述
2.输入hello,睡醒检测到,读入输出
在这里插入图片描述
结论:1.阻塞和非阻塞是文件的属性
           2.普通文件,默认不阻塞
           3.终端设备,默认阻塞
           4.管道,默认阻塞
           5.套接字,默认阻塞

stat函数

ctime:创建时间
atime:访问时间
mtime:修改时间
终端的stat命令:
在这里插入图片描述
函数声明:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char * pathname, struct stat * buf);
int lstat(const char * pathname, struct stat * buf);

pathname,路径

struct stat{
	dev_t st_dev;  // 文件的设备编号
	ino_t st_ino;  // 节点
	mode_t st_mode;  // 文件的类型和存取的权限
	nlink_t st_nlink;  // 连到该文件的硬链接数目,刚建立的文件值为1
	uid_t st_uid;  // 用户ID
	gid_t st_gid;  // 组ID
	dev_t st_rdev;  // (设备类型)若此文件为设备文件,则为其设备编号
	off_t st_size;  // 文件字节数(文件大小)
	blksize_t st_blksize;  // 块大小(文件系统的I/O缓冲区大小)
	blkcnt_t st_blocks;  // 块数
	time_t st_atime;  // 最后一次访问时间
	time_t st_mtime;  // 最后一次修改时间
	time_t st_ctime;  // 最后一次改变时间(指属性)
};
st_mode -- 16位整数
	0-2 bit--其他人权限
		S_IROTH   00004 读权限
		S_IWOTH   00002 写权限
		S_IXOTH   00001 执行权限
		S_IRWXO   00007 掩码,过滤 st_mode 中除其他人权限以外的信息
	3-5 bit--所属组权限
		S_IRGRP   00040 读权限
		S_IWGRP   00020 写权限
		S_IXGRP   00010 执行权限
		S_IRWXG   00070 掩码,过滤 st_mode 中除所属组权限以外的信息
	6-8 bit--文件所有者权限
		S_IRUSR   00400 读权限
		S_IWUSR   00200 写权限
		S_IXUSR   00100 执行权限
		S_IRWXU   00700 掩码,过滤 st_mode 中除文件所有者权限以外的信息
	12-15 bit--文件类型
		S_IFSOCK  0140000 套接字
		S_IFLNK   0120000 符号链接(软链接)
		S_IFREG   0100000 普通文件
		S_IFBLK   0060000 块设备
		S_IFDIR   0040000 目录
		S_IFCHR   0020000 字符设备
		S_IFIFO   0010000 管道
		S_IFMT    0170000 掩码,过滤 st_mode 中除文件类型以外的信息

例如:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>  
int main(int argc, const char *argv[])
{
	struct stat st;
    struct stat* st1;
    st1 = &st;
    int ret = stat("english.txt", &st);
    if (ret == -1)
    {
    	perror("stat error");
        exit(1);
    }
    printf("file size = %d\n", (int)st.st_size);
    // 文件类型 - 判断是不是普通文件
    if ((st.st_mode & S_IFMT) == S_IFREG)
     {
     	printf("normal file\n");
     }
     return 0;
}

结果:
在这里插入图片描述

lstat和fstat

函数原型:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int lstat(const char * pathname, struct stat * buf);
int fstat(int fd, struct stat * buf);

statlstat区别:如果读取软链接文件,stat读的是链接文件指向的文件的属性,lstat读的是链接文件本身的属性。stat又称为追踪,穿透。

文件属性相关的函数

access

含义:测试当前用户指定文件是否具有某种属性,当前用户是指调用这个函数的用户
函数原型:

#include <unistd.h>
int access(const char *pathname, int mode);

pathname,文件名
mode:4种权限
          R_OK – 读
          W_OK – 写
          X_OK – 执行
          F_OK – 文件是否存在
返回值:
          0 – 有某种权限,或者文件存在
          1 – 没有,或文件不存在

chmod,修改文件权限

函数原型:

int chmod(const char * filename, int mode);

pathname,文件名
mode:文件权限,8进制数

chown,修改文件所有者和所属组

函数原型:

int chown(const char * path, uid_t owner, gid_t group);

path,文件路径
owner,整型值,用户ID
       /etc/passwd
group,整型值,组ID
       /etc/group

truncate,修改文件大小

函数原型:

int truncate(const char * path, off_t length);

path,文件名
length,文件的最终大小
      1,比原来小,删掉后边的部分
      2,比原来大,向后拓展

目录操作相关的函数

rename,文件重命名

函数原型:

int rename(const char * oldpath, const char * newpath);

chdir,修改当前进程(应用程序)的路径cd

函数原型:

int chdir(const char *path);

path,切换的路径

getpwd,获取当前进程的工作目录

函数原型:

char *getcwd(char *buf, size_t size);

buf,缓冲区,存储当前的工作目录
size,缓冲区大小
返回值:
      成功:当前的工作目录
      失败:NULL

mkdir,创建目录

函数原型:

int mkdir(const char * pathname, mode_t mode);

pathname,创建的目录名
mode,目录权限,8进制的数,实际权限:mode&~umask

rmdir,删除一个空目录

函数原型:

int rmdir(const char * pathname);

pathname,空目录的名字

目录遍历函数

opendir,打开一个目录

函数原型:

DIR *opendir(const char * name);

name,目录名
返回值:指向目录的指针

读目录

struct dirent
{
	ino_t d_ino;  // 此目录进入点的inode
	ff_t d_off;  // 目录文件开头至此目录进入点的位移
	signed short int d_reclen;  // d_name的长度,不包括NULL字符
	unsigned char d_type;  // d_name所指的文件类型
	har d_name[256];  // 文件名
};
d_type:
	DT_BLK -- 块设备
	DT_CHR -- 字符设备
	DT_DIR -- 目录
	DT_LNK -- 软链接
	DT_FIFO -- 管道
	DT_REG -- 普通文件
	DT_SOCK -- 套接字
	DT_UNKNOWN -- 未知
struct dirent *readdir(DIR *dirp);
参数 -- opendir的返回值
返回值 -- 目录项结构体

关闭目录

函数类型:

int closedir(DIR *dirp);

循环遍历目录

每次readdir按照某种规则,对目录内依次读取,每次读取一个文件

dup-dup2

dup,复制文件描述符

函数原型:

int dup(int oldfd);
int dup2(int oldfd, int newfd);

oldfd – 要复制的文件描述符
返回值:新的文件描述符,取最小的且没被占用的文件描述符
dup调用成功:有两个文件描述符指向同一个文件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值