C语言最佳实践之库文件介绍(下)

C语言的库文件包括:pthread线程、assert断言、string字符串、time时间、math数学运算、std开头的标准库、sys开头的系统库等。其中,标准库有stdalign.h、stdarg.h、stdatomic.h、stdbool.h、stddef.h、stdint.h、stdio.h、stdlib.h。系统库有sys/mman.h、sys/stat.h、sys/ioctl.h、sys/file.h。本篇文章主要介绍std标准库和sys系统库。

上一篇C语言库文件的介绍可查看:C语言最佳实践之库文件介绍(上)

目录

一、stddef.h预先宏定义

1、offsetof

2、ptrdiff_t

3、__func__与__LINE__

二、stdio.h

1、打开文件

2、读写数据 

3、移动文件指针

4、获取文件长度

5、重命名文件

6、判断是否到末尾

7、重置文件到开头位置

9、删除文件

10、关闭文件

三、stdlib.h

四、string.h

五、stdatomic.h

1、原子类型 

2、原子初始化

3、原子读写

4、原子加减

5、原子交换

6、内存屏障

六、stdarg.h

七、sys/mmap.h

八、sys/stat.h

1、stat结构体

2、统计文件信息

九、signal.h

1、信号分类

2、信号捕获


一、stddef.h预先宏定义

1、offsetof

stddef提供offsetof、size_t、ptrdiff等宏定义。其中offsetof用于获取成员变量在结构体的偏移位置。offsetof(t, d)的示例代码如下:

    struct Programmer {
        int    age;
        char* name;
        char* post;
    };

    void print_offset() {
        printf("offset of struct =%lu", offsetof(struct Programmer, name));
    }

2、ptrdiff_t

ptrdiff_t用于计算两个指针相减的差值。比如一个指针指向字符串的头,另一个指针指向字符串的尾,那么两个指针的差值等于字符串长度。示例代码如下:

    char* str = "hello, world!";
    char* ptr_start = str;
    char* ptr_end = str + strlen(str);
    ptrdiff_t diff = ptr_end - ptr_start;
    printf("ptr diff=%td\n", diff);

3、__func__与__LINE__

在调试时,我们需要打印哪个文件、哪个函数、哪一行有问题,__FILE__、__func__、__LINE__就派上用场了。如果要获取当前格式化时间戳,可以用__TIMESTAMP__。这些宏定义都是以双下划线开头。示例代码如下:

    printf("func=%s\n", __func__);
    printf("file=%s\n", __FILE__);
    printf("line=%d\n", __LINE__);
    printf("timestamp=%s\n", __TIMESTAMP__);

对应的打印输出如下:

    func=macro_define
    file=/Users/frank/Documents/FFmpegAndroid/app/src/main/cpp/test_api.c
    line=357
    timestamp=Sat Jun  4 17:19:22 2022

二、stdio.h

stdio.h提供文件的常用操作,包括:打开、读写、关闭、重命名、删除、刷新、标准输出等等。具体介绍如下表所示:

文件操作
函数 描述
FILE* fopen(const char* filename, const char* mode) 打开文件,mode包括:只读、只写、读写、追加等
size_t fread(void* ptr, size_t size, size_t count, FILE* stream) 读取文件,读取指定大小的内容到缓冲区
size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream) 写入文件,如果是读写模式会覆盖原始内容,追加是从末尾开始写
int fseek(FILE* stream, long offset, int whence) 移动文件指针,seek模式:SEEK_SET、SEEK_CUR\SEEK_END
long ftell(FILE* stream) 获取当前位置,结合fseek使用
int fflush(FILE* stream) 刷新文件缓冲区
void rewind(FILE* stream) 重置文件指针到文件开头
int remove(const char* filename) 删除文件,返回成功或失败
int rename(const char* old, const char* new) 重命名文件
int fclose(FILE* stream) 关闭文件
int printf(const char* format, ...) 打印输出到控制台
int scanf(const char* format, ...) 读入内容
int sprintf(char* s, const char* format, ...) 输出到字符串
int fprintf(FILE* stream, const char* format, ...) 输出到文件
int vprintf(const char* format, va_list arg) 输出到可变参数列表

