本篇內(nèi)容主要講解“有哪些C語言的高級用法”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“有哪些C語言的高級用法”吧!
創(chuàng)新互聯(lián)成立以來不斷整合自身及行業(yè)資源、不斷突破觀念以使企業(yè)策略得到完善和成熟,建立了一套“以技術(shù)為基點,以客戶需求中心、市場為導向”的快速反應(yīng)體系。對公司的主營項目,如中高端企業(yè)網(wǎng)站企劃 / 設(shè)計、行業(yè) / 企業(yè)門戶設(shè)計推廣、行業(yè)門戶平臺運營、重慶App定制開發(fā)、移動網(wǎng)站建設(shè)、微信網(wǎng)站制作、軟件開發(fā)、服務(wù)器托管等實行標準化操作,讓客戶可以直觀的預(yù)知到從創(chuàng)新互聯(lián)可以獲得的服務(wù)效果。
整形溢出和提升
大部分 C 程序員都以為基本的整形操作都是安全的其實不然,看下面這個例子,你覺得輸出結(jié)果是什么:
int main(int argc, char** argv) { long i = -1; if (i < sizeof(i)) { printf("OK\n"); } else { printf("error\n"); } return 0; }
當一個變量轉(zhuǎn)換成無符號整形時,i的值不再是-1,而是 size_t的最大值,因為sizeof操作返回的是一個 size_t類型的無符號數(shù)。在C99/C11標準里寫道:
“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標準里面 size_t至少是一個 16 位的無符號整數(shù),對于給定的架構(gòu) size_t 一般對應(yīng)long,所以sizeof(int)和size_t至少相等,這就帶來了可移植性的問題,C標準沒有定義 short, int,long,longlong的大小,只是說明了他們的最小長度,對于 x86_64 架構(gòu),long在Linux下是64位,而在64位Windows下是32位。一般的方法是采用固定長度的類型比如定義在C99頭文件stdint.h中的uint16_t,int32_t,uint_least16_t,uint_fast16_t等。
如果 int可以表示原始類型的所有值,那么這個操作數(shù)會轉(zhuǎn)換成 int,否則他會轉(zhuǎn)換成 unsigned int。下面這個函數(shù)在 32 位平臺返回 65536,但是在 16 位系統(tǒng)返回 0。
uint32_t sum() { uint16_t a = 65535; uint16_t b = 1; return a+b; }
對于char 類型到底是 signed 還是 unsigned 取決于硬件架構(gòu)和操作系統(tǒng),通常由特定平臺的 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)存,對象未被初始化,如果 size 是 0 取決與系統(tǒng)實現(xiàn)。malloc(0)返回一個空指針或者 unique pointer,如果 size 是表達式的運算結(jié)果,確保沒有整形溢出。
“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不會給分配的內(nèi)存初始化,如果要對新分配的內(nèi)存初始化,可以用calloc代替malloc,一般情況下給序列分配相等大小的元素時,用calloc來代替用表達式計算大小,calloc 會把內(nèi)存初始化為 0。
realloc 用來對已經(jīng)分配內(nèi)存的對象改變大小,如果新的 size 更大,額外的空間沒 有 被 初 始 化 , 如 果 提 供 給 realloc 的 指 針 是 空 指 針 , realloc 就 等 效 于malloc,如果原指針非空而 new size是0,結(jié)果依賴于操作系統(tǒng)的具體實現(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)會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語言要求所有變量在使用之前要初始化,使用未初始化的變量會造成為定義的行為,這和C++不同,C++保證所有變量在使用之前都得到初始化,Java盡量保證變量使用前的得到初始化,如類基本數(shù)據(jù)成員會被初始化為默認值。
free錯誤對空指針調(diào)用 free,對不是由 malloc family 函數(shù)分配的指針調(diào)用 free,或者對已經(jīng)調(diào)用 free 的指針再次調(diào)用 free。一開始初始化指針為NULL可以減少錯誤,GCC和Clang編譯器有-Wuninitialized 選項來對未初始化的變量顯示警告信息,另外不要將同一個指針用于靜態(tài)變量和動態(tài)變量。
char *ptr = NULL; void nullfree(void **pptr) { void *ptr = *pptr; assert(ptr != NULL) free(ptr); *pptr = NULL; }
3.對空指針解引用,數(shù)組越界訪問
對NULL指針或者free’d內(nèi)存解引用,數(shù)組越界訪問,是很明顯的錯誤,為了消除這種錯誤,一般的做法就是增加數(shù)組越界檢查的功能,比如Java里的array就有下標檢查的功能,但是這樣會帶來嚴重的性能代價,我們要修改ABI(application binary interface),讓每個指針都跟隨著它的范圍信息,在數(shù)值計算中cost is terrible。
4.違反類型規(guī)則
把int×指針cast成float×,然后對它解引用,在C里面會引發(fā)undefined behavior,C規(guī)定這種類型的轉(zhuǎn)換需要使用memset,C++里面有個reinterpret_cast函數(shù)用于無關(guān)類型之間的轉(zhuǎn)換,reinterpret_cast (expression)
內(nèi)存泄漏發(fā)生在程序不再使用的動態(tài)內(nèi)存沒有得到釋放,這需要我們掌握動態(tài)分配對象的作用域,尤其是什么時候該調(diào)用free來釋放內(nèi)存,常用的集中方法如下:
在程序啟動的時候分配在程序啟動的時候分配需要的heap memory,程序退出時把釋放的任務(wù)交給操作系統(tǒng),這種方法一般適用于程序運行后馬上退出的那種。
使用變長數(shù)組(VLA)如果你需要一塊變長大小的空間并且作用域在函數(shù)中,變長數(shù)組可以幫到你,但是也有一個限制,一個函數(shù)中的變長數(shù)組內(nèi)存大小一般不超過幾百字節(jié),這個數(shù)字C標準沒有明確的定義,最好是把內(nèi)存分配到棧上,在棧上允許分配的最大VLA內(nèi)存是SIZE_MAX,掌握目標平臺的棧大小可以有效的防止棧溢出。
使用引用計數(shù)引用計數(shù)是一個很好的管理內(nèi)存的方法,特別是當你不希望自己定義的對象被復制時,每一次賦值把引用計數(shù)加1,每次失去引用就把引用計數(shù)減1,當引用計數(shù)等于0時,以為的對象已經(jīng)不再需要了,我們需要釋放對象占用的內(nèi)存,由于C不提供自動的析構(gòu)函數(shù),我們必須手動釋放內(nèi)存,看一個例子:
#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++標準庫有個auto_ptr智能指針,能夠自動釋放指針所指對象的內(nèi)存,C++ boost庫有個boost::shared_ptr智能指針,內(nèi)置引用計數(shù),支持拷貝和賦值,看下面這個例子:
“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)存碎片,看下面這個例子:
#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.垃圾回收機制引用計數(shù)采用的方法是當內(nèi)存不再需要時得到手動釋放,垃圾回收發(fā)生在內(nèi)存分配失敗或者內(nèi)存到達一定的水位(watermarks),實現(xiàn)垃圾回收最簡單的一個算法是MARK AND SWEEP算法,該算法的思路是遍歷所有動態(tài)分配對象的內(nèi)存,標記那些還能繼續(xù)使用的,回收那些沒有被標記的內(nèi)存。Java采用的垃圾回收機制就更復雜了,思路也是回收那些不再使用的內(nèi)存,JAVA的垃圾回收和C++的析構(gòu)函數(shù)又不一樣,C++保證對象在使用之前得到初始化,對象超出作用域之后內(nèi)存得到釋放,而JAVA不能保證對象一定被析構(gòu)。
我們一般的概念里指針和數(shù)組名是可互換的,但是在編譯器里他們被不同的對待,當我們說一個對象或者表達式具有某種類型的時候我們一般是說這個對象是個左值(lvalue),當對象不是const的時候,左值是可以修改的,比如對象是復制操作符的左參數(shù),而數(shù)組名是一個const左值,指向地一個元素的const指針,所以你不能給數(shù)組名賦值或者意圖改變數(shù)組名,如果表達式是數(shù)組類型,數(shù)組名通常轉(zhuǎn)換成指向地一個元素的指針。
但是也有例外,什么情況下數(shù)組名不是一個指針呢?1.當它是sizeof操作符的操作數(shù)時,返回數(shù)組占的內(nèi)存字節(jié)數(shù)2.當它是取地址操作&的操作數(shù)時,返回一個數(shù)組的地址
看下面這個例子:
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是一個short類型數(shù)組,pa是一個指向short類型的指針,px呢?px是一個指向數(shù)組類型的指針,在a被賦值給pa之前,他的值被轉(zhuǎn)換成一個指向數(shù)組第一個元素的指針,下面那個a卻沒有轉(zhuǎn)換,因為遇到的是&操作符。數(shù)組下標a[1]等價于(a+1),和p[1]一樣,也指向(p+1),但是兩者還是有區(qū)別的,a是一個數(shù)組,它實際上存儲的是第一個元素的地址,所以數(shù)組a是用來定位第一個元素的,而pa不一樣,它就是一個指針,不是用來定位的。再比如:
int a[10]; int b[10]; int *a; c=&a[0];//c是指向數(shù)組a地一個元素的指針 c=a;//a自動轉(zhuǎn)換成指向第一個元素的指針,實際上是指針拷貝 b=a;//非法的,你不能用賦值符把一個數(shù)組的所有元素賦給另一個數(shù)組 a=c;//非法的,你不能修改const指針的值
到此,相信大家對“有哪些C語言的高級用法”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學習!
網(wǎng)頁題目:有哪些C語言的高級用法
標題來源:http://vcdvsql.cn/article16/pddgdg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計公司、響應(yīng)式網(wǎng)站、域名注冊、做網(wǎng)站、App開發(fā)、手機網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)