Linux基础IO :文件描述符,文件流指针,重定向

Posted 阿润菜菜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux基础IO :文件描述符,文件流指针,重定向相关的知识,希望对你有一定的参考价值。

🍎作者:阿润菜菜
📖专栏:Linux系统编程


这是目录


重新认识文件

  1. 是不是只有C/C++有文件操作呢?python、java、go等文件接口操作的方法是不太一样的,那如何理解这种现象?有没有统一的视角去看待所有的语言文件操作呢?—我们今天从系统视角去理解 ---- 实际都是通过系统调用来访问
  2. 文件=内容+属性 — 针对文件的操作:对内容的操作,对属性的操作,对内容和属性的操作
  3. 文件可以分为两大类:磁盘文件 和 被打开的文件(内存文件)
  4. 当文件没有被操作的时候,文件一般放在磁盘位置。空文件也在磁盘中占据空间,因为文件属性也是数据,保存数据就需要空间。
  5. 我们在文件操作的时候,文件需要在哪里?—内存,依据冯诺依曼体系的规定
  6. 所以我们在文件操作的时候,文件需要提前load到内存,那load是内容还是属性?至少有属性吧!那是不是只有你一个人在load呢?当然不是,内存中一定存在大量的不同文件的属性
  7. 所以,打开文件的本质就是将需要的文件加载到内存中,OS内部一定会同时存在大量的被打开的文件,那操作系统需不需要管理呢?怎么管理?— 先描述,在组织!
  8. 先描述 — 构建在内存中的文件结构体 struct file (文件从磁盘中来,struct file* next连接下一个文件信息)。在组织 — struct file结构体利用某种数据结构链接起来。在OS内部,对被打开的文件进行管理,就转换成了对类似链表的增删查改
  9. 结论:文件被打开,OS要为被打开的文件,创建对应的内核数据结构
  10. 所有文件操作的本质就是进程和被打开文件的关系。 — struct task_struct 和 struct file

系统内部的文件操作

库函数底层必须调用系统调用接口,因为无论什么进程想访问文件,都必须按照操作系统提供的方式来进行访问,所以就算文件操作相关函数千变万化,但是底层是不变的,这些函数最后都会调用系统调用接口,按照操作系统的意愿来合理的访问磁盘上的文件。

我们不能用语言绕过操作系统去操纵硬件,所以必须通过系统调用通过操作系统来进行文件操作!不管什么编程语言,只是不同语言对系统调用进行了各自不同的封装,所以对这些文件操作接口的理解,其实就要落实到对系统调用接口的理解! 也就是说所有的只要要访问硬件或者操作系统内部的资源,都要通过系统调用!避不开的!

我们C语言的文件操作

C语言文件操作接口主要包括以下几类:

  • 打开和关闭文件的接口,如fopen(), fclose()等。这些接口用于创建或打开一个文件,并返回一个FILE类型的指针,以及关闭一个已打开的文件,并释放相关资源。
  • 顺序读写数据的接口,如fgetc(), fputc(), fgets(), fputs(), fprintf(), fscanf()等。这些接口用于从文件中读取或写入字符、字符串或格式化数据,并自动移动文件指针。
  • 随机读写数据的接口,如fread(), fwrite(), fseek(), ftell()等。这些接口用于从文件中读取或写入二进制数据块,并根据指定位置移动或获取文件指针。
  • 其他辅助功能的接口,如feof(), ferror(), clearerr()等。这些接口用于检测文件是否到达末尾、是否发生错误、以及清除错误标志。

文件的打开方式:
r:以只读的方式打开文件,若文件不存在就会出错。
w:以只写的方式打开文件,文件若存在则清空文件内容重新开始写入,若不存在则创建一个文件。
a:以只写的方式打开文件,文件若存在则从文件尾部以追加的方式进行写入,若不存在则创建一个文件。
r+:以可读写的方式打开文件,若文件不存在就会出错。
w+:以可读写的方式打开文件,其他与w一样。
a+:以可读写的方式打开文件,其他与a一样。

fopen, fread, fwrite, fseek, fclose等函数的使用