int getc(FILE* stream)

从文件获取一个字符
int putc(int c, FILE* stream) 写入一个字符到文件
int feof(FILE* stream) 判断文件是否到末尾,eof=-1
int ferror(FILE* stream) 获取文件错误码
void clearerr(FILE* stream) 清除异常信息
void perror(const char* s) 错误描述信息输出到标准错误
void setbuf(FILE* stream, char* buf) 设置文件缓冲区

1、打开文件

char* path = "sdcard/hello.txt";
FILE* file = fopen(path, "wb+");

2、读写数据 

char *buf0  = "Just do it";
size_t size = strlen(buf0);
fwrite(buf0, size, 1, file); // 写文件
fseek(file, 0, SEEK_SET);
char *buf1 = (char*) malloc(size * sizeof(char));
fread(buf1, size * sizeof(char), 1, file); // 读文件

3、移动文件指针

fseek(file, 10, SEEK_SET);

4、获取文件长度

fseek(file, 0, SEEK_END);
long len = ftell(file);

5、重命名文件

rename("sdcard/2.txt", "sdcard/222.txt");

6、判断是否到末尾

int ret = feof(file);

7、重置文件到开头位置

rewind(file);

9、删除文件

remove("sdcard/1.txt");

10、关闭文件

 fclose(file);

三、stdlib.h

stdlib.h提供内存分配与释放、随机数的生成、进程退出、执行命令行、字符串转数值类型、二分查找算法、快排算法、求绝对值等操作。其中内存分配包括:malloc、calloc、realloc、aligned_alloc。具体对比如下表所示:

内存分配
malloc 内存分配,默认内存对齐
calloc 内存分配,并且初始化
realloc 重新分配,拷贝旧内存到新区域
aligned_alloc 对齐分配,指定对齐单位大小

stdlib相关的函数如下:

void lib_api() {
    // 内存分配
    void* malloc(size_t size);
    // 重新分配内存,拷贝旧内存到新内存空间,释放旧内存
    void* realloc(void* ptr, size_t size);
    // 内存对齐分配
    void *aligned_alloc(size_t alignment, size_t size);
    // 内存分配并且初始化,相当于malloc + memset
    void* calloc(size_t nmemb, size_t size);
    // 释放内存
    void free(void* ptr);
    // 异常的进程终止
    void abort(void);
    // 注册终止函数,在exit退出时调用
    int atexit(void (*func)(void));
    // status=0为正常退出,status!=0为异常退出
    void exit(int status);
    // 字符串转数值类型
    double atof (const char* nptr);
    int    atoi (const char* nptr);
    long   atol (const char* nptr);
    // 执行命令行,system在原进程开辟新的进程,exec用新进程覆盖原进程
    int system(const char* string);
    // 二分法查找
    void* bsearch(const void* key, const void* base, size_t nmemb, size_t size,
                  int (*compar)(const void *, const void *));
    // 快排算法
    void qsort(void* base, size_t nmemb, size_t size,
               int (*compar)(const void *, const void *));
    // 求绝对值
    int abs(int j);
    // 生成随机数
    int rand(void);
    void srand(unsigned int seed);
    long random();
}

四、string.h

string.h提供字符串的常见操作:拷贝、比较、拼接、查找、分割。其中,部分的mem操作和str操作功能一样。比如,memcpy拷贝内存,可以拷贝结构体、类、数组等,但需要指定长度;strcpy拷贝字符串,仅限于字符串,不需要指定长度。两者对比如下:

void* memcpy(void* dest, const void* src, size_t n)

拷贝内存,包括结构体、类、数组等

char* strcpy (char* dest, const char* src)

拷贝字符串

int memcmp(const void* str1, const void* str2, size_t n)

内存值比较

int strcmp (const char* str1, const char* str2)

字符串比较

void* memchr(const void* str, int c, size_t n)

内存区域查找字符

char* strchr(const char* str, int c)

字符串中查找字符

其他的常用函数如下:

