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