需要注意的是,当向文件中写入数据后,想要重新读取到数据,要么需要关闭文件重新打开,要么就要跳转读写位置到文件起始位置,然后再开始读取文件数据。

#include <stdio.h>
#include <string.h>
 
 
int main()

    FILE *fp = fopen("./bite", "wb+");
    if (fp == NULL) 
        perror("fopen error");
        return -1; 
       
    fseek(fp, 0, SEEK_SET);
    char *data = "linux so easy!\\n";
    //size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    size_t ret = fwrite(data, 1, strlen(data), fp);
    if (ret != strlen(data)) 
        perror("fwrite error");
        return -1; 
       
    fseek(fp, 0, SEEK_SET);//跳转读写位置到,从文件起始位置开始偏移0个字节
    char buf[1024] = 0;
    //size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    ret = fread(buf, 1, 1023, fp);//因为设置读取块大小位1,块个数为1023因此fread返回值为实际读取到的数据长度
    if (ret == 0) 
        if (ferror(fp)) //判断上一次IO操作是否正确
            printf("fread error\\n");
        if (feof(fp)) //判断是否读取到了文件末尾
            printf("read end of file!\\n");
        return -1; 
       
    printf("%s", buf);
    fclose(fp);
    return 0;


当然这些也都是C库提供的函数,是对系统调用的上层封装,在系统级别文件操作我们是通过系统调用实现的

系统内部的文件操作

文件操作系统调用接口是指Linux内核提供的一组用于对文件进行打开、读写、关闭等操作的函数。它们包括以下几个常用的函数:

  • open:打开一个文件,返回一个文件描述符,可以指定文件的打开方式和权限。
  • write:向一个已打开的文件中写入数据,返回实际写入的字节数。
  • read:从一个已打开的文件中读取数据,返回实际读取的字节数。
  • lseek:改变一个已打开文件的读写位置,返回新的偏移量。
  • close:关闭一个已打开的文件,释放资源。

这些函数都需要传入一个文件描述符作为参数,它是一个非负整数,用于标识不同的打开文件。每个进程都有自己独立的一组文件描述符,并且默认有三个预定义的描述符:0代表标准输入,1代表标准输出,2代表标准错误输出。

这些函数都有可能失败,并返回-1,并设置errno变量为相应的错误码。因此,在调用这些函数后,需要检查返回值和错误码来判断是否成功。
我们主要介绍前三个:

OS一般会如何让用户给自己传递标志位的?多个标志位怎么实现呢? — 位图

其实是通过位操作实现的:

#include <stdio.h>

#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
#define FOUR 0x8
#define FIVE 0x10

// 0000 0000 0000 0000 0000 0000 0000 0000
void Print(int flags)

    if(flags & ONE) printf("hello 1\\n"); //充当不同的行为
    if(flags & TWO) printf("hello 2\\n");
    if(flags & THREE) printf("hello 3\\n");
    if(flags & FOUR) printf("hello 4\\n");
    if(flags & FIVE) printf("hello 5\\n");



int main()

    printf("--------------------------\\n");
    Print(ONE);
    printf("--------------------------\\n");
    Print(TWO);
    printf("--------------------------\\n");
    Print(FOUR);
    printf("--------------------------\\n");

    Print(ONE|TWO);
    printf("--------------------------\\n");

    Print(ONE|TWO|THREE);
    printf("--------------------------\\n");

    Print(ONE|TWO|THREE|FOUR|FIVE);
    printf("--------------------------\\n");

    return 0;

open:打开一个文件,返回一个文件描述符,可以指定文件的打开方式和权限

open有两种调用方式:
一种是只传入文件名和访问模式,另一种是还传入创建权限(如果需要创建新文件)。访问模式有必需部分和可选部分,必需部分是 O_RDONLY(只读)、O_WRONLY(只写)或 O_RDWR(读写),可选部分有 O_APPEND(追加)、O_TRUNC(截断)、O_CREAT(创建)、O_EXCL(排他)等。创建权限是由几个标志按位或得到的,如 S_IRUSR(用户读)、S_IWUSR(用户写)、S_IXUSR(用户执行)等。

字符串/0 问题: 系统调用不需要这个!