void string_api() {
    // 移动指定长度的源内存到目的内存
    void* memmove(void* dest, const void* src, size_t n);
    // 设置内存值,用于内存初始化
    void* memset(void* str, int c, size_t n);

    // 拼接字符串
    char* strcat (char* dest, const char* src);
    // 查找字符在字符串最后一次出现的位置
    char* strrchr(const char* str, int c);
    // 查找str2在str1字符串出现的位置
    char* strstr(const char* str1, const char* str2);
    // 分割字符串
    char* strtok(char* src, const char* delim);
    // 把错误码转换为字符串
    char* strerror(int errnum);
    // 获取字符串长度
    size_t strlen(const char* s);
}

五、stdatomic.h

1、原子类型 

stdatomic.h提供原子操作,线程安全,比锁更加轻量级。支持的原子类型包括:bool、char、int、short、long等。宏定义如下(包括但不限于):

    typedef _Atomic(bool)	atomic_bool;
    typedef _Atomic(char)	atomic_char;
    typedef _Atomic(short)	atomic_short;
    typedef _Atomic(int)	atomic_int;
    typedef _Atomic(long)	atomic_long;

2、原子初始化

使用atomic_init()进行初始化,需要注意,第一个参数是对象的地址,代码如下:

    atomic_int count;
    atomic_init(&count, 1);

3、原子读写

使用atomic_load()进行读取,atomic_store_explicit()进行写入,代码如下:

    // 读取
    atomic_load(&count);
    // 写入
    atomic_store_explicit(&count, 3, memory_order_seq_cst);

4、原子加减

 原子运算支持加、减、或、异或、与。以加减运算为例:

    atomic_fetch_add(&count, 5);
    atomic_fetch_sub(&count, 3);

5、原子交换

原子交换步骤分为三步:读取、比较、写入。代码如下:

    atomic_exchange(&count, 6);

6、内存屏障

原子操作提供fence内存屏障,与关键字volatile类似,保证内存有序性。有一个memory_order枚举类型,包括获取操作、释放操作、获取与释放、消费操作、无序性、有序性等,具体如下:

    typedef enum {
        memory_order_relaxed = __ATOMIC_RELAXED,
        memory_order_consume = __ATOMIC_CONSUME,
        memory_order_acquire = __ATOMIC_ACQUIRE,
        memory_order_release = __ATOMIC_RELEASE,
        memory_order_acq_rel = __ATOMIC_ACQ_REL,
        memory_order_seq_cst = __ATOMIC_SEQ_CST
    } memory_order;

对应的函数API如下:

    atomic_thread_fence(memory_order order);

六、stdarg.h

stdarg.h提供可变参数的遍历,由va_start()、va_arg()和va_end()三个函数组成,还有一个va_list可变参数列表。其中va_start()是参数列表的开始,va_arg()是获取列表的下一个,va_end()结束遍历释放内存。示例代码如下:

void sum_args(int args, ...) {
    int sum = 0;
    va_list ap;
    va_start(ap, args);
    for (int i=0; i<args; i++) {
        sum += va_arg(ap, int);
    }
    va_end(ap);
    printf("sum=%d\n", sum);
}

大家可以猜猜这个函数调用的结果:

sum_args(3, 11, 22, 33);

七、sys/mmap.h

mmap.h提供内存映射的函数,相关函数声明如下:

    /**
     * [mmap](http://man7.org/linux/man-pages/man2/mmap.2.html)
     * creates a memory mapping for the given range.
     *
     * Returns the address of the mapping on success,
     * and returns `MAP_FAILED` and sets `errno` on failure.
     */
    void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset);

    /**
     * [munmap](http://man7.org/linux/man-pages/man2/munmap.2.html)
     * deletes a memory mapping for the given range.
     *
     * Returns 0 on success, and returns -1 and sets `errno` on failure.
     */
    int munmap(void* addr, size_t size);

mmap()函数的第四个参数prot类型包括:读、写、执行、无权限。如下图所示:

第五个参数flag标志位包括:共享的、私有的、固定的。如下图所示:

​​​​​​​ 

内存映射的示例代码如下:

void mapping() {
    char *buf = "hello, world";
    // file:use data in buf
    int fd = open("sdcard/hello.txt", O_RDWR);
    write(fd, buf, strlen(buf));
    // mapping:use data at address
    fd = open("sdcard/hello.txt", O_RDWR);
    void* address = mmap(0, strlen(buf), PROT_READ, MAP_PRIVATE, fd, 0);
}

