C語言標準庫(1)

姓名:呂彬 學號:1613014035

【嵌牛導讀】標準庫(Standard Library)是C語言重要的一部分,不過學習C語言這么長時間,都沒有細致的了解過標準庫到底中包含哪些內容,這幾天打算來仔細看看這部分內容。

【嵌牛鼻子】C語言標準庫有各種不同的實現,比如最著名的glibc, 用于嵌入式Linux的uClibc,還有ARM公司的自己的C語言標準庫及精簡版的MicroLib等。不同標準庫的實現并不相同,而且提供的函數也不完全相同,不過有一個它們都支持的最小子集,這也就是最典型的C語言標準庫。

【嵌牛提問】C語言標準庫包含哪些內容呢?

【嵌牛正文】這個C語言標準庫中一共包含15個頭文件,粗略的按常用程度排序列舉如下:

圖片發自簡書App


本文總結的是不完整的C標準庫,僅列舉一些常用且最重要的部分。ime.h日期和時間操作。需要特別注意的是,書中使用的time_t時間戳標準是從1900年1月1日午夜開始的,這與目前廣泛使用的UNIX時間戳不一樣,也和Glibc的實現不一樣,書中是通過_TBIAS這個宏定義偏置量來解決這個問題的,為了簡單起見,此處對此進行了改寫,忽略了偏置問題,直接將其修改為與UNIX時間戳一樣。使用方法通常使用time(NULL)獲取一個time_t類型的UNIX時間戳,這一般是一個32位整數(signed int),指的是從1970年1月1日午夜至今的秒數,大約可以表示到2038年。如果要獲取更精確的時間,可使用clock()函數。其余函數用于在幾種不同數據結構間進行轉換,根據需要選取即可,其中tm類型的定義一般是這樣的:1234567891011 struct tm {? ? int tm_sec;? ? /* [0, 60], 1 leap second */? ? int tm_min;? ? /* [0, 59] */? ? int tm_hour;? ? /* [0, 23] */? ? int tm_mday;? ? /* [1, 31] */? ? int tm_mon;? ? /* [0, 11] */? ? int tm_year;? ? /* Years since 1900 */? ? int tm_wday;? ? /* [0, 6], Sunday, Monday... */? ? int tm_yday;? ? /* [0, 365], days since January 1th */? ? int tm_isdst;? /* 夏令時標志,無效則為0 */}需要注意的是,以上只是time_t的最小實現,實際Glibc 2.23版本的代碼中除了上述成員外還添加了其它字段。tm_year是從1900年開始的,并不是和UNIX時間戳相同的1970年。實現方法time()和clock()函數是依賴于具體實現的,此處不作分析。difftime()函數返回兩個時間戳之間的差值,考慮到time_t可能會被定義為無符號整數,故需要先比較二者的大?。?23 double difftime(time_t t1, time_t t0) {? ? return (t0 <= t1 ? (double)(t1 - t0) : -(double)(t0 - t1));}tm與time_t間的轉換函數是<time.h>中的重點,這里主要來看一下gmtime()和mktime()的實現方法。下列代碼在書中給出的代碼基礎上進行了些改寫,主要是做了些精簡,沒有考慮夏令時等問題。雖然以下兩段代碼比Glibc中的實現要簡單得多,不過經測試完全可以正常使用。1234567891011121314151617181920212223242526272829303132333435363738394041 static const short lmos[] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 };static const short mos[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };#define MONTAB(year) ((year & 0x03) == 2 ? lmos : mos)struct tm * gmtime(time_t * timer) {? static struct tm ts;? int year;? int days;? int secs;? secs = *timer;? days = secs / 86400;? ? ? ? ? // 獲取天數? ts.tm_wday = (days + 4) % 7;? // 1970年1月1日是星期四? int dDay;? /* days / 365 先求出year的初步估計,因為閏年的存在不一定準確(可能會多1年) */? /* (year + 1) / 4 求出因閏年多出來的天數 */? /* days與year年初的天數比較,若days小于它,說明year估計有誤,需要減去1年 */? for (year = days / 365; days < (dDay = (year + 1) / 4 + 365 * year);)? ? year--;? days -= dDay;? ? ? ? ? ? // 將days變成1年中的天數? ts.tm_year = year + 70;? // tm_year是從1900年開始的? ts.tm_yday = days;? ? ? ? // 總天數減去年初的天數? /* 從最后一個月開始,逐步向前尋找正確的月份,pm[mon]得到月初的天數 */? int mon;? const short * pm = MONTAB(year);? int tmp = (year & 0x03) == 2;? for (mon = 11; days < pm[mon]; mon--);? ts.tm_mon = mon;? ts.tm_mday = days - pm[mon] + 1;? /* 根據secs依次求出小時、分鐘和秒 */? secs %= 86400;? ts.tm_hour = secs / 3600;? secs %= 3600;? ts.tm_min = secs / 60;? ts.tm_sec = secs % 60;? ? return &ts;}123456789101112131415161718192021222324252627282930 static const short lmos[] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 };static const short mos[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };#define MONTAB(year) ((year & 0x03) == 2 ? lmos : mos)time_t mktime(struct tm * timeptr) {? int year, days, secs;? // 檢查參數有效性,不使用tm_yday & tm_wday#ifndef NDEBUG? if (timeptr->tm_hour < 0 || timeptr->tm_hour > 23)? ? return -1;? if (timeptr->tm_mday < 1 || timeptr->tm_mday > 31)? ? return -1;? if (timeptr->tm_min < 0 || timeptr->tm_min > 59)? ? return -1;? if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)? ? return -1;? if (timeptr->tm_sec < 0 || timeptr->tm_sec > 60)? ? return -1;? if (timeptr->tm_year < 70 || timeptr->tm_year > 138)? ? return -1;#endif? year = timeptr->tm_year - 70;? days = (year + 1) / 4 + 365 * year;? days += MONTAB(year)[timeptr->tm_mon] + timeptr->tm_mday - 1;? secs = 3600 * timeptr->tm_hour+ 60 * timeptr->tm_min + timeptr->tm_sec;? return (86400 * days + secs);}需要指出的是,上面這個mktime()函數沒有考慮時區的問題,而標準的mktime()函數實現的是將由tm表示的地方時轉換為time_t表示的GMT時間,所以二者并不等價。在1970~2038年這個范圍內,閏年規律符合簡單的4年一閏,所以可以用(year & 0x03) == 2來進行閏年判斷。其余asctime()、ctime()等函數用于返回格式化的時間字符串,其原理和sprintf()等函數大同小異,在此不作分析。ctype.h包含字符測試及大小寫轉換函數。使用方法提供了若干isxxxx()函數用于判斷字符類型,并提供了大小寫轉換函數,具體函數列表見:C語言標準庫總結需要說明的是,字符集的具體定義和區域設置有關,不過常用的就是英文的情況,因為這些函數也無法處理中文編碼。另外,函數接受的參數是一個int類型的整數,不過只有unsigned char類型所能表示的值加上EOF宏定義的值(一般為-1)是有效的,傳入其它值的行為是未定義的。實現方法出于效率考慮,標準庫中的實現方法是基于轉換表的,這里不列舉具體使用的轉換表了,僅描述一下設計思路。首先將整個字符集合劃分為若干個合理設計的子集,如數字(0~9)、小寫字母(a~z)、大寫字母(A~Z)等,每一類用一個比特位來表示,這樣就可以得到如下宏定義:1234 #define _XD? 0x01? /* '0'-'9', 'A'-'F', 'a'-'f' */#define _UP? 0x02? /* 'A'-'Z' */#define _SP? 0x04? /* space */// ......任何一個字符都屬于某一子集(或某幾個子集)中,這樣就可以根據以上宏定義得到這個字符的編碼了,將全體字符編碼構成一個數組,這就是所謂的轉換表,書中這個數組的名字叫做_Ctype。這樣一來,要判斷某個字符是否屬于某個子集就很簡單了,只要檢查這個字符在轉換表中對應值的特定位是否被置位了就可以了,比如檢查一個字符是否是大寫字母:123 int isupper(int c) {? ? return (_Ctype[c] & _UP);}大小寫間的轉換也是基于轉換表的,這個轉換表相當于在原始ACSII表的基礎上將大寫字母替換為小寫字母(或相反)得到的。關于區域編碼的問題此處從略。stdarg.h用于處理可變參數。使用方法可變參數函數的定義類似這樣:1234567891011 #include <stdarg.h>void fun(int parmN,...) {? ? va_list ap;? ? va_start(ap, parmN);? ? //......? ? int a = va_arg(ap, int);? ? double b = va_arg(ap, double);? ? //......? ? va_end(ap);}必須要有至少一個固定參數,習慣上把最后一個固定參數叫做parmN。在函數中先調用va_start()初始化va_list,之后就可以通過va_arg()依次獲取各參數,最后調用va_end()即可。需要注意的是,在可變參數中,應用的是“加寬”原則,也就是float會被擴展成double,char、short等會被擴展成int,也就是說,函數中只該使用以下這些表達式:123 va_arg(ap, double);va_arg(ap, int);va_arg(ap, unsigned int);實現方法12345 typedef char * va_list;#define va_start(ap, A)? (void)((ap) = (char *)&A)#define va_end(ap)? ? ? (void)(0)#define va_arg(ap, T)? ? (*(T *))((ap += sizeof(T)) - sizeof(T))這里給出的代碼是簡化版代碼,沒有考慮存儲空隙及對齊問題,僅用來說明基本原理。assert.h提供斷言。使用方法在需要使用斷言的地方加入assert(x)即可,x是一個int,若x為零斷言成立,此時程序會向標準錯誤流輸出一條包含出錯行號等的錯誤信息并調用abort()函數終止程序的運行。assert(x)返回void。一般只有在程序調試時才需要終止程序運行,發布時應該去掉這個功能,為實現這一目的,可通過定義NDEBUG這個宏來實現,一般使用編譯器預定義。實現方法為了對NDEBUG作出正確回應,頭文件的基本結構如下:123456 #undef assert? ? /* remove existing definition */#ifdef NDEBUG#define assert (test) ((void) 0)? /* passive form */#else#define assert (test) ...? ? ? ? /* active form */#endif其中active form的定義如下:1234 void _Assert(char *);#define _STR(x) _VAL(x)#define _VAL(x) #x#define assert(test)? ((test) ? (void) 0 : _Assert(__FILE__":"_STR(__LINE__)" "#test))_Assert()是一個隱藏庫函數,用于調用<stdio.h>中的其它庫函數輸出錯誤信息并調用abort()函數,這個很簡單,沒有什么問題,上述代碼的關鍵在于后面幾行宏定義上。__FILE__及__LINE__這兩個宏是由編譯器定義的,代表當前文件名及當前代碼行號,__FILE__是一個字符串,而__LINE__是一個十進制整數。_STR()及_VAL()這兩個宏神奇的實現了將一個整數常量轉換為字符串字面量的功能,二者缺一不可,也就是說,下面這個寫法是錯誤的:1 #define _STR(x) #x使用這個寫法的話,_STR(__LINE__)得到的是"__LINE__"#就是把宏參數進行字符串處理。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容