使用 open 函数打开一个文件,如果不存在则创建一个新文件,并设置访问模式为读写和追加,创建权限为用户读写和组读写:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>

int main() 
    // 打开或创建一个文件
    int fd = open("test.txt", O_RDWR | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
    if (fd == -1) 
        // 打开失败,打印错误信息
        perror("open error");
        exit(1);
    
    // 打开成功,打印文件描述符
    printf("open success, fd = %d\\n", fd);
    // 关闭文件
    close(fd);
    return 0;

创建目录的命令mkdir,目录起始权限默认是0777,创建文件的命令touch,文件起始权限是0666,这些命令的实现实际上是要调用系统接口open的,并且在创建文件或目录的时候要在open的第三个参数中设置文件的起始权限。

 25 int main()
 26 
 27     umask(0);//将进程的umask值设置为0000
 28 
 29     // C语言中的w选项实际上底层需要调用这么多的选项O_WRONLY O_CREAT O_TRUNC 0666
 30     // C语言中的a选项需要将O_TRUNC替换为O_APPEND
 31     int fd = open(FILE_NAME,O_WRONLY | O_CREAT,0666);//设置文件起始权限为0666
 32     if(fd < 0)
 33     
 34         perror("open");
 35         return 1;//退出码设置为1
 36     
 37     close(fd);   
 38 

### write:向一个已打开的文件中写入数据,返回实际写入的字节数
**write:向一个已打开的文件中写入数据,返回实际写入的字节数。需要传入文件描述符、数据缓冲区和数据长度。如果返回值小于请求的字节数,可能是因为错误或者设备驱动程序对数据块长度敏感。如果返回值为 0,表示没有写入任何数据;如果返回值为 -1,则表示出现错误。**

使用 write 函数向一个已打开的文件中写入一段字符串,并检查返回值是否正确:
```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() 
    // 要写入的字符串和长度
    char *str = "Hello world!\\n";
    int len = 13;
    // 向标准输出(文件描述符为1)写入字符串
    int ret = write(1, str, len);
    if (ret == -1) 
        // 写入失败,打印错误信息
        perror("write error");
        exit(1);
    
    if (ret != len) 
        // 写入字节数不正确,打印警告信息
        fprintf(stderr, "write warning: expected %d bytes, but got %d bytes\\n", len, ret);
    
    // 写入成功,打印返回值
    printf("write success, ret = %d\\n", ret);
    

read:从一个已打开的文件中读取数据,返回实际读取的字节数

read:从一个已打开的文件中读取数据,返回实际读取的字节数。需要传入文件描述符、数据缓冲区和数据长度。如果返回值小于请求的字节数,可能是因为错误或者已到达文件尾。如果返回值为 0,表示没有读取任何数据;如果返回值为 -1,则表示出现错误。
使用 read 函数从一个已打开的文件中读取一定长度的数据,并存储到一个缓冲区中,并检查返回值是否正确:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() 
   // 要读取的字节数和缓冲区大小 
   int len = 100;
   char buf[100];
   // 从标准输入(文件描述符为0)读取数据到缓冲区中
   int ret = read(0, buf, len);
   if (ret == -1) 
       // 读取失败,打印错误信息
       perror("read error");
       exit(1);
   
   if (ret == 0) 
       // 读取到文件尾,没有数据可读,打印提示信息
       printf("read end of file\\n");
   
   // 读取成功,打印返回值和缓冲区内容(注意添加结束符)
   printf("read success, ret = %d\\n", ret);
  buf[ret] = '\\0';
   printf("buf: %s\\n", buf);


使用这些接口时,有一些事项需要注意:

  • 在调用 open 函数时,要根据文件的用途和状态选择合适的访问模式和创建权限。如果使用了 O_CREAT 标志,要指定创建权限,否则可能导致文件权限不正确。如果使用了 O_EXCL 标志,要检查返回值是否为 -1,否则可能导致覆盖已有文件。如果打开的是设备文件或符号链接,要注意一些特殊的访问模式,如 O_NONBLOCK、O_NOCTTY、O_NOFOLLOW 等。
  • 在调用 write 函数时,要保证数据缓冲区的有效性和长度正确性。如果写入的是文本文件,要注意添加换行符或结束符。如果写入的是二进制文件,要注意字节序和对齐问题。如果写入的是设备文件或网络套接字,要注意数据块长度和超时问题。
  • 在调用 read 函数时,要保证数据缓冲区的有效性和大小足够。如果读取的是文本文件,要注意处理换行符或结束符。如果读取的是二进制文件,要注意字节序和对齐问题。如果读取的是设备文件或网络套接字,要注意数据块长度和超时问题。
  • 在调用这些接口后,都要检查返回值是否为 -1,并根据 errno 变量来判断错误原因,并进行相应的处理或提示。有些错误可能是暂时性的或可恢复的,如 EINTR、EAGAIN、EWOULDBLOCK 等;有些错误可能是严重性的或不可恢复的,如 EACCES、EBADF、EFAULT、EINVAL 等。

综合使用:
fopen, fread, fwrite, fseek, fclose等函数的使用
需要注意的是,当向文件中写入数据后,想要重新读取到数据,要么需要关闭文件重新打开,要么就要跳转读写位置到文件起始位置,然后再开始读取文件数据。

#include <stdio.h>
#include <string.h>
 
 
int main()

    FILE *fp = fopen("./bite", "wb+");
    if (fp == NULL) 
        perror("fopen error");
        return -1; 
       
    fseek(fp, 0, SEEK_SET);
    char *data = "linux so easy!\\n";
    //size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    size_t ret = fwrite(data, 1, strlen(data), fp);
    if (ret != strlen(data)) 
        perror("fwrite error");
        return -1; 
       
    fseek(fp, 0, SEEK_SET);//跳转读写位置到,从文件起始位置开始偏移0个字节
    char buf[1024] = 0;
    //size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    ret = fread(buf, 1, 1023, fp);//因为设置读取块大小位1,块个数为1023因此fread返回值为实际读取到的数据长度
    if (ret == 0) 
        if (ferror(fp)) //判断上一次IO操作是否正确
            printf("fread error\\n");
        if (feof(fp)) //判断是否读取到了文件末尾
            printf("read end of file!\\n");
        return -1; 
       
    printf("%s", buf);
    fclose(fp);
    return 0;


综合使用:
open, read, write, lseek, close等函数的使用

#include <stdio.h>
#include <unistd.h>//是close, write这些接口的头文件
#include <string.h>
#include <fcntl.h>//是 O_CREAT 这些宏的头文件
#include <sys/stat.h>//umask接口头文件
 
 
int main()

    //将当前进程的默认文件创建权限掩码设置为0--- 并不影响系统的掩码,仅在当前进程内生效
    umask(0);
    //int open(const char *pathname, int flags, mode_t mode);
    int fd = open("./bite", O_CREAT|O_RDWR, 0664);
    if(fd < 0) 
        perror("open error");
        return -1; 
       
    char *data = "i like linux!\\n";
    //ssize_t write(int fd, const void *buf, size_t count);
    ssize_t ret = write(fd, data, strlen(data));
    if (ret < 0) 
        perror("write error");
        return -1; 
       
    //off_t lseek(int fd, off_t offset, int whence);
    lseek(fd, 0, SEEK_SET);
    char buf[1024] = 0;
    //ssize_t read(int fd, void *buf, size_t count);
    ret = read(fd, buf, 1023);
    if (ret < 0) 
        perror("read error");
        return -1; 
    else if (ret == 0) 
        printf("end of file!\\n");
        return -1; 
       
    printf("%s", buf);
    close(fd);
    return 0;

看看Linux内核源代码是怎么说的


可以看到内核源代码的设计内容跟我们所说的基本一致

理解文件控制块&&文件描述符&&文件指针的关系

在进程中每打开一个文件,都会创建有相应的文件描述信息struct file,这个描述信息被添加在pcb的struct files_struct中,以数组的形式进行管理,随即向用户返回数组的下标作为文件描述符,用于操作文件

进程可以打开多个文件,对于大量的被打开文件,操作系统一定是要进行管理的,也就是先描述再组织,所以操作系统会为被打开的文件创建对应的内核数据结构,也就是文件控制块FCB,在linux源码中是struct file结构体,包含了文件的大部分属性

 #include <assert.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define FILE_NAME(number) "log.txt"#number

int main()

    int fd0 = open(FILE_NAME(1),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为0666
    int fd1 = open(FILE_NAME(2),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为0666
    int fd2 = open(FILE_NAME(3),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为0666
    int fd3 = open(FILE_NAME(4),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为0666
    int fd4 = open(FILE_NAME(5),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为0666
    printf("fd:%d\\n",fd0)

《Linux从0到99》九 基础IO

基础IO

1. 回顾c语言文件操作接口

点此回顾

2. 系统调用文件操作系统

01 open函数

open函数用于打开一个文件。
函数原型: int open(const char *pathname, int flags, mode_t mode);
头文件: #include<unistd.h>
参数:

  • pathname:打开文件或创建文件的名字.
  • flags:表示选项,用|连接多个选项
    flags选项宏的定义文件在每个系统中有所不同,Linux中定义在fcntl-linux.h文件中
  • mode参数仅在使用部分选项时才用到,例如O_CREAT在mode中需要给定文件初始权限

返回值: 成功返回打开的文件描述符,失败返回-1
flogs参数:

必选参数(有且仅有一个):

  • O_RDONLY : 以只读的方式打开
  • O_WRONLY : 以只写的方式打开
  • O_RDWR : 以可读可写的方式打开

可选参数:

  • O_TRUNC : 截断文件,清空文件内容
  • O_CREAT : 若文件不存在,则创建文件
  • O_APPEND : 以追加的方式打开
  • O_EXCL | O_CREAT : 如文件存在,则打开失败

mod参数:

当新打开一个文件时,给文件设置权限。设置权限时,传递一个8进程数就可以了

02 read函数

read函数用于从文件中读取数据。
函数原型: ssize_t read(int fildes, void *buf, size_t nbyte);
头文件: #include<unistd.h>
参数:

  • fildes : 读取的文件描述符
  • buf : 数据存放的目标缓冲区
  • nbyte : 最多读取的数据长度,16位无符号整型,一次读取最多为65535个字节

返回值: 返回实际读取的数据长度。

  • 如果是管道或者套接字目前暂无数据则会阻塞
  • 如果是普通文件,读到文件结尾返回0
  • 可以设置非阻塞读取,如果暂无数据则不会阻塞而回返回-1并将errno置为EAGAIN

03 write函数

write函数用于向文件内写入。
函数原型: int write(int fildes, void *buf, int nbyte)
头文件: #include<unistd.h>
参数:

  • fildes :文件描述符
  • buf :写入数据存放的缓冲区
  • nbyte :最大写入字节数

返回值: 写入成功返回实际写入的数据长度,若写入失败,返回-1.

如果数据长度小于nbyte则在后补’\\0’;如果文件剩余容量小于nbyte则返回能写入的最大数据长度 。

04 lseek函数

lseek函数用于修改文件当前偏移量。
函数原型: off_t lseek(int fd, off_t offset, int whence);
头文件: #include<unistd.h>
参数:

  • fd:操作的文件描述符
  • whence:可以有三种参数,
    SEEK_SET :文件开头
    SEEK_CUR :当前文件偏移量
    SEEK_END :文件结尾
  • offset:表示移动距离,offset可正可负

返回值: 成功返回当前文偏移量,失败返回-1。

如果当前fd是一个管道,套接字等不可修改的会将errno置为ESPIPE

05 close函数

close函数用于关闭打开的文件。
函数原型: int close(int fd);
头文件: #include<unistd.h>
参数:

  • fd :待删除文件的文件描述符

返回值: 成功返回0,失败返回-1.

3. 文件描述符

文件描述符就是一个整数。

0 & 1 & 2

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器

所以输入输出还可以采用如下方式:

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

int main()

	char buf[1024];
	ssize_t s = read(0, buf, sizeof(buf));
	if(s > 0)
	
		buf[s] = 0;
		write(1, buf, strlen(buf));
		write(2, buf, strlen(buf));
	
	return 0;

文件描述符的分配规则(最小未分配原则)

文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

4. 文件描述符与文件流指针的区别

01 文件流指针的本质

02 c标准库对应的缓冲区

读缓冲区 & 写缓冲区。

03 文件流指针与文件描述符的关系

文件流指针中包含文件描述符。

5. 重定向

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

int main()

    close(1);//鍏抽棴鎵撳紑鐨勬爣鍑嗚緭鍏ユ枃浠?
    int fd = open("myfile",O_WRONLY | O_CREAT,0644);
    if(fd < 0)
    
        perror("open:");
        return 1;
    
    printf("fd: %d\\n",fd);
    fflush(stdout);
    close(fd);
    return 0;

此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。

常见的重定向有:>, >>, <
重定向的本质

使用dup2系统调用

函数原型: int dup2(int oldfd, int newfd);
头文件: #include <unistd.h>
作用: newfd拷贝oldfd的值,将newfd重定向为oldfd
如果成功: 1. 关闭newfd; 2. 让newfd指向oldfd。
如果失败: 1. oldfd是一个非法的文件描述符或者不存在的文件描述符,函数调用失败并且没有关闭newfd。 2. newfd与oldfd值相等,则dup2函数什么事都不干。

实例:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

int main()

    int fd = open("text",O_WRONLY | O_CREAT,0644);
    if(fd<0)// 打开失败
    
        perror("open:");
        return 1;
    
    close(1);
    dup2(fd,1);
    while(1)
    
        char buf[1024]=0;
        ssize_t read_size = read(0,buf,sizeof(buf)-1);
        if(read_size<0)
        
            perror("read:");
            break;
        
        printf("%s",buf);
        fflush(stdout);
    
    return 0;


6. 动态库和静态库

动态库和静态库都是代码的集合,将代码集合封装在库文件当中,提供给调用者使用。

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
  • 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
  • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
  • 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间

测试程序:

//add.h
#ifndef __ADD_H__
#define __ADD_H__
int add(int a,int b);
#endif //__ADD_H__ 


//add.c
#include "add.h"
int add(int a,int b)

    return a+b;



//sub.h
#ifndef __SUB_H__
#define __SUB_H__ 
int sub(int a,int b);
#endif //__SUB_H__


//sub.c
#include "sub.h"
int sub(int a,int b)

    return a-b;



//main.c
#include <stdio.h>
#include "sub.h"
#include "add.h"

int main()

     int a=10;
     int b=20;
     printf("%d + %d = %d\\n",a,b,add(a,b));
     printf("%d - %d = %d\\n",a,b,sub(a,b));
     return 0;

静态库

生成静态库:

测试目标文件生成后,静态库删掉,程序照样可以运行。

  • -L 指定库路径
  • -l 指定库名

    库搜索路径
  • 从左到右搜索-L指定的目录。
  • 由环境变量指定的目录 (LIBRARY_PATH)
  • 由系统指定的目录
    • /usr/lib
    • /usr/local/lib

动态库

  • shared: 表示生成共享库格式
  • fPIC:产生位置无关码(position independent code)
  • 库名规则:libxxx.so

实例:

  1. 生成动态库
  2. 使用动态库
  • l : 链接动态库,只要库名即可(去掉lib以及版本号)
  • L: 链接库所在的路径
  1. 运行动态库

1、拷贝.so文件到系统共享库路径下, 一般指/usr/lib
2、更改 LD_LIBRARY_PATH

使用外部库
系统中其实有很多库,它们通常由一组互相关联的用来完成某项常见工作的函数构成。比如用来处理屏幕显示情况的函数(ncurses库)

#include <math.h>
#include <stdio.h>
int main(void)

	double x = pow(2.0, 3.0);
	printf("The cubed is %f\\n", x);
	return 0;

gcc -Wall calc.c -o calc -lm

  • -lm表示要链接libm.so或者libm.a库文件

以上就是这篇文章的所有内容啦,感谢老铁有耐心看完。有啥错误请多多指正哈!码字不易,希望大佬们点个赞

以上是关于Linux基础IO :文件描述符,文件流指针,重定向的主要内容,如果未能解决你的问题,请参考以下文章

《Linux从0到99》九 基础IO

《Linux从0到99》九 基础IO

Linux基础IO篇

Linux:基础IO

Linux:基础IO

Linux:基础IO