八、sys/stat.h

1、stat结构体

stat.h用于获取文件状态信息,包括:设备id、文件大小、文件访问权限、文件修改时间、文件访问时间、序列号、文件链接的数量等。stat结构体如下:

    struct stats {
        dev_t st_dev;     // ID of device containing file
        ino_t st_ino;     // file serial number
        mode_t st_mode;   // mode of file
        nlink_t st_nlink; // number of links to the file
        uid_t st_uid;     // user ID of file
        gid_t st_gid;     // group ID of file
        dev_t st_rdev;    // device ID
        off_t st_size;    // file size in bytes
        int st_blksize;   // a filesystem-specific preferred I/O block size
        long st_blocks;   // number of blocks allocated for this object
        struct timespec st_atim; // time of last access
        struct timespec st_mtim; // time of last data modification
        struct timespec st_ctim; // time of last status change
    };

2、统计文件信息

stat()函数的使用示例如下:

void file_stat() {
    struct stat buf;
    const char* path = "sdcard/hello.txt";
    stat(path, &buf);
    printf("file mode:%u\n",buf.st_mode);          // 文件访问权限
    printf("file size:%lu\n",buf.st_size);         // 文件大小
    printf("file access time:%lu\n",buf.st_atime); // 文件访问时间
    printf("file modify time:%lu\n",buf.st_mtime); // 文件修改时间
}

九、signal.h

1、信号分类

signal.h是系统提供的信号,包括:非法指令、函数陷阱、异常终止、非法地址、杀掉进程、内存异常、进程退出、浮点异常等。比如,访问一个空指针,或者已经释放的指针,或者访问指针指向的内存区域超出边界,系统会发送SIGSEGV信号。再比如,使用命令行杀掉进程kill -9 pid,系统会发生SIGKILL信号。具体信号与数值如下表所示:

系统信号
信号 描述 信号 描述
SIGHUP  1 信号挂起 SIGCHLD 17 子进程结束
SIGINT    2 信号中断 SIGCONT 18 进程恢复
SIGQUIT 3 进程退出 SIGSTOP 19 程序停止
SIGILL     4 非法指令 SIGTSTP 20 停止进程
SIGTRAP 5 函数陷阱 SIGTTIN 21 读取数据
SIGABRT 6 异常终止 SIGTTOU 22 写入数据
SIGBUS   7 非法地址 SIGURG 23 紧急处理
SIGFPE    8 浮点溢出 SIGXCPU 24 超出CPU限制
SIGKILL   9 杀掉进程 SIGXFSZ 25 扩大文件
SIGUSR1 10 用户保留 SIGVTALRM 26 虚拟时钟
SIGSEGV 11 内存异常 SIGPROF 27 CPU时钟
SIGUSR2 12 用户保留 SIGWINCH 28 窗口变化
SIGPIPE 13 信号管道 SIGIO 29 文件描述符就绪
SIGALRM 14 定时时钟  SIGPWR 30 电源异常
SIGTERM 15 程序结束 SIGSYS 31 非法系统调用
SIGSTKFLT 16 处理器栈异常    SIGRTMIN 32 实时信号

2、信号捕获

信号处理函数包括:信号捕获、中断、等待、挂起、生成信号、杀掉进程。具体如下:

    int sigaction(int signal, struct sigaction* new_action, struct sigaction* old_action); // 捕获信号
    int siginterrupt(int signal, int flag); // 信号中断
    int sigwait(const sigset_t* set, int* signal); // 信号等待
    int sigsuspend(const sigset_t* mask); // 信号挂起
    int raise(int signal); // 发送信号
    int kill(pid_t pid, int signal); // 杀掉进程

下面我们来模拟发送信号与捕获信号的过程:

void process_signal() {
    printf("receive sig=%d\n", SIGFPE);
}

void test_signal() {
    struct sigaction action;
    action.sa_handler = process_signal; // 声明信号处理函数
    sigaction(SIGFPE, &action, NULL); // 捕获信号
    raise(SIGFPE); // 发送信号
}

猜你喜欢

转载自blog.csdn.net/u011686167/article/details/125112002
今日推荐