參見
size_t其實與uintptr_t乙個道理。就是乙個東西。指標其實就是定址,與位址匯流排位數一致,編譯器根據平台決定,從效能和可移植性分析有其由來。
前言:使用size_t可能會提高**的可移植性、有效性或者可讀性,或許同時提高這三者。
在標準c庫中的許多函式使用的引數或者返回值都是表示的用位元組表示的物件大小,比如說malloc(n) 函式的引數n指明了需要申請的空間大小,還有memcpy(s1, s2, n)的最後乙個引數,表明需要複製的記憶體大小,strlen(s)函式的返回值表明了以'\0'結尾的字串的長度(不包括'\0'),其返回值並不是該字串的實際長度,因為要去掉'\0'。
或許你會認為這些引數或者返回值應該被申明為int型別(或者long或者unsigned),但是事實上並不是。c標準中將他們定義為size_t
。標準中記載malloc的申明應該出現在,定義為:
void *malloc(size_t n);
memcpy和strlen的申明應該出現在中:
void *memcpy(void *s1, void const *s2, size_t n);
size_t strlen(char const *s);
size_t還經常出現在c++標準庫中,此外,c++庫中經常會使用乙個相似的型別size_type,用的可能比size_t還要多。
據我所知,大部分的c和c++程式設計師害怕這些庫使用size_t,因為他們不知道size_t代表什麼或者為什麼這些庫需要使用它,歸根結底,原因在於他們什麼時候什麼地方需要用到它。
早期的c語言(由brian kernighan 和 dennis ritchie 在the c programming language書中所寫,prentice-hall, 1978)並沒有提供size_t型別,c標準委員會為了解決移植性問題將size_t引入,舉例如下:
讓我們來寫乙個可移植的標準memcpy函式,我們將會看到一些不同的申明和它們在不同平台不同大小的位址空間上編譯下的情況。
回憶memcpy(s1, s2, n)函式,它將s2指向位址開始的n個位元組拷貝到s2指向的位址,返回s1,這個函式可以拷貝任何資料型別,所以引數和返回值的型別應該為可以指向任何型別的void*
,同時,源位址不應該被改變,所以第二個引數s2型別應該為const void*
,這些都不是問題。
真正的問題在於我們如何申明第三個引數,它代表了源物件的大小,我相信大部分程式設計師都會選擇int:
void *memcpy(void *s1, void const *s2, int n);
使用int型別在大部分情況下都是可以的,但是並不是所有情況下都可以。int是有符號的,它可以表示負數,但是,大小不可能是複數。所以我們可以使用unsigned int代替它讓第三個引數表示的範圍更大。
在大部分機器上,unsigned int的最大值要比int的最大值大兩倍,比如說再也給16位的機器上,unsigned int的最大值為65535,int的最大值為32767。
儘管int型別的大小依賴於c編譯器的實現,但是在給定的平台上int物件的大小和unsigned int物件的大小是一樣的。因此,使用unsigned int修飾第三個引數的代價與int是相同的:
void *memcpy(void *s1, void const *s2, unsigned int n);
這樣似乎沒有問題了,unsigned int可以表示最大型別的物件大小了,這種情況只有在整形和指標型別具有相同大小的情況下,比如說在ip16中,整形和指標都佔2個位元組(16位),而在ip32上面,整形和指標都佔4個位元組(32位)。(參見下面c資料模型表示法)
c資料模型表示法
最近,我偶然發現幾篇文章,他們使用簡明的標記來表述不同目標平台下c語言資料的實現。我還沒有找到這個標記的**,正式的語法,甚至連名字都沒有,但他似乎很簡單,即使沒有正規的定義也可以很容易使用起來。這些標記的一邊形式形如:
i ni l nl ll nll p np。
其中每個大寫字母(或成對出現)代表乙個c的資料型別,每乙個對應的n是這個型別包含的位數。i代表int,l代表long,ll代表long long,以及p代表指標(指向資料,而不是函式)。每個字母和數字都是可選的。
例如,i16p32架構支援16位int和32位指標型別,沒有指明是否支援long或者long long。如果兩個連續的型別具有相同的大小,通常省略第乙個數字。例如,你可以將i16l32p32寫為i16lp32,這是乙個支援16位int,32位long,和32位指標的架構。
標記通常把字母分類在一起,所以可以按照其對應的數字公升序排列。例如,il32ll64p32表示支援32位int,32位long,64位long long和32位指標的架構;然而,通常寫作ilp32ll64。
不幸的是,這種memcpy的申明在i16lp32架構上(整形是16-bit 長整形和指標型別時32-bits)顯得不夠用了,比如說摩托羅拉第一代處理器68000,在這種情況下,處理器可能拷貝的資料大於65535個位元組,但是這個函式第三個引數n不能處理這麼大的資料。
什麼?你說很容易就可以改正?只需要把memcpy的第三個引數的型別修改一下:
void *memcpy(void *s1, void const *s2, unsigned long n);
你可以在i16lp32目標架構上使用這個函式了,它可以處理更大的資料。而且在ip16和ip32平台上效果也還行,說明它確實給出了memcpy的一種移植性較好的申明。但是,在ip16平台上相比於使用unsigned int
,你使用unsigned long
可能會使你的**執行效率大打折扣(**量變大而且執行變慢)。
在標準c中規定,長整形(無論無符號或者有符號)至少占用32位,因此在ip16平台上支援標準c的話,那麼它一定是ip16l32 平台。這些平台通常使用一對16位的字來實現32位的長整形。在這種情況下,移動乙個長整形需要兩條機器指令,每條移動乙個16位的塊。事實上,這個平台上的大部分的32位操作都需要至上兩條指令。
因此,以可移植性為名將memcpy的第三個引數申明為unsigned long而降低某些平台的效能是我們所不希望看到的。使用size_t可以有效避免這種情況。
size_t型別是乙個型別定義,通常將一些無符號的整形定義為size_t,比如說unsigned int或者unsigned long,甚至unsigned long long。每乙個標準c實現應該選擇足夠大的無符號整形來代表該平台上最大可能出現的物件大小。
n = sizeof(thing);
考慮到可移植性和程式效率,n應該被申明為size_t型別。類似的,下面的foo函式的引數也應當被申明為sizeof:
foo(sizeof(thing));
引數中帶有size_t的函式通常會含有區域性變數用來對陣列的大小或者索引進行計算,在這種情況下,size_t是個不錯的選擇。
適當地使用size_t還會使你的**變得如同自帶文件。當你看到乙個物件宣告為size_t型別,你馬上就知道它代表位元組大小或陣列索引,而不是錯誤**或者是乙個普通的算術值。
在我接下來的一些文章的例子中會使用size_t,敬請期待!
為什麼練習很重要?
當你認識到1 1 2時,你需要去練習它嗎?它是乙個元知識,是固定的,原則性的。你對它的整個學習過程,到意識到它的層面,就足夠了。如若是由此發展出來的加法運算呢?它是一種方法,一種推理過程,它有兩個可變的引數。你需要練習,以使你的大腦遇見這兩個可變引數中的某些組合形式,以此總結某些規律,並運用規律來提...
為什麼提問能力很重要?
問題的定義 提問的時候,首先要發現問題,如何發現問題,需要定義問題。提問問題是對學習思考後的產物 再提問題過程中 可以和解答者對問題有更深的認識。也可以作為對學習的校驗和擴充 有提問能力說明在積極主動思考,很多時候提出乙個問題比找到答案更重要。很多事情都是通過不停提問題而越來越明確 側面反應了思考能...
為什麼IT安全組如此重要?
安全組會保護亦會破壞您的it網路安全,該組成員主要負責管理網路中的訪問許可權,如對資源和資料的訪問許可權。但您有沒有想過,乙個組成員身份的配置錯誤可能會導致一些安全事件的發生?深度剖析組成員許可權 在安全方面,active directory和azure ad中的組成員身份通常會被低估,成員身份通常...