当前位置:网站首页>C語言最佳實踐之庫文件介紹(下)

C語言最佳實踐之庫文件介紹(下)

2022-06-10 11:47:00 徐福記456

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 27CPU時鐘
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); // 發送信號
}

原网站

版权声明
本文为[徐福記456]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/161/202206101146173686.html