創作內容

9 GP

C 語言初始化格式化字串

作者:Yotsuba│2020-06-25 17:36:26│巴幣:36│人氣:786
注意 ! 文章中會出現大量可怕的 printf,需要搭配 Google 或 man page 閱讀 XD


C 語言沒有實作所謂的「字串」,取而代之的是「字元陣列」

也就是說 char * str = "Hello World !"; 這樣的東西,只不過是一段連續的記憶體

這使得 C 語言如果要初始化一個「格式化」字串有些麻煩

我就曾經設想過,C 語言有沒有辦法寫成像 Python 這樣高階 ?


s = 'It %s %d seconds .' % ('cost', 10)
print(s)


如果去研究一下 printf 家族的東西,其實看來是有機會的

一般來說 printf 是寫入到標準輸出,你可能還知道 fprintf 是寫入到檔案輸出

而有一個 sprintf 是寫入到 C 字串用的


這裡有一個小問題 : sprintf 是危險的,可能會 Buffer overflow,那 snprintf 呢 ?

snprintf 安全多了,就是有限制長度版本的 sprintf

可是還是得預先知道字串長度,才有辦法設定 Buffer 大小

這就是 C 語言搞格式化字串麻煩的地方

原生的 C 語言既沒有「字串」的概念,還得自行管理記憶體

記憶體給太多是浪費,給太少會 Buffer overflow

究竟有沒有什麼辦法可以先算好格式化字串的大小 ?


查資料的時候又發現了 asprintf,這東西又是 sprintf 加強版

函數原型 : int asprintf(char **strp, const char *fmt, ...);

asprintf 完全是我們要找的東西,它可以拿來初始化格式化字串

而且乾脆連字串長度都幫你算好了,再幫你 malloc 跟寫入

使用完後要記得 free 就好


可惜 asprintf 是 GNU 的擴展,實際上根本不屬於 C 語言或者 POSIX 的標準

所以乾脆自己寫一個 asprintf,可以參考以下的 code


// asprintf.h


#ifndef __AS_H__
#define __AS_H__


#include <stdarg.h>


int vasprintf(char ** buffer, const char * format, va_list ap);
int asprintf(char ** buffer, const char * format, ...);


#endif


// asprintf.c


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


int vasprintf(char ** buffer, const char * format, va_list ap) {
    int len = vsnprintf(NULL, 0, format, ap);
    char * str = malloc(sizeof(char) * (len + 1));
    vsnprintf(str, len + 1, format, ap);
    *buffer = str;
    return len;
}


int asprintf(char ** buffer, const char * format, ...) {
    va_list ap;
    va_start(ap, format);
    int ret = vasprintf(buffer, format, ap);
    va_end(ap);
    return ret;
}


// main.c


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


#include "asprintf.h"


int main(int argc, char const * argv[]) {
    char * buffer;
    asprintf(&buffer, "It %s %d seconds .", "cost", 10);
    printf("%s", buffer);
    free(buffer);
    return 0;
}




一共會有三支檔案,包含編譯到執行的畫面都如圖所示

如果只看 main.c 會發現,asprintf 真的是一個超高階的寫法

然後我再刻這東西的時候有參考一篇很棒的文章

Using asprintf() on windows



首先解釋一下 asprintf 的函式原型 : int asprintf(char **strp, const char *fmt, ...);

回傳值的 int 是告訴你字串長度,printf 家族都一樣

strp 是一個 pointer to pointer 的技巧,達到間接改寫 strp 的內容

fmt 就是格式化字串本身,就像平常用 printf 那樣

... 是不定參數,讓你傳入 %d 或 %s 要接的東西,也跟 printf 一樣


成功傳入一個 Buffer 跟格式化字串後,裡面用到一些 va_ 開頭的函式

這些東西是定義在 stdarg.h 裡面的,用來處理不定參數值

這時候又會 call 到 vasprintf,把原本的 ... 改成 va_list 的方式傳入


vasprintf 裡面又 call 了兩次 vsnprintf

vsnprintf 是 C 語言的標準,它的功能是把 va_list 的內容寫到 C 字串,並且限制長度

第一次 call 我們帶入 NULL 跟 0,因為並不是要真的寫入,而是讓它幫我們算字串長度

知道字串長度就解決我們最初的問題了,終於知道如何 malloc 剛好的大小

第二次 call 就真的帶入 Buffer、長度、格式化內容,並且真的寫入

接著回傳長度,asprintf 再把 va_list 釋放掉,再傳回長度

我們就成功初始化一個格式化字串了


這邊幫大家整理一下重點 :


1. printf 家族的成員雖然很多,但只是各種輸出到各種地方而已

不用感到太害怕,也不要死記


2. printf 家族的成員,其中帶有 n 的都是限制長度版本 ( 安全版本 )

就像 strcpy 還有 strncpy 版本一樣


3. printf 家族的成員,回傳值都是 int,每一個都會幫你算字串長度


4. asprintf 是一個很棒的函式,讓你用極度高階的方式初始化格式化字串

但是依然脫離不了記憶體管理,使用完記得要 free 還回去
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=4828255
All rights reserved. 版權所有,保留一切權利

相關創作

留言共 3 篇留言

young

06-25 18:38

Yotsuba
卡卡卡06-27 22:08
卡布奇諾
推推
真的怕
完全沒想過這個問題哈哈
但我都sprintf爆啦(x

06-27 22:01

Yotsuba
你可以直接把 code 打包帶走,以後直接用這招就好 XD06-27 22:07
川普的ㄐㄐ
卡 我也是用sprintf

04-20 12:11

我要留言提醒:您尚未登入,請先登入再留言

9喜歡★happy819tw 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:我真的爬不了這個網站,怎... 後一篇:撰寫網路文章的排版風格...

追蹤私訊切換新版閱覽

作品資料夾

gowofmath666各位巴友們
推薦一部冷門動畫 寫實到不行的遺珠之憾—《星合之空》 https://home.gamer.com.tw/artwork.php?sn=5947014看更多我要大聲說1小時前


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】