您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“有哪些C語(yǔ)言的高級(jí)用法”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“有哪些C語(yǔ)言的高級(jí)用法”吧!
整形溢出和提升
大部分 C 程序員都以為基本的整形操作都是安全的其實(shí)不然,看下面這個(gè)例子,你覺(jué)得輸出結(jié)果是什么:
int main(int argc, char** argv) { long i = -1; if (i < sizeof(i)) { printf("OK\n"); } else { printf("error\n"); } return 0; }
當(dāng)一個(gè)變量轉(zhuǎn)換成無(wú)符號(hào)整形時(shí),i的值不再是-1,而是 size_t的最大值,因?yàn)閟izeof操作返回的是一個(gè) size_t類(lèi)型的無(wú)符號(hào)數(shù)。在C99/C11標(biāo)準(zhǔn)里寫(xiě)道:
“If the operand that has unsigned integer type has rank greater orequal to the rank of the type of the other operand, then the operandwith signed integer type is converted to the type of the operand withunsigned integer type.”
在C標(biāo)準(zhǔn)里面 size_t至少是一個(gè) 16 位的無(wú)符號(hào)整數(shù),對(duì)于給定的架構(gòu) size_t 一般對(duì)應(yīng)long,所以sizeof(int)和size_t至少相等,這就帶來(lái)了可移植性的問(wèn)題,C標(biāo)準(zhǔn)沒(méi)有定義 short, int,long,longlong的大小,只是說(shuō)明了他們的最小長(zhǎng)度,對(duì)于 x86_64 架構(gòu),long在Linux下是64位,而在64位Windows下是32位。一般的方法是采用固定長(zhǎng)度的類(lèi)型比如定義在C99頭文件stdint.h中的uint16_t,int32_t,uint_least16_t,uint_fast16_t等。
如果 int可以表示原始類(lèi)型的所有值,那么這個(gè)操作數(shù)會(huì)轉(zhuǎn)換成 int,否則他會(huì)轉(zhuǎn)換成 unsigned int。下面這個(gè)函數(shù)在 32 位平臺(tái)返回 65536,但是在 16 位系統(tǒng)返回 0。
uint32_t sum() { uint16_t a = 65535; uint16_t b = 1; return a+b; }
對(duì)于char 類(lèi)型到底是 signed 還是 unsigned 取決于硬件架構(gòu)和操作系統(tǒng),通常由特定平臺(tái)的 ABI(Application Binary Interface) 指定,如果是 signed char,下面的代碼輸出-128 和-127,否則輸出 128,129(x86 架構(gòu))。
char c = 128; char d = 129; printf("%d,%d\n",c,d);
malloc 函數(shù)分配制定字節(jié)大小的內(nèi)存,對(duì)象未被初始化,如果 size 是 0 取決與系統(tǒng)實(shí)現(xiàn)。malloc(0)返回一個(gè)空指針或者 unique pointer,如果 size 是表達(dá)式的運(yùn)算結(jié)果,確保沒(méi)有整形溢出。
“If the size of the space requested is 0, the behavior isimplementation- defined: the value returned shall be either a nullpointer or a unique pointer.”
size_t computed_size; if (elem_size && num > SIZE_MAX / elem_size) { errno = ENOMEM; err(1, "overflow"); } computed_size = elem_size*num;
malloc不會(huì)給分配的內(nèi)存初始化,如果要對(duì)新分配的內(nèi)存初始化,可以用calloc代替malloc,一般情況下給序列分配相等大小的元素時(shí),用calloc來(lái)代替用表達(dá)式計(jì)算大小,calloc 會(huì)把內(nèi)存初始化為 0。
realloc 用來(lái)對(duì)已經(jīng)分配內(nèi)存的對(duì)象改變大小,如果新的 size 更大,額外的空間沒(méi) 有 被 初 始 化 , 如 果 提 供 給 realloc 的 指 針 是 空 指 針 , realloc 就 等 效 于malloc,如果原指針?lè)强斩?new size是0,結(jié)果依賴(lài)于操作系統(tǒng)的具體實(shí)現(xiàn)。
“In case of failure realloc shall return NULL and leave provided memoryobject intact. Thus it is important not only to check for integeroverflow of size argument, but also to correctly handle object size ifrealloc fails.”
下面這段代碼可以帶你領(lǐng)會(huì)malloc,calloc,realloc,free的用法:
#include <stdio.h> #include <stdint.h> #include <malloc.h> #include <errno.h> #define VECTOR_OK 0 #define VECTOR_NULL_ERROR 1 #define VECTOR_SIZE_ERROR 2 #define VECTOR_ALLOC_ERROR 3 struct vector { int *data; size_t size; }; int create_vector(struct vector *vc, size_t num) { if (vc == NULL) { return VECTOR_NULL_ERROR; } vc->data = 0; vc->size = 0; /* check for integer and SIZE_MAX overflow */ if (num == 0 || SIZE_MAX / num < sizeof(int)) { errno = ENOMEM; return VECTOR_SIZE_ERROR; } vc->data = calloc(num, sizeof(int)); /* calloc faild */ if (vc->data == NULL) { return VECTOR_ALLOC_ERROR; } vc->size = num * sizeof(int); return VECTOR_OK; } int grow_vector(struct vector *vc) { void *newptr = 0; size_t newsize; if (vc == NULL) { return VECTOR_NULL_ERROR; } /* check for integer and SIZE_MAX overflow */ if (vc->size == 0 || SIZE_MAX / 2 < vc->size) { errno = ENOMEM; return VECTOR_SIZE_ERROR; } newsize = vc->size * 2; newptr = realloc(vc->data, newsize); /* realloc faild; vector stays intact size was not changed */ if (newptr == NULL) { return VECTOR_ALLOC_ERROR; } /* upon success; update new address and size */ vc->data = newptr; vc->size = newsize; return VECTOR_OK; }
使用未初始化的變量,C語(yǔ)言要求所有變量在使用之前要初始化,使用未初始化的變量會(huì)造成為定義的行為,這和C++不同,C++保證所有變量在使用之前都得到初始化,Java盡量保證變量使用前的得到初始化,如類(lèi)基本數(shù)據(jù)成員會(huì)被初始化為默認(rèn)值。
free錯(cuò)誤對(duì)空指針調(diào)用 free,對(duì)不是由 malloc family 函數(shù)分配的指針調(diào)用 free,或者對(duì)已經(jīng)調(diào)用 free 的指針再次調(diào)用 free。一開(kāi)始初始化指針為NULL可以減少錯(cuò)誤,GCC和Clang編譯器有-Wuninitialized 選項(xiàng)來(lái)對(duì)未初始化的變量顯示警告信息,另外不要將同一個(gè)指針用于靜態(tài)變量和動(dòng)態(tài)變量。
char *ptr = NULL; void nullfree(void **pptr) { void *ptr = *pptr; assert(ptr != NULL) free(ptr); *pptr = NULL; }
3.對(duì)空指針解引用,數(shù)組越界訪(fǎng)問(wèn)
對(duì)NULL指針或者free’d內(nèi)存解引用,數(shù)組越界訪(fǎng)問(wèn),是很明顯的錯(cuò)誤,為了消除這種錯(cuò)誤,一般的做法就是增加數(shù)組越界檢查的功能,比如Java里的array就有下標(biāo)檢查的功能,但是這樣會(huì)帶來(lái)嚴(yán)重的性能代價(jià),我們要修改ABI(application binary interface),讓每個(gè)指針都跟隨著它的范圍信息,在數(shù)值計(jì)算中cost is terrible。
4.違反類(lèi)型規(guī)則
把int×指針cast成float×,然后對(duì)它解引用,在C里面會(huì)引發(fā)undefined behavior,C規(guī)定這種類(lèi)型的轉(zhuǎn)換需要使用memset,C++里面有個(gè)reinterpret_cast函數(shù)用于無(wú)關(guān)類(lèi)型之間的轉(zhuǎn)換,reinterpret_cast (expression)
內(nèi)存泄漏發(fā)生在程序不再使用的動(dòng)態(tài)內(nèi)存沒(méi)有得到釋放,這需要我們掌握動(dòng)態(tài)分配對(duì)象的作用域,尤其是什么時(shí)候該調(diào)用free來(lái)釋放內(nèi)存,常用的集中方法如下:
在程序啟動(dòng)的時(shí)候分配在程序啟動(dòng)的時(shí)候分配需要的heap memory,程序退出時(shí)把釋放的任務(wù)交給操作系統(tǒng),這種方法一般適用于程序運(yùn)行后馬上退出的那種。
使用變長(zhǎng)數(shù)組(VLA)如果你需要一塊變長(zhǎng)大小的空間并且作用域在函數(shù)中,變長(zhǎng)數(shù)組可以幫到你,但是也有一個(gè)限制,一個(gè)函數(shù)中的變長(zhǎng)數(shù)組內(nèi)存大小一般不超過(guò)幾百字節(jié),這個(gè)數(shù)字C標(biāo)準(zhǔn)沒(méi)有明確的定義,最好是把內(nèi)存分配到棧上,在棧上允許分配的最大VLA內(nèi)存是SIZE_MAX,掌握目標(biāo)平臺(tái)的棧大小可以有效的防止棧溢出。
使用引用計(jì)數(shù)引用計(jì)數(shù)是一個(gè)很好的管理內(nèi)存的方法,特別是當(dāng)你不希望自己定義的對(duì)象被復(fù)制時(shí),每一次賦值把引用計(jì)數(shù)加1,每次失去引用就把引用計(jì)數(shù)減1,當(dāng)引用計(jì)數(shù)等于0時(shí),以為的對(duì)象已經(jīng)不再需要了,我們需要釋放對(duì)象占用的內(nèi)存,由于C不提供自動(dòng)的析構(gòu)函數(shù),我們必須手動(dòng)釋放內(nèi)存,看一個(gè)例子:
#include <stdlib.h> #include <stdint.h> #define MAX_REF_OBJ 100 #define RC_ERROR -1 struct mem_obj_t{ void *ptr; uint16_t count; }; static struct mem_obj_t references[MAX_REF_OBJ]; static uint16_t reference_count = 0; /* create memory object and return handle */ uint16_t create(size_t size){ if (reference_count >= MAX_REF_OBJ) return RC_ERROR; if (size){ void *ptr = calloc(1, size); if (ptr != NULL){ references[reference_count].ptr = ptr; references[reference_count].count = 0; return reference_count++; } } return RC_ERROR; }
/* get memory object and increment reference counter */ void* retain(uint16_t handle){ if(handle < reference_count && handle >= 0){ references[handle].count++; return references[handle].ptr; } else { return NULL; } } /* decrement reference counter */ void release(uint16_t handle){ printf("release\n"); if(handle < reference_count && handle >= 0){ struct mem_obj_t *object = &references[handle]; if (object->count <= 1){ printf("released\n"); free(object->ptr); reference_count--; } else { printf("decremented\n"); object->count--; } } }
C++標(biāo)準(zhǔn)庫(kù)有個(gè)auto_ptr智能指針,能夠自動(dòng)釋放指針?biāo)笇?duì)象的內(nèi)存,C++ boost庫(kù)有個(gè)boost::shared_ptr智能指針,內(nèi)置引用計(jì)數(shù),支持拷貝和賦值,看下面這個(gè)例子:
“Objects of shared_ptr types have the ability of taking ownership of a pointer and share that ownership: once they take ownership, the group of owners of a pointer become responsible for its deletion when the last one of them releases that ownership.”
#include <boost/smart_ptr.hpp> #include <iostream> int main() { // Basic useage boost::shared_ptr<int> p1(new int(10)); std::cout << "ref count of p1: " << p1.use_count() << std::endl; boost::shared_ptr<int> p2(p1); // or p2 = p1; std::cout << "ref count of p1: " << p1.use_count() << std::endl; *p1 = 999; std::cout << "*p2: " << *p2 << std::endl; p2.reset(); std::cout << "ref count of p1: " << p1.use_count() << std::endl; return 0; }
4.內(nèi)存池,有利于減少內(nèi)存碎片,看下面這個(gè)例子:
#include <stdlib.h> #include <stdint.h> struct mem_pool_t{ void* ptr;//指向內(nèi)存池起始地址 size_t size;//內(nèi)存池大小 size_t used;//已用內(nèi)存大小 }; //create memory pool struct mem_pool_t* create_pool(size_t size){ mem_pool_t* pool=calloc(1,sizeof(struct men_pool_t)); if(pool!=NULL){ void* mem=calloc(1,size); if(mem!=NULL){ pool->ptr=mem; pool->size=size; pool->used=0; return pool; } } return NULL; } //allocate memory from pool void* pool_alloc(mem_pool_t* pool,size_t size){ if(pool=NULL) return NULL; size_t bytes_left=pool->size-pool->used; if(size&&size<=bytes_left){ void* mem=pool->ptr+pool->used; pool->used+=size; return mem; } return NULL; } //release memory of the pool void pool_free(mem_pool_t* pool){ if(pool!=NULL){ free(pool->ptr); free(pool); } }
5.垃圾回收機(jī)制引用計(jì)數(shù)采用的方法是當(dāng)內(nèi)存不再需要時(shí)得到手動(dòng)釋放,垃圾回收發(fā)生在內(nèi)存分配失敗或者內(nèi)存到達(dá)一定的水位(watermarks),實(shí)現(xiàn)垃圾回收最簡(jiǎn)單的一個(gè)算法是MARK AND SWEEP算法,該算法的思路是遍歷所有動(dòng)態(tài)分配對(duì)象的內(nèi)存,標(biāo)記那些還能繼續(xù)使用的,回收那些沒(méi)有被標(biāo)記的內(nèi)存。Java采用的垃圾回收機(jī)制就更復(fù)雜了,思路也是回收那些不再使用的內(nèi)存,JAVA的垃圾回收和C++的析構(gòu)函數(shù)又不一樣,C++保證對(duì)象在使用之前得到初始化,對(duì)象超出作用域之后內(nèi)存得到釋放,而JAVA不能保證對(duì)象一定被析構(gòu)。
我們一般的概念里指針和數(shù)組名是可互換的,但是在編譯器里他們被不同的對(duì)待,當(dāng)我們說(shuō)一個(gè)對(duì)象或者表達(dá)式具有某種類(lèi)型的時(shí)候我們一般是說(shuō)這個(gè)對(duì)象是個(gè)左值(lvalue),當(dāng)對(duì)象不是const的時(shí)候,左值是可以修改的,比如對(duì)象是復(fù)制操作符的左參數(shù),而數(shù)組名是一個(gè)const左值,指向地一個(gè)元素的const指針,所以你不能給數(shù)組名賦值或者意圖改變數(shù)組名,如果表達(dá)式是數(shù)組類(lèi)型,數(shù)組名通常轉(zhuǎn)換成指向地一個(gè)元素的指針。
但是也有例外,什么情況下數(shù)組名不是一個(gè)指針呢?1.當(dāng)它是sizeof操作符的操作數(shù)時(shí),返回?cái)?shù)組占的內(nèi)存字節(jié)數(shù)2.當(dāng)它是取地址操作&的操作數(shù)時(shí),返回一個(gè)數(shù)組的地址
看下面這個(gè)例子:
short a[] = {1,2,3}; short *pa; short (*px)[]; void init(){ pa = a; px = &a; printf("a:%p; pa:%p; px:%p\n", a, pa, px); printf("a[1]:%i; pa[1]:%i (*px)[1]:%i\n", a[1], pa[1],(*px)[1]); }
a是一個(gè)short類(lèi)型數(shù)組,pa是一個(gè)指向short類(lèi)型的指針,px呢?px是一個(gè)指向數(shù)組類(lèi)型的指針,在a被賦值給pa之前,他的值被轉(zhuǎn)換成一個(gè)指向數(shù)組第一個(gè)元素的指針,下面那個(gè)a卻沒(méi)有轉(zhuǎn)換,因?yàn)橛龅降氖?amp;操作符。數(shù)組下標(biāo)a[1]等價(jià)于(a+1),和p[1]一樣,也指向(p+1),但是兩者還是有區(qū)別的,a是一個(gè)數(shù)組,它實(shí)際上存儲(chǔ)的是第一個(gè)元素的地址,所以數(shù)組a是用來(lái)定位第一個(gè)元素的,而pa不一樣,它就是一個(gè)指針,不是用來(lái)定位的。再比如:
int a[10]; int b[10]; int *a; c=&a[0];//c是指向數(shù)組a地一個(gè)元素的指針 c=a;//a自動(dòng)轉(zhuǎn)換成指向第一個(gè)元素的指針,實(shí)際上是指針拷貝 b=a;//非法的,你不能用賦值符把一個(gè)數(shù)組的所有元素賦給另一個(gè)數(shù)組 a=c;//非法的,你不能修改const指針的值
到此,相信大家對(duì)“有哪些C語(yǔ)言的高級(jí)用法”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。